Checkpoint: Modernisation de l'ergonomie des pages Administration des listes et Paramètres d'import avec icônes colorées, dégradés, badges de statut, et animations

This commit is contained in:
Manus
2026-02-13 04:07:35 -05:00
parent f7f82fc68a
commit 196bd7103d
3 changed files with 400 additions and 285 deletions

View File

@@ -3,9 +3,9 @@ import { trpc } from "@/lib/trpc";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Badge } from "@/components/ui/badge";
import { toast } from "sonner";
import { Loader2, Plus, Trash2, Settings } from "lucide-react";
import { Loader2, Plus, Trash2, List, Building2, Calculator, Sparkles } from "lucide-react";
import DashboardLayout from "@/components/DashboardLayout";
import {
AlertDialog,
@@ -36,7 +36,7 @@ export default function ListsAdmin() {
const handleCreateDepartment = async () => {
if (!newDepartment.trim()) {
toast.error("Erreur", { description: "Le nom ne peut pas être vide" });
toast.error("Le nom ne peut pas être vide");
return;
}
@@ -44,15 +44,15 @@ export default function ListsAdmin() {
await createDepartmentMutation.mutateAsync({ name: newDepartment.trim() });
await utils.departments.getByUser.invalidate();
setNewDepartment("");
toast.success("Service ajouté", { description: `"${newDepartment}" a été ajouté à la liste` });
toast.success(`Service "${newDepartment}" ajouté avec succès`);
} catch (error: any) {
toast.error("Erreur", { description: error.message || "Impossible d'ajouter le service" });
toast.error(error.message || "Impossible d'ajouter le service");
}
};
const handleCreateAllocation = async () => {
if (!newAllocation.trim()) {
toast.error("Erreur", { description: "Le nom ne peut pas être vide" });
toast.error("Le nom ne peut pas être vide");
return;
}
@@ -60,9 +60,9 @@ export default function ListsAdmin() {
await createAllocationMutation.mutateAsync({ name: newAllocation.trim() });
await utils.accountingAllocations.getByUser.invalidate();
setNewAllocation("");
toast.success("Ventilation ajoutée", { description: `"${newAllocation}" a été ajoutée à la liste` });
toast.success(`Ventilation "${newAllocation}" ajoutée avec succès`);
} catch (error: any) {
toast.error("Erreur", { description: error.message || "Impossible d'ajouter la ventilation" });
toast.error(error.message || "Impossible d'ajouter la ventilation");
}
};
@@ -82,9 +82,9 @@ export default function ListsAdmin() {
await deleteAllocationMutation.mutateAsync({ id: itemToDelete.id });
await utils.accountingAllocations.getByUser.invalidate();
}
toast.success("Supprimé", { description: `"${itemToDelete.name}" a été supprimé` });
toast.success(`"${itemToDelete.name}" supprimé avec succès`);
} catch (error: any) {
toast.error("Erreur", { description: error.message || "Impossible de supprimer" });
toast.error(error.message || "Impossible de supprimer");
} finally {
setDeleteDialogOpen(false);
setItemToDelete(null);
@@ -94,36 +94,91 @@ export default function ListsAdmin() {
if (loadingDepartments || loadingAllocations) {
return (
<DashboardLayout>
<div className="flex items-center justify-center h-64">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
<div className="flex flex-col items-center justify-center h-64 gap-4">
<Loader2 className="w-12 h-12 animate-spin text-primary" />
<p className="text-muted-foreground">Chargement des listes...</p>
</div>
</DashboardLayout>
);
}
const departmentCount = departments?.length || 0;
const allocationCount = allocations?.length || 0;
return (
<DashboardLayout>
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold tracking-tight flex items-center gap-3">
<Settings className="h-8 w-8" />
Administration des listes
</h1>
<p className="text-muted-foreground mt-2">
Gérez les listes de valeurs pour les champs métier des factures
</p>
<div className="max-w-5xl space-y-8">
{/* Header */}
<div className="space-y-2">
<div className="flex items-center gap-3">
<div className="p-3 bg-gradient-to-br from-purple-500 to-pink-600 rounded-xl shadow-lg">
<List className="w-7 h-7 text-white" />
</div>
<div>
<h1 className="text-4xl font-bold bg-gradient-to-r from-purple-600 to-pink-600 bg-clip-text text-transparent">
Administration des listes
</h1>
<p className="text-muted-foreground mt-1">
Gérez les listes de valeurs pour les champs métier des factures
</p>
</div>
</div>
</div>
{/* Statistics */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="p-4 bg-gradient-to-r from-blue-50 to-cyan-50 dark:from-blue-950/20 dark:to-cyan-950/20 rounded-lg border border-blue-200 dark:border-blue-800">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="p-2 bg-blue-500 rounded-lg">
<Building2 className="w-5 h-5 text-white" />
</div>
<div>
<p className="text-sm text-muted-foreground">Services</p>
<p className="text-2xl font-bold text-blue-900 dark:text-blue-100">{departmentCount}</p>
</div>
</div>
<Badge variant="secondary" className="text-sm">
Actif{departmentCount > 1 ? 's' : ''}
</Badge>
</div>
</div>
<div className="p-4 bg-gradient-to-r from-green-50 to-emerald-50 dark:from-green-950/20 dark:to-emerald-950/20 rounded-lg border border-green-200 dark:border-green-800">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="p-2 bg-green-500 rounded-lg">
<Calculator className="w-5 h-5 text-white" />
</div>
<div>
<p className="text-sm text-muted-foreground">Ventilations</p>
<p className="text-2xl font-bold text-green-900 dark:text-green-100">{allocationCount}</p>
</div>
</div>
<Badge variant="secondary" className="text-sm">
Active{allocationCount > 1 ? 's' : ''}
</Badge>
</div>
</div>
</div>
{/* Department List */}
<Card>
<CardHeader>
<CardTitle>Service concerné</CardTitle>
<CardDescription>
Gérez la liste des services disponibles pour la classification des factures
</CardDescription>
<Card className="border-2 hover:border-primary/50 transition-colors">
<CardHeader className="bg-gradient-to-r from-blue-50 to-cyan-50 dark:from-blue-950/20 dark:to-cyan-950/20 border-b">
<div className="flex items-center gap-3">
<div className="p-2 bg-blue-500 rounded-lg">
<Building2 className="w-6 h-6 text-white" />
</div>
<div>
<CardTitle className="text-xl">Service concerné</CardTitle>
<CardDescription className="mt-1">
Gérez la liste des services disponibles pour la classification des factures
</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex gap-2">
<CardContent className="space-y-6 pt-6">
<div className="flex gap-3">
<div className="flex-1">
<Input
placeholder="Nouveau service (ex: INFORMATIQUE)"
@@ -134,58 +189,80 @@ export default function ListsAdmin() {
handleCreateDepartment();
}
}}
className="h-11"
/>
</div>
<Button
onClick={handleCreateDepartment}
disabled={createDepartmentMutation.isPending || !newDepartment.trim()}
size="lg"
className="bg-blue-500 hover:bg-blue-600"
>
{createDepartmentMutation.isPending ? (
<Loader2 className="h-4 w-4 animate-spin" />
<Loader2 className="h-5 w-5 animate-spin" />
) : (
<Plus className="h-4 w-4" />
<Plus className="h-5 w-5" />
)}
<span className="ml-2">Ajouter</span>
</Button>
</div>
<div className="space-y-2">
<div className="space-y-3">
{departments && departments.length > 0 ? (
departments.map((dept) => (
<div
key={dept.id}
className="flex items-center justify-between p-3 border rounded-lg hover:bg-accent/50 transition-colors"
className="group flex items-center justify-between p-4 border-2 rounded-lg hover:border-blue-300 hover:bg-blue-50/50 dark:hover:bg-blue-950/20 transition-all"
>
<span className="font-medium">{dept.name}</span>
<div className="flex items-center gap-3">
<div className="p-2 bg-blue-100 dark:bg-blue-900/30 rounded-lg group-hover:bg-blue-200 dark:group-hover:bg-blue-900/50 transition-colors">
<Building2 className="h-4 w-4 text-blue-600 dark:text-blue-400" />
</div>
<span className="font-semibold text-lg">{dept.name}</span>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => confirmDelete("department", dept.id, dept.name)}
disabled={deleteDepartmentMutation.isPending}
className="opacity-0 group-hover:opacity-100 transition-opacity hover:bg-red-100 dark:hover:bg-red-900/30"
>
<Trash2 className="h-4 w-4 text-destructive" />
<Trash2 className="h-5 w-5 text-destructive" />
</Button>
</div>
))
) : (
<p className="text-sm text-muted-foreground text-center py-4">
Aucun service configuré. Ajoutez-en un pour commencer.
</p>
<div className="text-center py-12 bg-muted/30 rounded-lg border-2 border-dashed">
<Building2 className="h-12 w-12 mx-auto text-muted-foreground/50 mb-3" />
<p className="text-muted-foreground font-medium">
Aucun service configuré
</p>
<p className="text-sm text-muted-foreground/70 mt-1">
Ajoutez-en un pour commencer
</p>
</div>
)}
</div>
</CardContent>
</Card>
{/* Accounting Allocation List */}
<Card>
<CardHeader>
<CardTitle>Ventilation comptable</CardTitle>
<CardDescription>
Gérez la liste des ventilations comptables disponibles pour la classification des factures
</CardDescription>
<Card className="border-2 hover:border-primary/50 transition-colors">
<CardHeader className="bg-gradient-to-r from-green-50 to-emerald-50 dark:from-green-950/20 dark:to-emerald-950/20 border-b">
<div className="flex items-center gap-3">
<div className="p-2 bg-green-500 rounded-lg">
<Calculator className="w-6 h-6 text-white" />
</div>
<div>
<CardTitle className="text-xl">Ventilation comptable</CardTitle>
<CardDescription className="mt-1">
Gérez la liste des ventilations comptables disponibles pour la classification des factures
</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex gap-2">
<CardContent className="space-y-6 pt-6">
<div className="flex gap-3">
<div className="flex-1">
<Input
placeholder="Nouvelle ventilation (ex: MAINTENANCE)"
@@ -196,43 +273,58 @@ export default function ListsAdmin() {
handleCreateAllocation();
}
}}
className="h-11"
/>
</div>
<Button
onClick={handleCreateAllocation}
disabled={createAllocationMutation.isPending || !newAllocation.trim()}
size="lg"
className="bg-green-500 hover:bg-green-600"
>
{createAllocationMutation.isPending ? (
<Loader2 className="h-4 w-4 animate-spin" />
<Loader2 className="h-5 w-5 animate-spin" />
) : (
<Plus className="h-4 w-4" />
<Plus className="h-5 w-5" />
)}
<span className="ml-2">Ajouter</span>
</Button>
</div>
<div className="space-y-2">
<div className="space-y-3">
{allocations && allocations.length > 0 ? (
allocations.map((alloc) => (
<div
key={alloc.id}
className="flex items-center justify-between p-3 border rounded-lg hover:bg-accent/50 transition-colors"
className="group flex items-center justify-between p-4 border-2 rounded-lg hover:border-green-300 hover:bg-green-50/50 dark:hover:bg-green-950/20 transition-all"
>
<span className="font-medium">{alloc.name}</span>
<div className="flex items-center gap-3">
<div className="p-2 bg-green-100 dark:bg-green-900/30 rounded-lg group-hover:bg-green-200 dark:group-hover:bg-green-900/50 transition-colors">
<Calculator className="h-4 w-4 text-green-600 dark:text-green-400" />
</div>
<span className="font-semibold text-lg">{alloc.name}</span>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => confirmDelete("allocation", alloc.id, alloc.name)}
disabled={deleteAllocationMutation.isPending}
className="opacity-0 group-hover:opacity-100 transition-opacity hover:bg-red-100 dark:hover:bg-red-900/30"
>
<Trash2 className="h-4 w-4 text-destructive" />
<Trash2 className="h-5 w-5 text-destructive" />
</Button>
</div>
))
) : (
<p className="text-sm text-muted-foreground text-center py-4">
Aucune ventilation configurée. Ajoutez-en une pour commencer.
</p>
<div className="text-center py-12 bg-muted/30 rounded-lg border-2 border-dashed">
<Calculator className="h-12 w-12 mx-auto text-muted-foreground/50 mb-3" />
<p className="text-muted-foreground font-medium">
Aucune ventilation configurée
</p>
<p className="text-sm text-muted-foreground/70 mt-1">
Ajoutez-en une pour commencer
</p>
</div>
)}
</div>
</CardContent>
@@ -243,15 +335,20 @@ export default function ListsAdmin() {
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Confirmer la suppression</AlertDialogTitle>
<AlertDialogDescription>
Êtes-vous sûr de vouloir supprimer "{itemToDelete?.name}" ?
<AlertDialogTitle className="text-xl">Confirmer la suppression</AlertDialogTitle>
<AlertDialogDescription className="text-base">
Êtes-vous sûr de vouloir supprimer <span className="font-semibold">"{itemToDelete?.name}"</span> ?
<br />
Cette action est irréversible.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Annuler</AlertDialogCancel>
<AlertDialogAction onClick={handleDelete} className="bg-destructive text-destructive-foreground hover:bg-destructive/90">
<AlertDialogAction
onClick={handleDelete}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
<Trash2 className="h-4 w-4 mr-2" />
Supprimer
</AlertDialogAction>
</AlertDialogFooter>