diff --git a/attached_assets/image_1754940238213.png b/attached_assets/image_1754940238213.png new file mode 100644 index 0000000..21d25ff Binary files /dev/null and b/attached_assets/image_1754940238213.png differ diff --git a/attached_assets/image_1754940275041.png b/attached_assets/image_1754940275041.png new file mode 100644 index 0000000..3cfcb2d Binary files /dev/null and b/attached_assets/image_1754940275041.png differ diff --git a/client/src/components/RouterProduction.tsx b/client/src/components/RouterProduction.tsx index 2ac71bf..11a4395 100644 --- a/client/src/components/RouterProduction.tsx +++ b/client/src/components/RouterProduction.tsx @@ -14,7 +14,7 @@ import Publicities from "@/pages/Publicities"; import NocoDBConfig from "@/pages/NocoDBConfig"; import CustomerOrders from "@/pages/CustomerOrders"; -import RoleManagement from "@/pages/RoleManagement"; + import DlcPage from "@/pages/DlcPage"; import Tasks from "@/pages/Tasks"; import TasksSimplified from "@/pages/TasksSimplified"; @@ -70,8 +70,7 @@ function RouterProduction() { - - + diff --git a/client/src/pages/RoleManagement.tsx b/client/src/pages/RoleManagement.tsx deleted file mode 100644 index 2e5c4df..0000000 --- a/client/src/pages/RoleManagement.tsx +++ /dev/null @@ -1,741 +0,0 @@ -import { useState, useEffect } from "react"; -import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import { apiRequest } from "@/lib/queryClient"; -import { getRoleColor, getRoleDisplayName, ROLE_COLORS } from "@/lib/roleUtils"; -import { useToast } from "@/hooks/use-toast"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { Badge } from "@/components/ui/badge"; -import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; -import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Checkbox } from "@/components/ui/checkbox"; -import { Plus, Edit, Trash2, Shield, Users, Settings } from "lucide-react"; -import type { Role, Permission, User } from "@shared/schema"; - -// Fonction pour forcer l'affichage des noms français même si displayName est incorrect -function getPermissionDisplayName(permission: any): string { - // Si displayName existe et n'est pas le même que name, l'utiliser - if (permission.displayName && permission.displayName !== permission.name) { - return permission.displayName; - } - - // Sinon, mapper manuellement selon le nom technique - const frenchNames: Record = { - 'dashboard_read': 'Voir tableau de bord', - 'groups_read': 'Voir magasins', - 'groups_create': 'Créer magasins', - 'groups_update': 'Modifier magasins', - 'groups_delete': 'Supprimer magasins', - 'suppliers_read': 'Voir fournisseurs', - 'suppliers_create': 'Créer fournisseurs', - 'suppliers_update': 'Modifier fournisseurs', - 'suppliers_delete': 'Supprimer fournisseurs', - 'orders_read': 'Voir commandes', - 'orders_create': 'Créer commandes', - 'orders_update': 'Modifier commandes', - 'orders_delete': 'Supprimer commandes', - 'deliveries_read': 'Voir livraisons', - 'deliveries_create': 'Créer livraisons', - 'deliveries_update': 'Modifier livraisons', - 'deliveries_delete': 'Supprimer livraisons', - 'deliveries_validate': 'Valider livraisons', - 'deliveries_reconcile': 'Rapprocher livraisons', - 'publicities_read': 'Voir publicités', - 'publicities_create': 'Créer publicités', - 'publicities_update': 'Modifier publicités', - 'publicities_delete': 'Supprimer publicités', - 'customer_orders_read': 'Voir commandes clients', - 'customer_orders_create': 'Créer commandes clients', - 'customer_orders_update': 'Modifier commandes clients', - 'customer_orders_delete': 'Supprimer commandes clients', - 'customer_orders_print': 'Imprimer commandes clients', - 'customer_orders_export': 'Exporter commandes clients', - 'users_read': 'Voir utilisateurs', - 'users_create': 'Créer utilisateurs', - 'users_update': 'Modifier utilisateurs', - 'users_delete': 'Supprimer utilisateurs', - 'roles_read': 'Voir rôles', - 'roles_create': 'Créer rôles', - 'roles_update': 'Modifier rôles', - 'roles_delete': 'Supprimer rôles', - 'permissions_assign': 'Assigner permissions', - 'calendar_read': 'Voir calendrier', - 'calendar_create': 'Créer événement calendrier', - 'calendar_update': 'Modifier événement calendrier', - 'calendar_delete': 'Supprimer événement calendrier', - 'reconciliation_read': 'Voir rapprochement', - 'reconciliation_update': 'Modifier rapprochement', - 'tasks_read': 'Voir tâches', - 'tasks_create': 'Créer tâches', - 'tasks_update': 'Modifier tâches', - 'tasks_delete': 'Supprimer tâches', - 'tasks_assign': 'Assigner tâches', - 'dlc_read': 'Voir DLC', - 'dlc_create': 'Créer DLC', - 'dlc_update': 'Modifier DLC', - 'dlc_delete': 'Supprimer DLC', - 'dlc_validate': 'Valider DLC', - 'dlc_print': 'Imprimer DLC', - 'dlc_stats': 'Statistiques DLC', - 'nocodb_config': 'Configuration NocoDB', - 'magasins_read': 'Voir magasins', - 'magasins_create': 'Créer magasins', - 'magasins_update': 'Modifier magasins', - 'magasins_delete': 'Supprimer magasins' - }; - - return frenchNames[permission.name] || permission.displayName || permission.name; -} - -interface RoleWithPermissions extends Role { - rolePermissions: Array<{ - roleId: number; - permissionId: number; - createdAt: Date; - permission: Permission; - }>; -} - -interface UserWithRoles extends User { - userRoles: Array<{ - userId: string; - roleId: number; - assignedBy: string; - assignedAt: Date; - role: Role; - }>; -} - -export default function RoleManagement() { - const [selectedRole, setSelectedRole] = useState(null); - const [createRoleOpen, setCreateRoleOpen] = useState(false); - const [editRoleOpen, setEditRoleOpen] = useState(false); - - const { toast } = useToast(); - const queryClient = useQueryClient(); - - // Fetch roles - const { data: rolesData = [], isLoading: rolesLoading, error: rolesError, refetch: refetchRoles } = useQuery({ - queryKey: ['/api/roles'], - staleTime: 0, - gcTime: 0, - refetchOnMount: true, - refetchOnWindowFocus: true, - retry: false - }); - - // Fetch permissions with forced refetch - const { data: permissionsData = [], isLoading: permissionsLoading, refetch: refetchPermissions, error: permissionsError } = useQuery({ - queryKey: ['/api/permissions'], - queryFn: async () => { - const response = await fetch('/api/permissions', { - credentials: 'include' - }); - if (!response.ok) { - throw new Error(`Failed to fetch permissions: ${response.status}`); - } - return response.json(); - }, - staleTime: 0, - gcTime: 0, - refetchOnMount: true, - refetchOnWindowFocus: true, - retry: false - }); - - // Force refresh permissions on mount - useEffect(() => { - queryClient.removeQueries({ queryKey: ['/api/permissions'] }); - refetchPermissions(); - }, [refetchPermissions, queryClient]); - - // Force refresh permissions function - const forceRefreshPermissions = () => { - queryClient.removeQueries({ queryKey: ['/api/permissions'] }); - queryClient.invalidateQueries({ queryKey: ['/api/permissions'] }); - queryClient.resetQueries({ queryKey: ['/api/permissions'] }); - refetchPermissions(); - }; - - // Debug function removed after successful DLC permissions fix - - - - // Protection Array.isArray et logs debug RENFORCÉS - const roles = Array.isArray(rolesData) ? rolesData : []; - const permissions = Array.isArray(permissionsData) ? permissionsData : []; - - // Auto-select first role if none selected and roles are available - useEffect(() => { - if (!selectedRole && roles.length > 0) { - setSelectedRole(roles[0] as RoleWithPermissions); - } - }, [roles, selectedRole]); - - - - // Check for task permissions availability - const taskPermissions = permissions.filter(p => p.category === 'gestion_taches'); - - // Get role with permissions - const { data: roleWithPermissions } = useQuery({ - queryKey: ['/api/roles', selectedRole?.id], - enabled: !!selectedRole, - }); - - // Get role permissions specifically - const { data: rolePermissions = [] } = useQuery({ - queryKey: [`/api/roles/${selectedRole?.id}/permissions`], - queryFn: async () => { - const response = await fetch(`/api/roles/${selectedRole?.id}/permissions`, { - credentials: 'include' - }); - if (!response.ok) { - throw new Error(`Failed to fetch role permissions: ${response.status}`); - } - return response.json(); - }, - enabled: !!selectedRole, - }); - - // Task permissions check for role - const taskPermissionsInRole = rolePermissions?.filter(rp => { - const permission = permissions.find(p => p.id === rp.permissionId); - return permission?.category === 'gestion_taches'; - }); - - // Traduction des catégories en français - const categoryTranslations: Record = { - 'administration': 'Administration', - 'calendrier': 'Calendrier', - 'commandes': 'Commandes', - 'commandes_clients': 'Commandes Clients', - 'fournisseurs': 'Fournisseurs', - 'gestion_dlc': 'Gestion DLC', - 'gestion_roles': 'Gestion des Rôles', - 'gestion_taches': 'Gestion des Tâches', - 'livraisons': 'Livraisons', - 'magasins': 'Magasins', - 'publicites': 'Publicités', - 'rapprochement': 'Rapprochement', - 'tableau_de_bord': 'Tableau de Bord', - 'utilisateurs': 'Utilisateurs', - // Fallbacks pour les anciennes catégories techniques - 'customer_orders': 'Commandes Clients', - 'groups': 'Magasins', - 'roles': 'Gestion des Rôles', - // Fallbacks pour les anciennes catégories en anglais - 'Calendar': 'Calendrier', - 'Dashboard': 'Tableau de Bord', - 'Deliveries': 'Livraisons', - 'Orders': 'Commandes', - 'Publicities': 'Publicités', - 'Reconciliation': 'Rapprochement', - 'Suppliers': 'Fournisseurs', - 'Customer Orders': 'Commandes Clients', - 'Users': 'Utilisateurs', - 'Roles': 'Gestion des Rôles', - 'Administration': 'Administration' - }; - - // Group permissions by category - const permissionsByCategory = Array.isArray(permissions) ? permissions.reduce((acc, permission) => { - if (!acc[permission.category]) { - acc[permission.category] = []; - } - acc[permission.category].push(permission); - return acc; - }, {} as Record) : {}; - - // Check permission categories availability - const hasTasksCategory = !!permissionsByCategory['gestion_taches']; - const hasAdminCategory = !!permissionsByCategory['administration']; - - // Debug permissions (cleaned up after successful DLC fix) - - // Create role mutation - const createRoleMutation = useMutation({ - mutationFn: async (data: { name: string; displayName: string; description: string; color: string }) => { - return await apiRequest('/api/roles', 'POST', data); - }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['/api/roles'] }); - setCreateRoleOpen(false); - toast({ title: "Rôle créé avec succès" }); - }, - onError: (error) => { - toast({ title: "Erreur lors de la création du rôle", description: error.message, variant: "destructive" }); - }, - }); - - // Update role mutation - const updateRoleMutation = useMutation({ - mutationFn: async (data: { id: number; name: string; displayName: string; description: string; color: string }) => { - return await apiRequest(`/api/roles/${data.id}`, 'PUT', data); - }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['/api/roles'] }); - setEditRoleOpen(false); - toast({ title: "Rôle modifié avec succès" }); - }, - onError: (error) => { - toast({ title: "Erreur lors de la modification du rôle", description: error.message, variant: "destructive" }); - }, - }); - - // Delete role mutation - const deleteRoleMutation = useMutation({ - mutationFn: async (roleId: number) => { - return await apiRequest(`/api/roles/${roleId}`, 'DELETE'); - }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['/api/roles'] }); - toast({ title: "Rôle supprimé avec succès" }); - }, - onError: (error) => { - toast({ title: "Erreur lors de la suppression du rôle", description: error.message, variant: "destructive" }); - }, - }); - - // Update role permissions mutation - const updateRolePermissionsMutation = useMutation({ - mutationFn: async (data: { roleId: number; permissionIds: number[] }) => { - return await apiRequest(`/api/roles/${data.roleId}/permissions`, 'POST', { permissionIds: data.permissionIds }); - }, - onSuccess: () => { - console.log("🚀 Permission mutation success - invalidating cache"); - - // Invalider seulement les queries nécessaires - if (selectedRole) { - queryClient.invalidateQueries({ queryKey: [`/api/roles/${selectedRole.id}/permissions`] }); - } - queryClient.invalidateQueries({ queryKey: ['/api/permissions'] }); - - toast({ title: "Permissions mises à jour avec succès" }); - }, - onError: (error) => { - toast({ title: "Erreur lors de la mise à jour des permissions", description: error.message, variant: "destructive" }); - }, - }); - - // Fix admin permissions mutation - const fixAdminPermissionsMutation = useMutation({ - mutationFn: async () => { - return await apiRequest('/api/admin/fix-permissions', 'POST'); - }, - onSuccess: (data) => { - // Rafraîchir toutes les données - queryClient.invalidateQueries({ queryKey: ['/api/roles'] }); - queryClient.invalidateQueries({ queryKey: ['/api/permissions'] }); - if (selectedRole) { - queryClient.invalidateQueries({ queryKey: [`/api/roles/${selectedRole.id}/permissions`] }); - } - - toast({ - title: "Permissions administrateur corrigées", - description: `${data.added} permissions ajoutées. Total: ${data.after}/${data.total}` - }); - }, - onError: (error) => { - toast({ - title: "Erreur lors de la correction", - description: error.message, - variant: "destructive" - }); - }, - }); - - - - const handleCreateRole = (event: React.FormEvent) => { - event.preventDefault(); - const formData = new FormData(event.currentTarget); - createRoleMutation.mutate({ - name: formData.get('name') as string, - displayName: formData.get('displayName') as string, - description: formData.get('description') as string, - color: formData.get('color') as string, - }); - }; - - const handleUpdateRole = (event: React.FormEvent) => { - event.preventDefault(); - if (!selectedRole) return; - - const formData = new FormData(event.currentTarget); - updateRoleMutation.mutate({ - id: selectedRole.id, - name: formData.get('name') as string, - displayName: formData.get('displayName') as string, - description: formData.get('description') as string, - color: formData.get('color') as string, - }); - }; - - const handlePermissionToggle = (permissionId: number, checked: boolean) => { - if (!selectedRole) return; - - const currentPermissions = rolePermissions?.map(rp => rp.permissionId) || []; - const newPermissions = checked - ? [...currentPermissions, permissionId] - : currentPermissions.filter(id => id !== permissionId); - - console.log("🔍 Permission toggle:", { - permissionId, - checked, - currentPermissions, - newPermissions, - roleId: selectedRole.id - }); - - updateRolePermissionsMutation.mutate({ - roleId: selectedRole.id, - permissionIds: newPermissions, - }); - }; - - - - return ( -
-
-
-

