mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-12 01:37:40 +02:00
Remove role management and update user role assignment functionality
The RoleManagement page and related routes have been removed. The Users page has been updated to remove role fetching and modify the user role update mutation to directly use the role name instead of a role ID. Type definitions for user roles have also been updated to reflect the change from role IDs to role names. Replit-Commit-Author: Agent Replit-Commit-Session-Id: a8a78c07-e900-425c-a577-5b4c5894379d Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/a8a78c07-e900-425c-a577-5b4c5894379d/reFhh9U
This commit is contained in:
1 parent
df479800e3
commit
37494e6570
5 files changed
+24
-844
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 227 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
@@ -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() {
|
||||
<Route path="/suppliers" component={Suppliers} />
|
||||
<Route path="/groups" component={Groups} />
|
||||
<Route path="/users" component={Users} />
|
||||
<Route path="/roles" component={RoleManagement} />
|
||||
<Route path="/role-management" component={RoleManagement} />
|
||||
|
||||
<Route path="/bl-reconciliation" component={BLReconciliation} />
|
||||
<Route path="/publicities" component={Publicities} />
|
||||
<Route path="/customer-orders" component={CustomerOrders} />
|
||||
|
||||
@@ -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<string, string> = {
|
||||
'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<RoleWithPermissions | null>(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<Role[]>({
|
||||
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<Permission[]>({
|
||||
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<RoleWithPermissions>({
|
||||
queryKey: ['/api/roles', selectedRole?.id],
|
||||
enabled: !!selectedRole,
|
||||
});
|
||||
|
||||
// Get role permissions specifically
|
||||
const { data: rolePermissions = [] } = useQuery<any[]>({
|
||||
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<string, string> = {
|
||||
'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<string, Permission[]>) : {};
|
||||
|
||||
// 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<HTMLFormElement>) => {
|
||||
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<HTMLFormElement>) => {
|
||||
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 (
|
||||
<div className="container mx-auto p-6 max-w-7xl">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Gestion des Rôles et Permissions</h1>
|
||||
<p className="text-muted-foreground">Gérez les rôles utilisateurs et leurs permissions</p>
|
||||
</div>
|
||||
<Dialog open={createRoleOpen} onOpenChange={setCreateRoleOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Créer un Rôle
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Créer un Nouveau Rôle</DialogTitle>
|
||||
<DialogDescription>Ajoutez un nouveau rôle au système</DialogDescription>
|
||||
</DialogHeader>
|
||||
<form onSubmit={handleCreateRole} className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="name">Nom technique</Label>
|
||||
<Input id="name" name="name" required />
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="displayName">Nom d'affichage</Label>
|
||||
<Input id="displayName" name="displayName" required />
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="description">Description</Label>
|
||||
<Input id="description" name="description" />
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="color">Couleur</Label>
|
||||
<Input id="color" name="color" type="color" defaultValue="#3B82F6" />
|
||||
</div>
|
||||
<Button type="submit" disabled={createRoleMutation.isPending}>
|
||||
{createRoleMutation.isPending ? 'Création...' : 'Créer'}
|
||||
</Button>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="roles" className="space-y-4">
|
||||
<TabsList>
|
||||
<TabsTrigger value="roles">
|
||||
<Shield className="w-4 h-4 mr-2" />
|
||||
Rôles
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="permissions">
|
||||
<Settings className="w-4 h-4 mr-2" />
|
||||
Permissions
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="roles" className="space-y-4">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Roles List */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle>Liste des Rôles</CardTitle>
|
||||
<CardDescription>Sélectionnez un rôle pour voir ses permissions</CardDescription>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => fixAdminPermissionsMutation.mutate()}
|
||||
disabled={fixAdminPermissionsMutation.isPending}
|
||||
className="text-xs"
|
||||
>
|
||||
{fixAdminPermissionsMutation.isPending ? 'Correction...' : '🔧 Corriger Admin'}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
console.log("🔄 Manual refetch triggered");
|
||||
queryClient.invalidateQueries({ queryKey: ['/api/roles'] });
|
||||
refetchRoles();
|
||||
}}
|
||||
>
|
||||
🔄 Actualiser
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{rolesLoading ? (
|
||||
<div className="text-center py-4">Chargement des rôles...</div>
|
||||
) : rolesError ? (
|
||||
<div className="text-center py-4 text-red-500">
|
||||
Erreur: {rolesError.message}
|
||||
</div>
|
||||
) : roles.length === 0 ? (
|
||||
<div className="text-center py-4 text-muted-foreground">
|
||||
Aucun rôle trouvé
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{roles.map((role) => {
|
||||
// Utiliser la couleur de la base de données, avec fallback sur getRoleColor
|
||||
const roleColor = role.color || getRoleColor(role.name);
|
||||
return (
|
||||
<div
|
||||
key={role.id}
|
||||
className={`p-3 border rounded-lg cursor-pointer transition-colors ${
|
||||
selectedRole?.id === role.id ? 'border-primary bg-primary/5' : 'hover:bg-muted'
|
||||
}`}
|
||||
onClick={() => setSelectedRole(role)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className="w-4 h-4 rounded-full border"
|
||||
style={{
|
||||
backgroundColor: roleColor,
|
||||
borderColor: roleColor
|
||||
}}
|
||||
title={`Couleur: ${roleColor}`}
|
||||
/>
|
||||
<span className="font-medium">{role.displayName}</span>
|
||||
{role.isSystem && (
|
||||
<Badge variant="secondary" className="text-xs">Système</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedRole(role);
|
||||
setEditRoleOpen(true);
|
||||
}}
|
||||
>
|
||||
<Edit className="w-4 h-4" />
|
||||
</Button>
|
||||
{!role.isSystem && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
deleteRoleMutation.mutate(role.id);
|
||||
}}
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-1">{role.description}</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Role Permissions */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Permissions du Rôle</CardTitle>
|
||||
<CardDescription>
|
||||
{selectedRole ? `Permissions pour ${selectedRole.displayName}` : 'Sélectionnez un rôle'}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{selectedRole ? (
|
||||
<div className="space-y-4">
|
||||
{Object.entries(permissionsByCategory)
|
||||
.filter(([category, categoryPermissions]) => {
|
||||
return Array.isArray(categoryPermissions) && categoryPermissions.length > 0;
|
||||
})
|
||||
.map(([category, categoryPermissions]) => (
|
||||
<div key={category} className="space-y-2 border-l-2 border-blue-200 pl-3">
|
||||
<h4 className="font-medium text-sm text-blue-800">
|
||||
{categoryTranslations[category] || category}
|
||||
<span className="text-xs text-gray-400 ml-2 bg-gray-100 px-1 rounded">({categoryPermissions.length})</span>
|
||||
</h4>
|
||||
<div className="space-y-1">
|
||||
{categoryPermissions.map((permission) => {
|
||||
const hasPermission = rolePermissions?.some(
|
||||
rp => rp.permissionId === permission.id
|
||||
);
|
||||
return (
|
||||
<div key={permission.id} className="flex items-center space-x-2 bg-gray-50 p-1 rounded">
|
||||
<Checkbox
|
||||
id={`permission-${permission.id}`}
|
||||
checked={hasPermission || false}
|
||||
onCheckedChange={(checked) => handlePermissionToggle(permission.id, checked as boolean)}
|
||||
/>
|
||||
<label
|
||||
htmlFor={`permission-${permission.id}`}
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 flex-1"
|
||||
>
|
||||
{getPermissionDisplayName(permission)}
|
||||
</label>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{permission.action}
|
||||
</Badge>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-muted-foreground text-center py-8">Sélectionnez un rôle pour voir ses permissions</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
|
||||
|
||||
<TabsContent value="permissions" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle>Liste des Permissions</CardTitle>
|
||||
<CardDescription>Toutes les permissions disponibles dans le système</CardDescription>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={forceRefreshPermissions}
|
||||
>
|
||||
🔄 Actualiser Permissions
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
{Object.entries(permissionsByCategory)
|
||||
.filter(([category, categoryPermissions]) => {
|
||||
return Array.isArray(categoryPermissions) && categoryPermissions.length > 0;
|
||||
})
|
||||
.map(([category, categoryPermissions]) => (
|
||||
<div key={category} className="space-y-2">
|
||||
<h3 className="font-medium text-lg border-b pb-1">
|
||||
{categoryTranslations[category] || category}
|
||||
<span className="text-xs text-gray-500 ml-2 bg-blue-100 px-2 py-1 rounded">
|
||||
({categoryPermissions.length})
|
||||
</span>
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||
{categoryPermissions.map((permission) => (
|
||||
<div key={permission.id} className="flex items-center justify-between p-3 border rounded-lg shadow-sm">
|
||||
<div>
|
||||
<p className="font-medium text-sm">{getPermissionDisplayName(permission)}</p>
|
||||
<p className="text-xs text-muted-foreground">{permission.description}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{permission.action}
|
||||
</Badge>
|
||||
{permission.isSystem && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
Système
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
{/* Edit Role Dialog */}
|
||||
<Dialog open={editRoleOpen} onOpenChange={setEditRoleOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Modifier le Rôle</DialogTitle>
|
||||
<DialogDescription>Modifiez les informations du rôle</DialogDescription>
|
||||
</DialogHeader>
|
||||
{selectedRole && (
|
||||
<form onSubmit={handleUpdateRole} className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="edit-name">Nom technique</Label>
|
||||
<Input
|
||||
id="edit-name"
|
||||
name="name"
|
||||
defaultValue={selectedRole.name}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="edit-displayName">Nom d'affichage</Label>
|
||||
<Input
|
||||
id="edit-displayName"
|
||||
name="displayName"
|
||||
defaultValue={selectedRole.displayName}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="edit-description">Description</Label>
|
||||
<Input
|
||||
id="edit-description"
|
||||
name="description"
|
||||
defaultValue={selectedRole.description || ''}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="edit-color">Couleur</Label>
|
||||
<Input
|
||||
id="edit-color"
|
||||
name="color"
|
||||
type="color"
|
||||
defaultValue={selectedRole.color}
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" disabled={updateRoleMutation.isPending}>
|
||||
{updateRoleMutation.isPending ? 'Modification...' : 'Modifier'}
|
||||
</Button>
|
||||
</form>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+22
-100
@@ -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() {
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="flex items-center">
|
||||
{/* 🔧 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) => (
|
||||
<div key={userRole.roleId || index} className="flex items-center mr-2">
|
||||
<div
|
||||
className="w-3 h-3 rounded-full mr-2 border-2 border-white shadow-sm"
|
||||
style={{ backgroundColor: userRole.role?.color || '#6b7280' }}
|
||||
/>
|
||||
<Badge
|
||||
style={{
|
||||
backgroundColor: userRole.role?.color || '#6b7280',
|
||||
color: 'white'
|
||||
}}
|
||||
className="text-xs font-medium"
|
||||
>
|
||||
{userRole.role?.displayName || userRole.role?.name || 'Rôle inconnu'}
|
||||
</Badge>
|
||||
</div>
|
||||
))
|
||||
) : Array.isArray(userData.roles) && userData.roles.length > 0 ? (
|
||||
userData.roles.map((role, index) => (
|
||||
<div key={role.id || index} className="flex items-center mr-2">
|
||||
<div
|
||||
className="w-3 h-3 rounded-full mr-2 border-2 border-white shadow-sm"
|
||||
style={{ backgroundColor: role.color || '#6b7280' }}
|
||||
/>
|
||||
<Badge
|
||||
style={{
|
||||
backgroundColor: role.color || '#6b7280',
|
||||
color: 'white'
|
||||
}}
|
||||
className="text-xs font-medium"
|
||||
>
|
||||
{role.displayName || role.name}
|
||||
</Badge>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
// Fallback final si aucune donnée de rôle n'est disponible
|
||||
<div className="flex items-center">
|
||||
{getRoleIcon(userData.role)}
|
||||
<span className="ml-2">{getRoleBadge(userData.role)}</span>
|
||||
</div>
|
||||
)}
|
||||
{getRoleIcon(userData.role)}
|
||||
<span className="ml-2">{getRoleBadge(userData.role)}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
@@ -822,7 +751,7 @@ export default function UsersPage() {
|
||||
{/* Sélecteur de rôle rapide */}
|
||||
{userData.id !== user?.id && (
|
||||
<Select
|
||||
value={userData.userRoles?.[0]?.role?.name || userData.role || ''}
|
||||
value={userData.role || ''}
|
||||
onValueChange={(newRole) => handleRoleChange(userData.id, newRole)}
|
||||
disabled={updateUserRoleMutation.isPending}
|
||||
>
|
||||
@@ -830,17 +759,10 @@ export default function UsersPage() {
|
||||
<SelectValue placeholder="Rôle" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{safeRoles.map((role) => (
|
||||
<SelectItem key={role.id} value={role.name}>
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
className="w-2 h-2 rounded-full mr-2"
|
||||
style={{ backgroundColor: role.color }}
|
||||
/>
|
||||
{role.displayName || role.name}
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
<SelectItem value="admin">Administrateur</SelectItem>
|
||||
<SelectItem value="directeur">Directeur</SelectItem>
|
||||
<SelectItem value="manager">Manager</SelectItem>
|
||||
<SelectItem value="employee">Employé</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user