Merge pull request #507 from R0m1k3/1.1

1.1
This commit is contained in:
LogiFlow authored and GitHub committed 2025-11-04 14:18:56 +01:00
commit 35a346f1ca
3 files changed
+196 -144

No files matched your search

+183 -141
View File
@@ -228,90 +228,110 @@ export default function Sidebar() {
return false; return false;
}; };
const menuItems = [ const menuSections = [
{ {
path: "/dashboard", title: "GESTION PRINCIPALE",
label: "Tableau de bord", items: [
icon: BarChart3, {
roles: ["admin", "directeur", "manager", "employee"] path: "/dashboard",
label: "Tableau de bord",
icon: BarChart3,
roles: ["admin", "directeur", "manager", "employee"]
},
{
path: "/calendar",
label: "Calendrier",
icon: Calendar,
roles: ["admin", "directeur", "manager", "employee"]
},
]
}, },
{ {
path: "/calendar", title: "LOGISTIQUE",
label: "Calendrier", items: [
icon: Calendar, {
roles: ["admin", "directeur", "manager", "employee"] path: "/orders",
label: "Commandes",
icon: Package,
roles: ["admin", "directeur", "manager"]
},
{
path: "/deliveries",
label: "Livraisons",
icon: Truck,
roles: ["admin", "directeur", "manager"]
},
{
path: "/bl-reconciliation",
label: "Rapprochement",
icon: FileText,
roles: ["admin", "directeur"]
},
{
path: "/payment-schedule",
label: "Échéance",
icon: CreditCard,
roles: ["admin", "directeur"]
},
{
path: "/avoirs",
label: "Avoirs",
icon: Receipt,
roles: ["admin", "directeur", "manager"]
},
]
}, },
{ {
path: "/orders", title: "ANALYSES",
label: "Commandes", items: [
icon: Package, {
roles: ["admin", "directeur", "manager"] path: "/analytics",
label: "Statistiques",
icon: BarChart3,
roles: ["admin", "directeur", "manager"]
},
{
path: "/sales-analysis",
label: "Analyse Vente",
icon: TrendingUp,
roles: ["admin", "directeur"]
},
]
}, },
{ {
path: "/deliveries", title: "OPÉRATIONS",
label: "Livraisons", items: [
icon: Truck, {
roles: ["admin", "directeur", "manager"] path: "/publicities",
}, label: "Publicités",
{ icon: Megaphone,
path: "/bl-reconciliation", roles: ["admin", "directeur", "manager", "employee"]
label: "Rapprochement", },
icon: FileText, {
roles: ["admin", "directeur"] path: "/customer-orders",
}, label: "Commandes Client",
{ icon: ShoppingCart,
path: "/analytics", roles: ["admin", "directeur", "manager", "employee"]
label: "Statistiques", },
icon: BarChart3, {
roles: ["admin", "directeur", "manager"] path: "/dlc",
}, label: "Gestion DLC",
{ icon: Clock,
path: "/sales-analysis", roles: ["admin", "directeur", "manager", "employee"]
label: "Analyse Vente", },
icon: TrendingUp, {
roles: ["admin", "directeur"] path: "/tasks",
}, label: "Tâches",
{ icon: ListTodo,
path: "/payment-schedule", roles: ["admin", "directeur", "manager", "employee"]
label: "Échéance", },
icon: CreditCard, {
roles: ["admin", "directeur"] path: "/sav",
}, label: "SAV",
{ icon: Wrench,
path: "/avoirs", roles: ["admin", "directeur", "manager", "employee"]
label: "Avoirs", },
icon: Receipt, ]
roles: ["admin", "directeur", "manager"]
},
{
path: "/publicities",
label: "Publicités",
icon: Megaphone,
roles: ["admin", "directeur", "manager", "employee"]
},
{
path: "/customer-orders",
label: "Commandes Client",
icon: ShoppingCart,
roles: ["admin", "directeur", "manager", "employee"]
},
{
path: "/dlc",
label: "Gestion DLC",
icon: Clock,
roles: ["admin", "directeur", "manager", "employee"]
},
{
path: "/tasks",
label: "Tâches",
icon: ListTodo,
roles: ["admin", "directeur", "manager", "employee"]
},
{
path: "/sav",
label: "SAV",
icon: Wrench,
roles: ["admin", "directeur", "manager", "employee"]
}, },
]; ];
@@ -558,70 +578,90 @@ export default function Sidebar() {
</div> </div>
{/* Navigation */} {/* Navigation */}
<nav className="flex-1 py-4 px-3"> <nav className="flex-1 py-5 px-3 overflow-y-auto">
<div className="space-y-1"> <div className="space-y-7">
{menuItems.map((item) => { {menuSections.map((section, sectionIndex) => {
const hasRolePermission = hasPermission(item.roles); // Vérifier si au moins un item de la section est accessible
// console.log(`🔍 Menu item ${item.path} (${item.label}):`, { const hasAccessibleItems = section.items.some(item => hasPermission(item.roles));
// roles: item.roles, if (!hasAccessibleItems) return null;
// hasPermission: hasRolePermission,
// userRole: user?.role
// });
if (!hasRolePermission) return null;
const Icon = item.icon;
const active = isActive(item.path);
return ( return (
<Link key={item.path} href={item.path}> <div key={section.title} className="space-y-3">
<div {/* Titre de section - caché en mode collapsed */}
className={`flex items-center transition-colors hover:bg-gray-100 ${ {!sidebarCollapsed && (
// Espacement adaptatif selon l'écran et l'état <h3 className="px-3 mb-3 text-[11px] font-bold text-gray-500 uppercase tracking-widest">
sidebarCollapsed && !isMobile {section.title}
? 'px-3 py-3 justify-center min-h-[44px]' </h3>
: isMobileOrTablet )}
? 'px-4 py-3 min-h-[48px]'
: 'px-3 py-2 min-h-[40px]' {/* Séparateur visuel en mode collapsed */}
} text-sm font-medium ${ {sidebarCollapsed && sectionIndex > 0 && (
active <div className="border-t border-gray-200 my-4"></div>
? 'bg-gray-100 text-gray-900 border-r-2 border-blue-600' )}
: 'text-gray-700'
}`} {/* Items du menu */}
title={sidebarCollapsed && !isMobile ? item.label : undefined} <div className="space-y-1.5">
onClick={handleMenuClick} {section.items.map((item) => {
> const hasRolePermission = hasPermission(item.roles);
<Icon className={`${
// Tailles d'icônes adaptatives if (!hasRolePermission) return null;
isMobile ? 'h-5 w-5' :
isTablet ? 'h-6 w-6' : const Icon = item.icon;
'h-5 w-5' const active = isActive(item.path);
} ${sidebarCollapsed && !isMobile ? '' : 'mr-3'}`} />
{(!sidebarCollapsed || isMobile) && ( return (
<span className={`${isMobileOrTablet ? 'text-base' : 'text-sm'} font-medium`}> <Link key={item.path} href={item.path}>
{item.label} <div
</span> className={`flex items-center transition-colors hover:bg-gray-100 rounded-md ${
)} // Espacement adaptatif selon l'écran et l'état
sidebarCollapsed && !isMobile
? 'px-3 py-3 justify-center min-h-11'
: isMobileOrTablet
? 'px-4 py-3.5 min-h-12'
: 'px-3 py-3 min-h-11'
} ${
active
? 'bg-blue-50 text-blue-700 border-r-2 border-blue-600'
: 'text-gray-700'
}`}
title={sidebarCollapsed && !isMobile ? item.label : undefined}
onClick={handleMenuClick}
>
<Icon className={`${
// Tailles d'icônes adaptatives
isMobile ? 'h-5 w-5' :
isTablet ? 'h-6 w-6' :
'h-5 w-5'
} ${
active ? 'text-blue-700' : 'text-gray-600'
} ${sidebarCollapsed && !isMobile ? '' : 'mr-3'}`} />
{(!sidebarCollapsed || isMobile) && (
<span className={`${isMobileOrTablet ? 'text-base' : 'text-[15px]'} font-medium`}>
{item.label}
</span>
)}
</div>
</Link>
);
})}
</div> </div>
</Link> </div>
); );
})} })}
</div> </div>
</nav> </nav>
{/* Administration Section */} {/* Administration Section */}
{adminItems.some(item => hasPermission(item.roles)) && ( {adminItems.some(item => hasPermission(item.roles)) && (
<div className="border-t border-gray-200 py-4 px-3"> <div className="border-t border-gray-200 py-5 px-3">
{!sidebarCollapsed && ( {!sidebarCollapsed && (
<div className="mb-2"> <div className="mb-3">
<h3 className="px-3 text-xs font-medium text-gray-500 uppercase tracking-wider"> <h3 className="px-3 text-[11px] font-bold text-gray-500 uppercase tracking-widest">
Administration Administration
</h3> </h3>
</div> </div>
)} )}
<div className="space-y-1"> <div className="space-y-1.5">
{adminItems.map((item) => { {adminItems.map((item) => {
if (!hasPermission(item.roles)) return null; if (!hasPermission(item.roles)) return null;
@@ -632,13 +672,13 @@ export default function Sidebar() {
return ( return (
<div key={item.path}> <div key={item.path}>
<div <div
className={`flex items-center cursor-pointer transition-colors hover:bg-gray-100 text-gray-700 ${ className={`flex items-center cursor-pointer transition-colors hover:bg-gray-100 rounded-md ${
sidebarCollapsed && !isMobile sidebarCollapsed && !isMobile
? 'px-3 py-3 justify-center min-h-[44px]' ? 'px-3 py-3 justify-center min-h-11'
: isMobileOrTablet : isMobileOrTablet
? 'px-4 py-3 min-h-[48px]' ? 'px-4 py-3.5 min-h-12'
: 'px-3 py-2 min-h-[40px]' : 'px-3 py-3 min-h-11'
} text-sm font-medium`} } text-gray-700`}
title={sidebarCollapsed && !isMobile ? item.label : undefined} title={sidebarCollapsed && !isMobile ? item.label : undefined}
onClick={() => { onClick={() => {
setShowBapModal(true); setShowBapModal(true);
@@ -649,9 +689,9 @@ export default function Sidebar() {
isMobile ? 'h-5 w-5' : isMobile ? 'h-5 w-5' :
isTablet ? 'h-6 w-6' : isTablet ? 'h-6 w-6' :
'h-5 w-5' 'h-5 w-5'
} ${sidebarCollapsed && !isMobile ? '' : 'mr-3'}`} /> } text-gray-600 ${sidebarCollapsed && !isMobile ? '' : 'mr-3'}`} />
{(!sidebarCollapsed || isMobile) && ( {(!sidebarCollapsed || isMobile) && (
<span className={`${isMobileOrTablet ? 'text-base' : 'text-sm'} font-medium`}> <span className={`${isMobileOrTablet ? 'text-base' : 'text-[15px]'} font-medium`}>
{item.label} {item.label}
</span> </span>
)} )}
@@ -663,27 +703,29 @@ export default function Sidebar() {
return ( return (
<Link key={item.path} href={item.path}> <Link key={item.path} href={item.path}>
<div <div
className={`flex items-center transition-colors hover:bg-gray-100 ${ className={`flex items-center transition-colors hover:bg-gray-100 rounded-md ${
sidebarCollapsed && !isMobile sidebarCollapsed && !isMobile
? 'px-3 py-3 justify-center min-h-[44px]' ? 'px-3 py-3 justify-center min-h-11'
: isMobileOrTablet : isMobileOrTablet
? 'px-4 py-3 min-h-[48px]' ? 'px-4 py-3.5 min-h-12'
: 'px-3 py-2 min-h-[40px]' : 'px-3 py-3 min-h-11'
} text-sm font-medium ${ } ${
active active
? 'bg-gray-100 text-gray-900 border-r-2 border-blue-600' ? 'bg-blue-50 text-blue-700 border-r-2 border-blue-600'
: 'text-gray-700' : 'text-gray-700'
}`} }`}
title={sidebarCollapsed && !isMobile ? item.label : undefined} title={sidebarCollapsed && !isMobile ? item.label : undefined}
onClick={() => console.log(`Navigating to admin: ${item.path}`)} onClick={handleMenuClick}
> >
<Icon className={`${ <Icon className={`${
isMobile ? 'h-5 w-5' : isMobile ? 'h-5 w-5' :
isTablet ? 'h-6 w-6' : isTablet ? 'h-6 w-6' :
'h-5 w-5' 'h-5 w-5'
} ${
active ? 'text-blue-700' : 'text-gray-600'
} ${sidebarCollapsed && !isMobile ? '' : 'mr-3'}`} /> } ${sidebarCollapsed && !isMobile ? '' : 'mr-3'}`} />
{(!sidebarCollapsed || isMobile) && ( {(!sidebarCollapsed || isMobile) && (
<span className={`${isMobileOrTablet ? 'text-base' : 'text-sm'} font-medium`}> <span className={`${isMobileOrTablet ? 'text-base' : 'text-[15px]'} font-medium`}>
{item.label} {item.label}
</span> </span>
)} )}
+1 -1
View File
@@ -36,7 +36,7 @@ The platform is built with a clear separation of concerns, utilizing a modern fu
- **Reconciliation:** Functionality for reconciling Bills of Lading (BL) and invoices with immediate UI updates. - **Reconciliation:** Functionality for reconciling Bills of Lading (BL) and invoices with immediate UI updates.
- **Task Management:** A comprehensive task management system with advanced temporal features, intelligent sorting (pending > priority > date), due dates with calendar selector, visual indicators for overdue/urgent tasks, filters by due date, Kanban and list views, and colored badges for temporal status. Includes start dates with role-based visibility. **CRITICAL GROUP FILTERING FIX (August 2025)**: Fixed group selection persistence issues - all pages (Tasks, Orders, Deliveries, Calendar) now properly apply selectedStoreId filtering for ALL user roles, not just admin. Prevents mixed data display after page reload. - **Task Management:** A comprehensive task management system with advanced temporal features, intelligent sorting (pending > priority > date), due dates with calendar selector, visual indicators for overdue/urgent tasks, filters by due date, Kanban and list views, and colored badges for temporal status. Includes start dates with role-based visibility. **CRITICAL GROUP FILTERING FIX (August 2025)**: Fixed group selection persistence issues - all pages (Tasks, Orders, Deliveries, Calendar) now properly apply selectedStoreId filtering for ALL user roles, not just admin. Prevents mixed data display after page reload.
- **Information System (Announcements):** A complete announcement system with priority levels (normal, important, urgent), role-based access control (admin: full CRUD; others: read-only), intelligent sorting by priority and date, and store-based filtering for administrators. Includes an automatic dashboard modal for recent announcements. - **Information System (Announcements):** A complete announcement system with priority levels (normal, important, urgent), role-based access control (admin: full CRUD; others: read-only), intelligent sorting by priority and date, and store-based filtering for administrators. Includes an automatic dashboard modal for recent announcements.
- **DLC Module:** Permissions allow admin, director, and manager roles to validate DLC products. Includes explanatory tooltips for UX enhancement. - **DLC Module:** Permissions allow admin, director, and manager roles to validate DLC products. Includes explanatory tooltips for UX enhancement. **Modal d'alerte DLC (Novembre 2025):** Le modal d'alerte qui s'affiche automatiquement à la connexion ne montre désormais que les produits DLC non traités. Les produits marqués comme "traités jusqu'à expiration" (processedUntilExpiry = true) sont exclus de l'alerte pour les catégories "expirent bientôt" (15 jours) et "expirés", réduisant les notifications répétitives et améliorant l'expérience utilisateur.
- **Reporting:** Statistical reporting, including monthly summaries of deliveries. - **Reporting:** Statistical reporting, including monthly summaries of deliveries.
- **UI Harmonization:** All detail modals across modules (orders, deliveries, calendar) use a unified component for consistent information display. - **UI Harmonization:** All detail modals across modules (orders, deliveries, calendar) use a unified component for consistent information display.
- **Analyse Vente (Sales Analysis):** A dedicated module for admins and directors that displays a configurable external URL/IP in an iframe. Configuration is managed via a dedicated tab in the Utilities page, allowing administrators to set or update the sales analysis dashboard URL. The menu is visible only to admin and directeur roles, ensuring proper access control. Includes intelligent error handling with informative messages when the URL is not configured. Features a refresh button to force complete iframe reloads. - **Analyse Vente (Sales Analysis):** A dedicated module for admins and directors that displays a configurable external URL/IP in an iframe. Configuration is managed via a dedicated tab in the Utilities page, allowing administrators to set or update the sales analysis dashboard URL. The menu is visible only to admin and directeur roles, ensuring proper access control. Includes intelligent error handling with informative messages when the URL is not configured. Features a refresh button to force complete iframe reloads.
+12 -2
View File
@@ -1886,6 +1886,7 @@ export class DatabaseStorage implements IStorage {
if (filters.status === 'expires_soon') { if (filters.status === 'expires_soon') {
// Products expiring within 15 days but not expired yet // Products expiring within 15 days but not expired yet
// Ne pas afficher les produits déjà traités (processedUntilExpiry = true)
const in15Days = new Date(); const in15Days = new Date();
in15Days.setDate(today.getDate() + 15); in15Days.setDate(today.getDate() + 15);
in15Days.setHours(23, 59, 59, 999); // End of day in15Days.setHours(23, 59, 59, 999); // End of day
@@ -1893,15 +1894,24 @@ export class DatabaseStorage implements IStorage {
and( and(
gt(dlcProducts.expiryDate, today), gt(dlcProducts.expiryDate, today),
lte(dlcProducts.expiryDate, in15Days), lte(dlcProducts.expiryDate, in15Days),
ne(dlcProducts.status, 'valides') ne(dlcProducts.status, 'valides'),
or(
isNull(dlcProducts.processedUntilExpiry),
eq(dlcProducts.processedUntilExpiry, false)
)
) )
); );
} else if (filters.status === 'expires') { } else if (filters.status === 'expires') {
// Products already expired // Products already expired
// Ne pas afficher les produits déjà traités (processedUntilExpiry = true)
conditions.push( conditions.push(
and( and(
lte(dlcProducts.expiryDate, today), lte(dlcProducts.expiryDate, today),
ne(dlcProducts.status, 'valides') ne(dlcProducts.status, 'valides'),
or(
isNull(dlcProducts.processedUntilExpiry),
eq(dlcProducts.processedUntilExpiry, false)
)
) )
); );
} else if (filters.status === 'en_cours') { } else if (filters.status === 'en_cours') {