mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-24 07:28:19 +02:00
3 files changed
+104
-52
No files matched your search
@@ -228,7 +228,10 @@ export default function Sidebar() {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const menuItems = [
|
const menuSections = [
|
||||||
|
{
|
||||||
|
title: "GESTION PRINCIPALE",
|
||||||
|
items: [
|
||||||
{
|
{
|
||||||
path: "/dashboard",
|
path: "/dashboard",
|
||||||
label: "Tableau de bord",
|
label: "Tableau de bord",
|
||||||
@@ -241,6 +244,11 @@ export default function Sidebar() {
|
|||||||
icon: Calendar,
|
icon: Calendar,
|
||||||
roles: ["admin", "directeur", "manager", "employee"]
|
roles: ["admin", "directeur", "manager", "employee"]
|
||||||
},
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "LOGISTIQUE",
|
||||||
|
items: [
|
||||||
{
|
{
|
||||||
path: "/orders",
|
path: "/orders",
|
||||||
label: "Commandes",
|
label: "Commandes",
|
||||||
@@ -259,18 +267,6 @@ export default function Sidebar() {
|
|||||||
icon: FileText,
|
icon: FileText,
|
||||||
roles: ["admin", "directeur"]
|
roles: ["admin", "directeur"]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/analytics",
|
|
||||||
label: "Statistiques",
|
|
||||||
icon: BarChart3,
|
|
||||||
roles: ["admin", "directeur", "manager"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/sales-analysis",
|
|
||||||
label: "Analyse Vente",
|
|
||||||
icon: TrendingUp,
|
|
||||||
roles: ["admin", "directeur"]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/payment-schedule",
|
path: "/payment-schedule",
|
||||||
label: "Échéance",
|
label: "Échéance",
|
||||||
@@ -283,6 +279,28 @@ export default function Sidebar() {
|
|||||||
icon: Receipt,
|
icon: Receipt,
|
||||||
roles: ["admin", "directeur", "manager"]
|
roles: ["admin", "directeur", "manager"]
|
||||||
},
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "ANALYSES",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
path: "/analytics",
|
||||||
|
label: "Statistiques",
|
||||||
|
icon: BarChart3,
|
||||||
|
roles: ["admin", "directeur", "manager"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/sales-analysis",
|
||||||
|
label: "Analyse Vente",
|
||||||
|
icon: TrendingUp,
|
||||||
|
roles: ["admin", "directeur"]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "OPÉRATIONS",
|
||||||
|
items: [
|
||||||
{
|
{
|
||||||
path: "/publicities",
|
path: "/publicities",
|
||||||
label: "Publicités",
|
label: "Publicités",
|
||||||
@@ -313,6 +331,8 @@ export default function Sidebar() {
|
|||||||
icon: Wrench,
|
icon: Wrench,
|
||||||
roles: ["admin", "directeur", "manager", "employee"]
|
roles: ["admin", "directeur", "manager", "employee"]
|
||||||
},
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const adminItems = [
|
const adminItems = [
|
||||||
@@ -558,15 +578,31 @@ 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) => {
|
||||||
|
// Vérifier si au moins un item de la section est accessible
|
||||||
|
const hasAccessibleItems = section.items.some(item => hasPermission(item.roles));
|
||||||
|
if (!hasAccessibleItems) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={section.title} className="space-y-3">
|
||||||
|
{/* Titre de section - caché en mode collapsed */}
|
||||||
|
{!sidebarCollapsed && (
|
||||||
|
<h3 className="px-3 mb-3 text-[11px] font-bold text-gray-500 uppercase tracking-widest">
|
||||||
|
{section.title}
|
||||||
|
</h3>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Séparateur visuel en mode collapsed */}
|
||||||
|
{sidebarCollapsed && sectionIndex > 0 && (
|
||||||
|
<div className="border-t border-gray-200 my-4"></div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Items du menu */}
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
{section.items.map((item) => {
|
||||||
const hasRolePermission = hasPermission(item.roles);
|
const hasRolePermission = hasPermission(item.roles);
|
||||||
// console.log(`🔍 Menu item ${item.path} (${item.label}):`, {
|
|
||||||
// roles: item.roles,
|
|
||||||
// hasPermission: hasRolePermission,
|
|
||||||
// userRole: user?.role
|
|
||||||
// });
|
|
||||||
|
|
||||||
if (!hasRolePermission) return null;
|
if (!hasRolePermission) return null;
|
||||||
|
|
||||||
@@ -576,16 +612,16 @@ 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 ${
|
||||||
// Espacement adaptatif selon l'écran et l'état
|
// Espacement adaptatif selon l'écran et l'état
|
||||||
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}
|
||||||
@@ -596,9 +632,11 @@ 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'
|
||||||
|
} ${
|
||||||
|
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>
|
||||||
)}
|
)}
|
||||||
@@ -607,21 +645,23 @@ export default function Sidebar() {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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
@@ -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') {
|
||||||
|
|||||||
Reference in new issue
Block a user