Gestion des Rôles et Permissions

-

Gérez les rôles utilisateurs et leurs permissions

-
- - - - - - - Créer un Nouveau Rôle - Ajoutez un nouveau rôle au système - -
-
- - -
-
- - -
-
- - -
-
- - -
- -
-
-
-
- - - - - - Rôles - - - - Permissions - - - - -
- {/* Roles List */} - - -
-
- Liste des Rôles - Sélectionnez un rôle pour voir ses permissions -
-
- - -
-
-
- - {rolesLoading ? ( -
Chargement des rôles...
- ) : rolesError ? ( -
- Erreur: {rolesError.message} -
- ) : roles.length === 0 ? ( -
- Aucun rôle trouvé -
- ) : ( -
- {roles.map((role) => { - // Utiliser la couleur de la base de données, avec fallback sur getRoleColor - const roleColor = role.color || getRoleColor(role.name); - return ( -
setSelectedRole(role)} - > -
-
-
- {role.displayName} - {role.isSystem && ( - Système - )} -
-
- - {!role.isSystem && ( - - )} -
-
-

{role.description}

-
- ); - })} -
- )} - - - - {/* Role Permissions */} - - - Permissions du Rôle - - {selectedRole ? `Permissions pour ${selectedRole.displayName}` : 'Sélectionnez un rôle'} - - - - {selectedRole ? ( -
- {Object.entries(permissionsByCategory) - .filter(([category, categoryPermissions]) => { - return Array.isArray(categoryPermissions) && categoryPermissions.length > 0; - }) - .map(([category, categoryPermissions]) => ( -
-

- {categoryTranslations[category] || category} - ({categoryPermissions.length}) -

-
- {categoryPermissions.map((permission) => { - const hasPermission = rolePermissions?.some( - rp => rp.permissionId === permission.id - ); - return ( -
- handlePermissionToggle(permission.id, checked as boolean)} - /> - - - {permission.action} - -
- ); - })} -
-
- ) - )} -
- ) : ( -

Sélectionnez un rôle pour voir ses permissions

- )} -
-
-
- - - - - - - -
-
- Liste des Permissions - Toutes les permissions disponibles dans le système -
-
- -
-
-
- -
- {Object.entries(permissionsByCategory) - .filter(([category, categoryPermissions]) => { - return Array.isArray(categoryPermissions) && categoryPermissions.length > 0; - }) - .map(([category, categoryPermissions]) => ( -
-

- {categoryTranslations[category] || category} - - ({categoryPermissions.length}) - -

-
- {categoryPermissions.map((permission) => ( -
-
-

{getPermissionDisplayName(permission)}

-

{permission.description}

-
-
- - {permission.action} - - {permission.isSystem && ( - - Système - - )} -
-
- ))} -
-
- ))} -
-
-
-
- - - {/* Edit Role Dialog */} - - - - Modifier le Rôle - Modifiez les informations du rôle - - {selectedRole && ( -
-
- - -
-
- - -
-
- - -
-
- - -
- -
- )} -
-
- - -
- ); -} \ No newline at end of file diff --git a/client/src/pages/Users.tsx b/client/src/pages/Users.tsx index 698e3bf..e79d3ae 100644 --- a/client/src/pages/Users.tsx +++ b/client/src/pages/Users.tsx @@ -51,7 +51,7 @@ export default function UsersPage() { lastName: "", email: "", password: "", - role: "employee" as const, + role: "employee" as "admin" | "manager" | "employee" | "directeur", }); // Form states for creating user @@ -61,7 +61,7 @@ export default function UsersPage() { lastName: "", username: "", password: "", - role: "employee" as const, + role: "employee" as "admin" | "manager" | "employee" | "directeur", }); const USE_LOCAL_AUTH = import.meta.env.VITE_USE_LOCAL_AUTH === 'true' || import.meta.env.MODE === 'development'; @@ -76,10 +76,7 @@ export default function UsersPage() { enabled: user?.role === 'admin', }); - const { data: roles = [] } = useQuery({ - queryKey: ['/api/roles'], - enabled: user?.role === 'admin', - }); + // Protection React Error #310 - Vérification Array pour users et groups console.log('👥 Users page data:', { @@ -90,28 +87,13 @@ export default function UsersPage() { groupsType: typeof groups }); - // 🔍 Debug des couleurs de rôles - if (Array.isArray(users) && users.length > 0) { - console.log('🎨 Users roles colors debug:', users.map(u => ({ - username: u.username, - oldRole: u.role, - userRoles: u.userRoles?.map(ur => ({ - roleId: ur.roleId, - roleName: ur.role?.name, - roleColor: ur.role?.color - })) || 'NO_USER_ROLES', - roles: u.roles?.map(r => ({ name: r.name, color: r.color })) || 'NO_ROLES' - }))); - } - - // Protection: s'assurer que users, groups et roles sont des arrays + // Protection: s'assurer que users et groups sont des arrays const safeUsers = Array.isArray(users) ? users : []; const safeGroups = Array.isArray(groups) ? groups : []; - const safeRoles = Array.isArray(roles) ? roles : []; const updateUserRoleMutation = useMutation({ - mutationFn: async (data: { userId: string; roleId: number }) => { - await apiRequest(`/api/users/${data.userId}/roles`, "POST", { roleIds: [data.roleId] }); + mutationFn: async (data: { userId: string; role: string }) => { + await apiRequest(`/api/users/${data.userId}`, "PUT", { role: data.role }); }, onSuccess: () => { toast({ @@ -119,7 +101,7 @@ export default function UsersPage() { description: "Rôle utilisateur mis à jour avec succès", }); queryClient.invalidateQueries({ queryKey: ['/api/users'] }); - queryClient.invalidateQueries({ queryKey: ['/api/roles'] }); + }, onError: (error) => { if (isUnauthorizedError(error)) { @@ -146,7 +128,7 @@ export default function UsersPage() { console.log('🔄 Frontend updateUser mutation:', data); // Nettoyer les données côté frontend avant envoi - const cleanedUpdates = {}; + const cleanedUpdates: any = {}; for (const [key, value] of Object.entries(data.updates)) { // For name fields (firstName, lastName, email), allow empty strings to clear the fields if (key === 'firstName' || key === 'lastName' || key === 'email') { @@ -270,7 +252,7 @@ export default function UsersPage() { }); // Invalidation complète du cache queryClient.invalidateQueries({ queryKey: ['/api/users'] }); - queryClient.invalidateQueries({ queryKey: ['/api/roles'] }); + queryClient.invalidateQueries({ queryKey: ['/api/groups'] }); }, onError: (error: any) => { @@ -315,7 +297,7 @@ export default function UsersPage() { }); // Invalidation complète du cache queryClient.invalidateQueries({ queryKey: ['/api/users'] }); - queryClient.invalidateQueries({ queryKey: ['/api/roles'] }); + queryClient.invalidateQueries({ queryKey: ['/api/groups'] }); }, onError: (error: any) => { @@ -527,22 +509,11 @@ export default function UsersPage() { return; } - // Trouver l'ID du rôle basé sur le nom - const role = safeRoles.find(r => r.name === newRoleName); - if (!role) { - toast({ - title: "Erreur", - description: "Rôle non trouvé", - variant: "destructive", - }); - return; - } - // Préparer la confirmation et ouvrir le modal setPendingRoleChange({ userId, newRoleName, - newRoleDisplay: role.displayName || role.name + newRoleDisplay: newRoleName }); setShowRoleConfirmModal(true); } @@ -550,10 +521,10 @@ export default function UsersPage() { const confirmRoleChange = () => { if (!pendingRoleChange) return; - const role = safeRoles.find(r => r.name === pendingRoleChange.newRoleName); - if (role) { - updateUserRoleMutation.mutate({ userId: pendingRoleChange.userId, roleId: role.id }); - } + updateUserRoleMutation.mutate({ + userId: pendingRoleChange.userId, + role: pendingRoleChange.newRoleName + }); setShowRoleConfirmModal(false); setPendingRoleChange(null); @@ -748,50 +719,8 @@ export default function UsersPage() {
- {/* 🔧 SYSTÈME DE RÔLES CORRIGÉ - Utilise les vraies couleurs de la DB */} - {Array.isArray(userData.userRoles) && userData.userRoles.length > 0 ? ( - userData.userRoles.map((userRole, index) => ( -
-
- - {userRole.role?.displayName || userRole.role?.name || 'Rôle inconnu'} - -
- )) - ) : Array.isArray(userData.roles) && userData.roles.length > 0 ? ( - userData.roles.map((role, index) => ( -
-
- - {role.displayName || role.name} - -
- )) - ) : ( - // Fallback final si aucune donnée de rôle n'est disponible -
- {getRoleIcon(userData.role)} - {getRoleBadge(userData.role)} -
- )} + {getRoleIcon(userData.role)} + {getRoleBadge(userData.role)}
@@ -822,7 +751,7 @@ export default function UsersPage() { {/* Sélecteur de rôle rapide */} {userData.id !== user?.id && ( )}