mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-11 17:27:31 +02:00
Show the latest announcement in a modal on the dashboard
Fetch and display the most recent announcement created within the last two days in a modal on the dashboard page. Replit-Commit-Author: Agent Replit-Commit-Session-Id: f9197b9b-a5b3-4469-a27d-930a9e9ee736 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/f9197b9b-a5b3-4469-a27d-930a9e9ee736/b03w3Om
This commit is contained in:
1 parent
02809d6463
commit
070da4a129
2 files changed
+170
-5
No files matched your search
@@ -1,17 +1,20 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useAuthUnified } from "@/hooks/useAuthUnified";
|
||||
import { useStore } from "@/components/Layout";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Calendar, Package, ShoppingCart, TrendingUp, Clock, MapPin, User, AlertTriangle, CheckCircle, Truck, FileText, BarChart3, Megaphone, Shield, XCircle, CheckSquare, Circle } from "lucide-react";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Calendar, Package, ShoppingCart, TrendingUp, Clock, MapPin, User, AlertTriangle, CheckCircle, Truck, FileText, BarChart3, Megaphone, Shield, XCircle, CheckSquare, Circle, Info, Star, Sparkles, X } from "lucide-react";
|
||||
import { safeFormat, safeDate } from "@/lib/dateUtils";
|
||||
import type { PublicityWithRelations } from "@shared/schema";
|
||||
import type { PublicityWithRelations, DashboardMessage } from "@shared/schema";
|
||||
import AnnouncementCard from "@/components/AnnouncementCard";
|
||||
|
||||
export default function Dashboard() {
|
||||
const { user } = useAuthUnified();
|
||||
const { selectedStoreId } = useStore();
|
||||
const [showAnnouncementModal, setShowAnnouncementModal] = useState(false);
|
||||
|
||||
const { data: stats } = useQuery({
|
||||
queryKey: ['/api/stats/monthly', selectedStoreId],
|
||||
@@ -56,6 +59,106 @@ export default function Dashboard() {
|
||||
queryKey: [customerOrdersUrl, selectedStoreId],
|
||||
});
|
||||
|
||||
// Récupérer la dernière annonce récente (créée il y a moins de 2 jours)
|
||||
const { data: recentAnnouncement } = useQuery<DashboardMessage | null>({
|
||||
queryKey: ['/api/announcements/recent', selectedStoreId],
|
||||
queryFn: async () => {
|
||||
const params = new URLSearchParams();
|
||||
if (selectedStoreId && user?.role === 'admin') {
|
||||
params.append('storeId', selectedStoreId.toString());
|
||||
}
|
||||
params.append('recent', 'true');
|
||||
|
||||
const response = await fetch(`/api/announcements?${params.toString()}`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const announcements: DashboardMessage[] = await response.json();
|
||||
|
||||
// Filtrer les annonces créées il y a moins de 2 jours côté client
|
||||
const twoDaysAgo = new Date();
|
||||
twoDaysAgo.setDate(twoDaysAgo.getDate() - 2);
|
||||
|
||||
const recentAnnouncements = announcements.filter(announcement => {
|
||||
if (!announcement.createdAt) return false;
|
||||
const createdAt = safeDate(announcement.createdAt);
|
||||
return createdAt && createdAt >= twoDaysAgo;
|
||||
});
|
||||
|
||||
// Retourner la plus récente
|
||||
return recentAnnouncements.length > 0
|
||||
? recentAnnouncements.sort((a, b) => {
|
||||
const dateA = safeDate(a.createdAt);
|
||||
const dateB = safeDate(b.createdAt);
|
||||
return (dateB?.getTime() || 0) - (dateA?.getTime() || 0);
|
||||
})[0]
|
||||
: null;
|
||||
},
|
||||
enabled: !!user,
|
||||
});
|
||||
|
||||
// Fonction pour obtenir la configuration des priorités d'annonces
|
||||
const getAnnouncementPriorityConfig = (type: string) => {
|
||||
switch (type) {
|
||||
case 'error':
|
||||
return {
|
||||
label: 'Nouveauté',
|
||||
color: 'bg-purple-100 text-purple-800',
|
||||
icon: Sparkles,
|
||||
iconColor: 'text-purple-600'
|
||||
};
|
||||
case 'warning':
|
||||
return {
|
||||
label: 'Attention',
|
||||
color: 'bg-orange-100 text-orange-800',
|
||||
icon: AlertTriangle,
|
||||
iconColor: 'text-orange-600'
|
||||
};
|
||||
case 'success':
|
||||
return {
|
||||
label: 'Important',
|
||||
color: 'bg-red-100 text-red-800',
|
||||
icon: Star,
|
||||
iconColor: 'text-red-600'
|
||||
};
|
||||
case 'info':
|
||||
default:
|
||||
return {
|
||||
label: 'Information',
|
||||
color: 'bg-blue-100 text-blue-800',
|
||||
icon: Info,
|
||||
iconColor: 'text-blue-600'
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Effet pour afficher automatiquement le modal si une annonce récente existe
|
||||
useEffect(() => {
|
||||
if (recentAnnouncement && !showAnnouncementModal) {
|
||||
// Vérifier si l'utilisateur a déjà vu cette annonce (localStorage)
|
||||
const dismissedAnnouncements = JSON.parse(localStorage.getItem('dismissedAnnouncements') || '[]');
|
||||
if (!dismissedAnnouncements.includes(recentAnnouncement.id)) {
|
||||
setShowAnnouncementModal(true);
|
||||
}
|
||||
}
|
||||
}, [recentAnnouncement, showAnnouncementModal]);
|
||||
|
||||
// Fonction pour fermer le modal et marquer l'annonce comme vue
|
||||
const handleCloseAnnouncementModal = () => {
|
||||
setShowAnnouncementModal(false);
|
||||
if (recentAnnouncement) {
|
||||
const dismissedAnnouncements = JSON.parse(localStorage.getItem('dismissedAnnouncements') || '[]');
|
||||
if (!dismissedAnnouncements.includes(recentAnnouncement.id)) {
|
||||
dismissedAnnouncements.push(recentAnnouncement.id);
|
||||
localStorage.setItem('dismissedAnnouncements', JSON.stringify(dismissedAnnouncements));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Récupérer les publicités à venir (chercher dans 2024 ET 2025) - TOUTES les publicités
|
||||
const { data: upcomingPublicities = [] } = useQuery<PublicityWithRelations[]>({
|
||||
queryKey: ['/api/publicities', 'upcoming'],
|
||||
@@ -508,8 +611,7 @@ export default function Dashboard() {
|
||||
<div className="text-right space-y-1">
|
||||
<div className="flex items-center justify-end space-x-2">
|
||||
<Badge
|
||||
variant={priorityConfig.color}
|
||||
className="text-xs flex items-center gap-1"
|
||||
className={`text-xs flex items-center gap-1 ${priorityConfig.color}`}
|
||||
>
|
||||
<PriorityIcon className="h-3 w-3" />
|
||||
{priorityConfig.label}
|
||||
@@ -530,6 +632,69 @@ export default function Dashboard() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Modal pour les annonces récentes */}
|
||||
<Dialog open={showAnnouncementModal} onOpenChange={handleCloseAnnouncementModal}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Megaphone className="h-5 w-5 text-blue-600" />
|
||||
Nouvelle Annonce
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{recentAnnouncement && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
{(() => {
|
||||
const config = getAnnouncementPriorityConfig(recentAnnouncement.type);
|
||||
const IconComponent = config.icon;
|
||||
return (
|
||||
<>
|
||||
<IconComponent className={`h-4 w-4 ${config.iconColor}`} />
|
||||
<Badge className={config.color}>
|
||||
{config.label}
|
||||
</Badge>
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
<p className="text-sm text-gray-500">
|
||||
{safeFormat(recentAnnouncement.createdAt, "d MMMM yyyy 'à' HH:mm")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
||||
{recentAnnouncement.title}
|
||||
</h3>
|
||||
<div className="prose prose-sm max-w-none">
|
||||
<p className="text-gray-700 whitespace-pre-wrap">
|
||||
{recentAnnouncement.content}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{recentAnnouncement.createdBy && (
|
||||
<div className="flex items-center gap-2 pt-2 border-t border-gray-100">
|
||||
<User className="h-4 w-4 text-gray-400" />
|
||||
<span className="text-sm text-gray-600">
|
||||
Publié par {recentAnnouncement.createdBy}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end pt-4">
|
||||
<Button onClick={handleCloseAnnouncementModal} className="flex items-center gap-2">
|
||||
<X className="h-4 w-4" />
|
||||
Fermer
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -35,7 +35,7 @@ The platform is built with a clear separation of concerns, utilizing a modern fu
|
||||
- **Invoice Verification Cache:** Robust caching system for invoice verification results with automatic expiration and conflict resolution. Enhanced with unique constraint handling and graceful error management for production stability (August 2025). Fixed production SSL certificate errors and fetch timeout loops with enhanced error handling, network timeout protection, and error result caching to prevent infinite retry loops (August 2025). **MAJOR BUG FIX (August 2025)**: Resolved critical cache system failure - MemStorage was returning `undefined` for all cache lookups, preventing cache functionality entirely. Implemented working in-memory cache with Map-based storage and expiration logic. Fixed development mode to properly save verification results to cache. Added comprehensive cache operation logging for debugging. Verified complete cache workflow: miss → save → hit cycle working perfectly. **CRITICAL PRODUCTION FIX (August 2025)**: Fixed DatabaseStorage cache expiration bug - production was returning expired cache entries instead of checking expiration dates, causing repeated API calls despite existing cache. Added proper expiration verification and automatic cleanup of expired cache entries in PostgreSQL, matching MemStorage behavior.
|
||||
- **UI Scrollbar Optimization:** Eliminated redundant vertical scrollbar on the right side of the screen while preserving necessary internal page scrolling. Implemented global CSS rules with `overflow: hidden` on html/body/root and Layout height management to prevent vertical overflow issues in production (August 2025).
|
||||
- **Task Management:** Système complet de gestion des tâches avec fonctionnalités temporelles avancées - tri intelligent (pending > priorité > date), dates d'échéance avec sélecteur calendrier, indicateurs visuels pour tâches en retard/urgentes, filtres par échéance (aujourd'hui, cette semaine, en retard, sans échéance), vue Kanban et liste, badges colorés pour statut temporel. Interface optimisée pour suivi prioritaire des tâches non validées. Correction critique de l'isolation des données : chaque magasin ne voit maintenant que ses propres tâches (Août 2025). **PRODUCTION FIX (Août 2025)** : Résolution du problème de rechargements multiples nécessaires pour l'administrateur - le cache des tâches s'invalide maintenant automatiquement lors du changement de magasin sélectionné.
|
||||
- **Information System (Announcements):** Système d'informations complet remplaçant la carte commandes clients dans le dashboard - gestion d'annonces avec niveaux de priorité (normal, important, urgent), contrôle d'accès basé sur les rôles (admin : CRUD complet, autres rôles : lecture seule), tri intelligent par priorité et date, filtrage par magasin pour administrateurs, interface moderne avec badges colorés et indicateurs visuels. **PRODUCTION FIX MAJEUR (Août 2025)** : Correction critique du problème d'affichage en production PostgreSQL - fix du filtrage pour inclure annonces globales + magasin sélectionné, correction des relations d'auteur avec username au lieu d'ID, design adapté pour correspondre exactement aux autres cartes du dashboard. **SUPPRESSION FIX (Août 2025)** : Correction du problème de suppression en production - ajout de la logique PostgreSQL manquante dans la route DELETE, les annonces peuvent maintenant être supprimées correctement en production.
|
||||
- **Information System (Announcements):** Système d'informations complet remplaçant la carte commandes clients dans le dashboard - gestion d'annonces avec niveaux de priorité (normal, important, urgent), contrôle d'accès basé sur les rôles (admin : CRUD complet, autres rôles : lecture seule), tri intelligent par priorité et date, filtrage par magasin pour administrateurs, interface moderne avec badges colorés et indicateurs visuels. **PRODUCTION FIX MAJEUR (Août 2025)** : Correction critique du problème d'affichage en production PostgreSQL - fix du filtrage pour inclure annonces globales + magasin sélectionné, correction des relations d'auteur avec username au lieu d'ID, design adapté pour correspondre exactement aux autres cartes du dashboard. **SUPPRESSION FIX (Août 2025)** : Correction du problème de suppression en production - ajout de la logique PostgreSQL manquante dans la route DELETE, les annonces peuvent maintenant être supprimées correctement en production. **MODAL DASHBOARD (Août 2025)** : Ajout d'un modal automatique dans le dashboard qui affiche la dernière annonce créée il y a moins de 2 jours - système de mémorisation localStorage pour éviter les re-affichages, interface avec priorités colorées, formatage français des dates, filtrage par magasin pour admins.
|
||||
- **DLC Module Permissions:** Correction des permissions de validation DLC pour permettre aux admin, directeur et manager de valider les produits DLC. Ajout de logs de debug pour traçabilité des validations (Août 2025).
|
||||
- **DLC UX Enhancement:** Ajout de tooltips explicatifs sur les icônes stock épuisé (PackageX) et restauration (RotateCcw) pour clarifier la différence entre "périmé" et "stock épuisé" et améliorer l'expérience utilisateur (Août 2025).
|
||||
- **User and Permission Management:** Granular access controls ensuring that users (e.g., employees) have appropriate permissions for tasks like creating customer orders.
|
||||
|
||||
Reference in new issue
Block a user