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:
@@ -5,8 +5,9 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Loader2, Save, Upload, FolderOpen, Mail, Play, Square } from "lucide-react";
|
import { Loader2, Save, Upload, FolderOpen, Mail, Play, Square, Download, Inbox, CheckCircle2 } from "lucide-react";
|
||||||
import DashboardLayout from "@/components/DashboardLayout";
|
import DashboardLayout from "@/components/DashboardLayout";
|
||||||
|
|
||||||
export default function ImportSettings() {
|
export default function ImportSettings() {
|
||||||
@@ -73,53 +74,66 @@ export default function ImportSettings() {
|
|||||||
exportFolder: exportFolder || null,
|
exportFolder: exportFolder || null,
|
||||||
});
|
});
|
||||||
|
|
||||||
toast.success("Paramètres enregistrés", {
|
toast.success("Paramètres enregistrés avec succès");
|
||||||
description: "Vos paramètres de réception ont été mis à jour avec succès.",
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error("Erreur", {
|
toast.error("Impossible d'enregistrer les paramètres");
|
||||||
description: "Impossible d'enregistrer les paramètres. Veuillez réessayer.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center h-[calc(100vh-200px)]">
|
<DashboardLayout>
|
||||||
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
<div className="flex flex-col items-center justify-center h-64 gap-4">
|
||||||
</div>
|
<Loader2 className="w-12 h-12 animate-spin text-primary" />
|
||||||
|
<p className="text-muted-foreground">Chargement des paramètres...</p>
|
||||||
|
</div>
|
||||||
|
</DashboardLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
<div className="container max-w-4xl py-8">
|
<div className="max-w-5xl space-y-8">
|
||||||
<div className="mb-8">
|
{/* Header */}
|
||||||
<h1 className="text-3xl font-bold mb-2">Paramètres de réception</h1>
|
<div className="space-y-2">
|
||||||
<p className="text-muted-foreground">
|
<div className="flex items-center gap-3">
|
||||||
Configurez les différentes méthodes d'importation de factures
|
<div className="p-3 bg-gradient-to-br from-blue-500 to-cyan-600 rounded-xl shadow-lg">
|
||||||
</p>
|
<Inbox className="w-7 h-7 text-white" />
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1 className="text-4xl font-bold bg-gradient-to-r from-blue-600 to-cyan-600 bg-clip-text text-transparent">
|
||||||
|
Paramètres de réception
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground mt-1">
|
||||||
|
Configurez les différentes méthodes d'importation de factures
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="space-y-6">
|
|
||||||
{/* Manual Import */}
|
{/* Manual Import */}
|
||||||
<Card>
|
<Card className="border-2 hover:border-primary/50 transition-colors">
|
||||||
<CardHeader>
|
<CardHeader className="bg-gradient-to-r from-purple-50 to-pink-50 dark:from-purple-950/20 dark:to-pink-950/20 border-b">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center justify-between">
|
||||||
<div className="p-2 bg-primary/10 rounded-lg">
|
<div className="flex items-center gap-3">
|
||||||
<Upload className="h-5 w-5 text-primary" />
|
<div className="p-2 bg-purple-500 rounded-lg">
|
||||||
</div>
|
<Upload className="w-6 h-6 text-white" />
|
||||||
<div>
|
</div>
|
||||||
<CardTitle>Import manuel</CardTitle>
|
<div>
|
||||||
<CardDescription>
|
<CardTitle className="text-xl">Import manuel</CardTitle>
|
||||||
Autoriser l'upload manuel de fichiers PDF via l'interface
|
<CardDescription className="mt-1">
|
||||||
</CardDescription>
|
Autoriser l'upload manuel de fichiers PDF via l'interface
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Badge variant={manualImportEnabled ? "default" : "secondary"} className="text-sm">
|
||||||
|
{manualImportEnabled ? "Activé" : "Désactivé"}
|
||||||
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent className="pt-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between p-4 bg-muted/30 rounded-lg">
|
||||||
<Label htmlFor="manual-import" className="text-base">
|
<Label htmlFor="manual-import" className="text-base font-medium">
|
||||||
Activer l'import manuel
|
Activer l'import manuel
|
||||||
</Label>
|
</Label>
|
||||||
<Switch
|
<Switch
|
||||||
@@ -132,23 +146,28 @@ export default function ImportSettings() {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Automatic Import from Folder */}
|
{/* Automatic Import from Folder */}
|
||||||
<Card>
|
<Card className="border-2 hover:border-primary/50 transition-colors">
|
||||||
<CardHeader>
|
<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="flex items-center justify-between">
|
||||||
<div className="p-2 bg-blue-500/10 rounded-lg">
|
<div className="flex items-center gap-3">
|
||||||
<FolderOpen className="h-5 w-5 text-blue-500" />
|
<div className="p-2 bg-blue-500 rounded-lg">
|
||||||
</div>
|
<FolderOpen className="w-6 h-6 text-white" />
|
||||||
<div>
|
</div>
|
||||||
<CardTitle>Import automatique depuis dossier</CardTitle>
|
<div>
|
||||||
<CardDescription>
|
<CardTitle className="text-xl">Import automatique depuis dossier</CardTitle>
|
||||||
Surveiller un dossier et importer automatiquement les nouveaux fichiers PDF
|
<CardDescription className="mt-1">
|
||||||
</CardDescription>
|
Surveiller un dossier et importer automatiquement les nouveaux fichiers PDF
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Badge variant={autoImportEnabled ? "default" : "secondary"} className="text-sm">
|
||||||
|
{autoImportEnabled ? "Activé" : "Désactivé"}
|
||||||
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-6 pt-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between p-4 bg-muted/30 rounded-lg">
|
||||||
<Label htmlFor="auto-import" className="text-base">
|
<Label htmlFor="auto-import" className="text-base font-medium">
|
||||||
Activer l'import automatique
|
Activer l'import automatique
|
||||||
</Label>
|
</Label>
|
||||||
<Switch
|
<Switch
|
||||||
@@ -159,15 +178,16 @@ export default function ImportSettings() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{autoImportEnabled && (
|
{autoImportEnabled && (
|
||||||
<>
|
<div className="space-y-4 p-4 bg-blue-50/50 dark:bg-blue-950/10 rounded-lg border-2 border-blue-200 dark:border-blue-800">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="source-path">Chemin du dossier source</Label>
|
<Label htmlFor="source-path" className="text-base font-medium">Chemin du dossier source</Label>
|
||||||
<Input
|
<Input
|
||||||
id="source-path"
|
id="source-path"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="/chemin/vers/dossier/factures"
|
placeholder="/chemin/vers/dossier/factures"
|
||||||
value={autoImportSourcePath}
|
value={autoImportSourcePath}
|
||||||
onChange={(e) => setAutoImportSourcePath(e.target.value)}
|
onChange={(e) => setAutoImportSourcePath(e.target.value)}
|
||||||
|
className="h-11"
|
||||||
/>
|
/>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Chemin absolu du dossier à surveiller pour les nouveaux fichiers PDF
|
Chemin absolu du dossier à surveiller pour les nouveaux fichiers PDF
|
||||||
@@ -175,107 +195,104 @@ export default function ImportSettings() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="auto-frequency">Fréquence de lecture (minutes)</Label>
|
<Label htmlFor="auto-frequency" className="text-base font-medium">Fréquence de lecture (minutes)</Label>
|
||||||
<Input
|
<Input
|
||||||
id="auto-frequency"
|
id="auto-frequency"
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
value={autoImportFrequency}
|
value={autoImportFrequency}
|
||||||
onChange={(e) => setAutoImportFrequency(parseInt(e.target.value) || 60)}
|
onChange={(e) => setAutoImportFrequency(parseInt(e.target.value) || 60)}
|
||||||
|
className="h-11"
|
||||||
/>
|
/>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Intervalle de temps entre chaque vérification du dossier (minimum: 1 minute)
|
Intervalle de temps entre chaque vérification du dossier (minimum: 1 minute)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Service Control Buttons */}
|
{/* Service Control Buttons */}
|
||||||
<div className="flex gap-2 pt-4 border-t">
|
<div className="flex gap-3 pt-4 border-t border-blue-200 dark:border-blue-800">
|
||||||
{folderServiceStatus?.isRunning ? (
|
{folderServiceStatus?.isRunning ? (
|
||||||
<Button
|
<>
|
||||||
variant="destructive"
|
<Button
|
||||||
onClick={async () => {
|
variant="destructive"
|
||||||
try {
|
onClick={async () => {
|
||||||
await stopFolderServiceMutation.mutateAsync();
|
try {
|
||||||
await utils.folderImportService.status.invalidate();
|
await stopFolderServiceMutation.mutateAsync();
|
||||||
toast.success("Service arrêté", {
|
await utils.folderImportService.status.invalidate();
|
||||||
description: "L'import automatique depuis dossier a été arrêté.",
|
toast.success("Service arrêté");
|
||||||
});
|
} catch (error) {
|
||||||
} catch (error) {
|
toast.error("Impossible d'arrêter le service");
|
||||||
toast.error("Erreur", {
|
}
|
||||||
description: "Impossible d'arrêter le service.",
|
}}
|
||||||
});
|
disabled={stopFolderServiceMutation.isPending}
|
||||||
}
|
>
|
||||||
}}
|
{stopFolderServiceMutation.isPending ? (
|
||||||
disabled={stopFolderServiceMutation.isPending}
|
<Loader2 className="mr-2 h-5 w-5 animate-spin" />
|
||||||
>
|
) : (
|
||||||
{stopFolderServiceMutation.isPending ? (
|
<Square className="mr-2 h-5 w-5" />
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
)}
|
||||||
) : (
|
Arrêter le service
|
||||||
<Square className="mr-2 h-4 w-4" />
|
</Button>
|
||||||
)}
|
<Badge variant="default" className="flex items-center gap-2 px-4 bg-green-500">
|
||||||
Arrêter le service
|
<span className="w-2 h-2 bg-white rounded-full animate-pulse" />
|
||||||
</Button>
|
Service actif
|
||||||
) : (
|
</Badge>
|
||||||
<Button
|
</>
|
||||||
onClick={async () => {
|
) : (
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
try {
|
try {
|
||||||
const result = await startFolderServiceMutation.mutateAsync();
|
const result = await startFolderServiceMutation.mutateAsync();
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
await utils.folderImportService.status.invalidate();
|
await utils.folderImportService.status.invalidate();
|
||||||
toast.success("Service démarré", {
|
toast.success("Service démarré");
|
||||||
description: "L'import automatique depuis dossier est maintenant actif.",
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
toast.error("Erreur", {
|
toast.error("Impossible de démarrer le service");
|
||||||
description: "Impossible de démarrer le service. Vérifiez la configuration.",
|
}
|
||||||
});
|
} catch (error) {
|
||||||
|
toast.error("Impossible de démarrer le service");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
}}
|
||||||
toast.error("Erreur", {
|
disabled={!autoImportEnabled || startFolderServiceMutation.isPending}
|
||||||
description: "Impossible de démarrer le service.",
|
className="bg-blue-500 hover:bg-blue-600"
|
||||||
});
|
>
|
||||||
}
|
{startFolderServiceMutation.isPending ? (
|
||||||
}}
|
<Loader2 className="mr-2 h-5 w-5 animate-spin" />
|
||||||
disabled={!autoImportEnabled || startFolderServiceMutation.isPending}
|
) : (
|
||||||
>
|
<Play className="mr-2 h-5 w-5" />
|
||||||
{startFolderServiceMutation.isPending ? (
|
)}
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
Démarrer le service
|
||||||
) : (
|
</Button>
|
||||||
<Play className="mr-2 h-4 w-4" />
|
)}
|
||||||
)}
|
</div>
|
||||||
Démarrer le service
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{folderServiceStatus?.isRunning && (
|
|
||||||
<span className="flex items-center text-sm text-green-600">
|
|
||||||
<span className="w-2 h-2 bg-green-600 rounded-full mr-2 animate-pulse" />
|
|
||||||
Service actif
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Email Import */}
|
{/* Email Import */}
|
||||||
<Card>
|
<Card className="border-2 hover:border-primary/50 transition-colors">
|
||||||
<CardHeader>
|
<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="flex items-center justify-between">
|
||||||
<div className="p-2 bg-green-500/10 rounded-lg">
|
<div className="flex items-center gap-3">
|
||||||
<Mail className="h-5 w-5 text-green-500" />
|
<div className="p-2 bg-green-500 rounded-lg">
|
||||||
</div>
|
<Mail className="w-6 h-6 text-white" />
|
||||||
<div>
|
</div>
|
||||||
<CardTitle>Import par email</CardTitle>
|
<div>
|
||||||
<CardDescription>
|
<CardTitle className="text-xl">Import par email</CardTitle>
|
||||||
Récupérer automatiquement les factures reçues par email
|
<CardDescription className="mt-1">
|
||||||
</CardDescription>
|
Récupérer automatiquement les factures reçues par email
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Badge variant={emailImportEnabled ? "default" : "secondary"} className="text-sm">
|
||||||
|
{emailImportEnabled ? "Activé" : "Désactivé"}
|
||||||
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-6 pt-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between p-4 bg-muted/30 rounded-lg">
|
||||||
<Label htmlFor="email-import" className="text-base">
|
<Label htmlFor="email-import" className="text-base font-medium">
|
||||||
Activer l'import par email
|
Activer l'import par email
|
||||||
</Label>
|
</Label>
|
||||||
<Switch
|
<Switch
|
||||||
@@ -286,15 +303,16 @@ export default function ImportSettings() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{emailImportEnabled && (
|
{emailImportEnabled && (
|
||||||
<>
|
<div className="space-y-4 p-4 bg-green-50/50 dark:bg-green-950/10 rounded-lg border-2 border-green-200 dark:border-green-800">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="email-address">Adresse email</Label>
|
<Label htmlFor="email-address" className="text-base font-medium">Adresse email</Label>
|
||||||
<Input
|
<Input
|
||||||
id="email-address"
|
id="email-address"
|
||||||
type="email"
|
type="email"
|
||||||
placeholder="factures@votreentreprise.com"
|
placeholder="factures@votreentreprise.com"
|
||||||
value={emailImportAddress}
|
value={emailImportAddress}
|
||||||
onChange={(e) => setEmailImportAddress(e.target.value)}
|
onChange={(e) => setEmailImportAddress(e.target.value)}
|
||||||
|
className="h-11"
|
||||||
/>
|
/>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Adresse email à surveiller pour les factures
|
Adresse email à surveiller pour les factures
|
||||||
@@ -302,28 +320,30 @@ export default function ImportSettings() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="email-password">Mot de passe</Label>
|
<Label htmlFor="email-password" className="text-base font-medium">Mot de passe</Label>
|
||||||
<Input
|
<Input
|
||||||
id="email-password"
|
id="email-password"
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="••••••••"
|
placeholder="••••••••"
|
||||||
value={emailImportPassword}
|
value={emailImportPassword}
|
||||||
onChange={(e) => setEmailImportPassword(e.target.value)}
|
onChange={(e) => setEmailImportPassword(e.target.value)}
|
||||||
|
className="h-11"
|
||||||
/>
|
/>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Mot de passe du compte email (stocké de manière sécurisée)
|
Mot de passe du compte email (stocké de manière sécurisée)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="email-host">Serveur IMAP</Label>
|
<Label htmlFor="email-host" className="text-base font-medium">Serveur IMAP</Label>
|
||||||
<Input
|
<Input
|
||||||
id="email-host"
|
id="email-host"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="imap.gmail.com"
|
placeholder="imap.gmail.com"
|
||||||
value={emailImportHost}
|
value={emailImportHost}
|
||||||
onChange={(e) => setEmailImportHost(e.target.value)}
|
onChange={(e) => setEmailImportHost(e.target.value)}
|
||||||
|
className="h-11"
|
||||||
/>
|
/>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Adresse du serveur IMAP
|
Adresse du serveur IMAP
|
||||||
@@ -331,7 +351,7 @@ export default function ImportSettings() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="email-port">Port IMAP</Label>
|
<Label htmlFor="email-port" className="text-base font-medium">Port IMAP</Label>
|
||||||
<Input
|
<Input
|
||||||
id="email-port"
|
id="email-port"
|
||||||
type="number"
|
type="number"
|
||||||
@@ -339,6 +359,7 @@ export default function ImportSettings() {
|
|||||||
max="65535"
|
max="65535"
|
||||||
value={emailImportPort}
|
value={emailImportPort}
|
||||||
onChange={(e) => setEmailImportPort(parseInt(e.target.value) || 993)}
|
onChange={(e) => setEmailImportPort(parseInt(e.target.value) || 993)}
|
||||||
|
className="h-11"
|
||||||
/>
|
/>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Port SSL (993 par défaut)
|
Port SSL (993 par défaut)
|
||||||
@@ -347,13 +368,14 @@ export default function ImportSettings() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="email-frequency">Fréquence de lecture (minutes)</Label>
|
<Label htmlFor="email-frequency" className="text-base font-medium">Fréquence de lecture (minutes)</Label>
|
||||||
<Input
|
<Input
|
||||||
id="email-frequency"
|
id="email-frequency"
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
value={emailImportFrequency}
|
value={emailImportFrequency}
|
||||||
onChange={(e) => setEmailImportFrequency(parseInt(e.target.value) || 30)}
|
onChange={(e) => setEmailImportFrequency(parseInt(e.target.value) || 30)}
|
||||||
|
className="h-11"
|
||||||
/>
|
/>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Intervalle de temps entre chaque vérification des emails (minimum: 1 minute)
|
Intervalle de temps entre chaque vérification des emails (minimum: 1 minute)
|
||||||
@@ -361,32 +383,60 @@ export default function ImportSettings() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Service Control Buttons */}
|
{/* Service Control Buttons */}
|
||||||
<div className="flex gap-2 pt-4 border-t">
|
<div className="flex flex-wrap gap-3 pt-4 border-t border-green-200 dark:border-green-800">
|
||||||
{emailServiceStatus?.isRunning ? (
|
{emailServiceStatus?.isRunning ? (
|
||||||
<Button
|
<>
|
||||||
variant="destructive"
|
<Button
|
||||||
onClick={async () => {
|
variant="destructive"
|
||||||
try {
|
onClick={async () => {
|
||||||
await stopEmailServiceMutation.mutateAsync();
|
try {
|
||||||
await utils.emailImportService.status.invalidate();
|
await stopEmailServiceMutation.mutateAsync();
|
||||||
toast.success("Service arrêté", {
|
await utils.emailImportService.status.invalidate();
|
||||||
description: "L'import automatique par email a été arrêté.",
|
toast.success("Service arrêté");
|
||||||
});
|
} catch (error) {
|
||||||
} catch (error) {
|
toast.error("Impossible d'arrêter le service");
|
||||||
toast.error("Erreur", {
|
}
|
||||||
description: "Impossible d'arrêter le service.",
|
}}
|
||||||
});
|
disabled={stopEmailServiceMutation.isPending}
|
||||||
}
|
>
|
||||||
}}
|
{stopEmailServiceMutation.isPending ? (
|
||||||
disabled={stopEmailServiceMutation.isPending}
|
<Loader2 className="mr-2 h-5 w-5 animate-spin" />
|
||||||
>
|
) : (
|
||||||
{stopEmailServiceMutation.isPending ? (
|
<Square className="mr-2 h-5 w-5" />
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
)}
|
||||||
) : (
|
Arrêter le service
|
||||||
<Square className="mr-2 h-4 w-4" />
|
</Button>
|
||||||
)}
|
<Button
|
||||||
Arrêter le service
|
variant="outline"
|
||||||
</Button>
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
toast.info("Vérification en cours...");
|
||||||
|
const result = await checkNowEmailMutation.mutateAsync();
|
||||||
|
if (result.success) {
|
||||||
|
await utils.importLogs.getByUser.invalidate();
|
||||||
|
toast.success(result.message);
|
||||||
|
} else {
|
||||||
|
toast.error(result.message);
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
toast.error("Impossible de vérifier les emails");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={checkNowEmailMutation.isPending}
|
||||||
|
className="border-green-300 hover:bg-green-50 dark:hover:bg-green-950/20"
|
||||||
|
>
|
||||||
|
{checkNowEmailMutation.isPending ? (
|
||||||
|
<Loader2 className="mr-2 h-5 w-5 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<CheckCircle2 className="mr-2 h-5 w-5" />
|
||||||
|
)}
|
||||||
|
Vérifier maintenant
|
||||||
|
</Button>
|
||||||
|
<Badge variant="default" className="flex items-center gap-2 px-4 bg-green-500">
|
||||||
|
<span className="w-2 h-2 bg-white rounded-full animate-pulse" />
|
||||||
|
Service actif
|
||||||
|
</Badge>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
@@ -394,98 +444,56 @@ export default function ImportSettings() {
|
|||||||
const result = await startEmailServiceMutation.mutateAsync();
|
const result = await startEmailServiceMutation.mutateAsync();
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
await utils.emailImportService.status.invalidate();
|
await utils.emailImportService.status.invalidate();
|
||||||
toast.success("Service démarré", {
|
toast.success("Service démarré");
|
||||||
description: "L'import automatique par email est maintenant actif.",
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
toast.error("Erreur", {
|
toast.error("Impossible de démarrer le service");
|
||||||
description: "Impossible de démarrer le service. Vérifiez la configuration.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error("Erreur", {
|
toast.error("Impossible de démarrer le service");
|
||||||
description: "Impossible de démarrer le service.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={!emailImportEnabled || startEmailServiceMutation.isPending}
|
disabled={!emailImportEnabled || startEmailServiceMutation.isPending}
|
||||||
|
className="bg-green-500 hover:bg-green-600"
|
||||||
>
|
>
|
||||||
{startEmailServiceMutation.isPending ? (
|
{startEmailServiceMutation.isPending ? (
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
<Loader2 className="mr-2 h-5 w-5 animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
<Play className="mr-2 h-4 w-4" />
|
<Play className="mr-2 h-5 w-5" />
|
||||||
)}
|
)}
|
||||||
Démarrer le service
|
Démarrer le service
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{emailServiceStatus?.isRunning && (
|
|
||||||
<span className="flex items-center text-sm text-green-600">
|
|
||||||
<span className="w-2 h-2 bg-green-600 rounded-full mr-2 animate-pulse" />
|
|
||||||
Service actif
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{emailServiceStatus?.isRunning && (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={async () => {
|
|
||||||
try {
|
|
||||||
toast.info("Vérification en cours...", {
|
|
||||||
description: "Connexion au serveur IMAP et recherche de nouveaux emails.",
|
|
||||||
});
|
|
||||||
const result = await checkNowEmailMutation.mutateAsync();
|
|
||||||
if (result.success) {
|
|
||||||
await utils.importLogs.getByUser.invalidate();
|
|
||||||
toast.success("Vérification terminée", {
|
|
||||||
description: result.message,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
toast.error("Erreur", {
|
|
||||||
description: result.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
toast.error("Erreur", {
|
|
||||||
description: error.message || "Impossible de vérifier les emails.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={checkNowEmailMutation.isPending}
|
|
||||||
>
|
|
||||||
{checkNowEmailMutation.isPending ? (
|
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<Mail className="mr-2 h-4 w-4" />
|
|
||||||
)}
|
|
||||||
Vérifier maintenant
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Export Folder */}
|
{/* Export Folder */}
|
||||||
<Card>
|
<Card className="border-2 hover:border-primary/50 transition-colors">
|
||||||
<CardHeader>
|
<CardHeader className="bg-gradient-to-r from-orange-50 to-amber-50 dark:from-orange-950/20 dark:to-amber-950/20 border-b">
|
||||||
<CardTitle className="flex items-center gap-2">
|
<div className="flex items-center gap-3">
|
||||||
<FolderOpen className="h-5 w-5" />
|
<div className="p-2 bg-orange-500 rounded-lg">
|
||||||
Dossier d'export
|
<Download className="w-6 h-6 text-white" />
|
||||||
</CardTitle>
|
</div>
|
||||||
<CardDescription>
|
<div>
|
||||||
Configurez le dossier de destination pour l'export des factures
|
<CardTitle className="text-xl">Dossier d'export</CardTitle>
|
||||||
</CardDescription>
|
<CardDescription className="mt-1">
|
||||||
|
Configurez le dossier de destination pour l'export des factures
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4 pt-6">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="exportFolder">Chemin du dossier d'export</Label>
|
<Label htmlFor="exportFolder" className="text-base font-medium">Chemin du dossier d'export</Label>
|
||||||
<Input
|
<Input
|
||||||
id="exportFolder"
|
id="exportFolder"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="/chemin/vers/dossier/export"
|
placeholder="/chemin/vers/dossier/export"
|
||||||
value={exportFolder}
|
value={exportFolder}
|
||||||
onChange={(e) => setExportFolder(e.target.value)}
|
onChange={(e) => setExportFolder(e.target.value)}
|
||||||
|
className="h-11"
|
||||||
/>
|
/>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Spécifiez le chemin absolu du dossier où les factures seront exportées
|
Spécifiez le chemin absolu du dossier où les factures seront exportées
|
||||||
@@ -495,27 +503,27 @@ export default function ImportSettings() {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Save Button */}
|
{/* Save Button */}
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end pb-8">
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={updateMutation.isPending}
|
disabled={updateMutation.isPending}
|
||||||
size="lg"
|
size="lg"
|
||||||
|
className="bg-gradient-to-r from-blue-500 to-cyan-600 hover:from-blue-600 hover:to-cyan-700 text-white shadow-lg"
|
||||||
>
|
>
|
||||||
{updateMutation.isPending ? (
|
{updateMutation.isPending ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
<Loader2 className="mr-2 h-5 w-5 animate-spin" />
|
||||||
Enregistrement...
|
Enregistrement...
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Save className="mr-2 h-4 w-4" />
|
<Save className="mr-2 h-5 w-5" />
|
||||||
Enregistrer les paramètres
|
Enregistrer les paramètres
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</DashboardLayout>
|
</DashboardLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import { trpc } from "@/lib/trpc";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { toast } from "sonner";
|
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 DashboardLayout from "@/components/DashboardLayout";
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
@@ -36,7 +36,7 @@ export default function ListsAdmin() {
|
|||||||
|
|
||||||
const handleCreateDepartment = async () => {
|
const handleCreateDepartment = async () => {
|
||||||
if (!newDepartment.trim()) {
|
if (!newDepartment.trim()) {
|
||||||
toast.error("Erreur", { description: "Le nom ne peut pas être vide" });
|
toast.error("Le nom ne peut pas être vide");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,15 +44,15 @@ export default function ListsAdmin() {
|
|||||||
await createDepartmentMutation.mutateAsync({ name: newDepartment.trim() });
|
await createDepartmentMutation.mutateAsync({ name: newDepartment.trim() });
|
||||||
await utils.departments.getByUser.invalidate();
|
await utils.departments.getByUser.invalidate();
|
||||||
setNewDepartment("");
|
setNewDepartment("");
|
||||||
toast.success("Service ajouté", { description: `"${newDepartment}" a été ajouté à la liste` });
|
toast.success(`Service "${newDepartment}" ajouté avec succès`);
|
||||||
} catch (error: any) {
|
} 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 () => {
|
const handleCreateAllocation = async () => {
|
||||||
if (!newAllocation.trim()) {
|
if (!newAllocation.trim()) {
|
||||||
toast.error("Erreur", { description: "Le nom ne peut pas être vide" });
|
toast.error("Le nom ne peut pas être vide");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,9 +60,9 @@ export default function ListsAdmin() {
|
|||||||
await createAllocationMutation.mutateAsync({ name: newAllocation.trim() });
|
await createAllocationMutation.mutateAsync({ name: newAllocation.trim() });
|
||||||
await utils.accountingAllocations.getByUser.invalidate();
|
await utils.accountingAllocations.getByUser.invalidate();
|
||||||
setNewAllocation("");
|
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) {
|
} 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 deleteAllocationMutation.mutateAsync({ id: itemToDelete.id });
|
||||||
await utils.accountingAllocations.getByUser.invalidate();
|
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) {
|
} catch (error: any) {
|
||||||
toast.error("Erreur", { description: error.message || "Impossible de supprimer" });
|
toast.error(error.message || "Impossible de supprimer");
|
||||||
} finally {
|
} finally {
|
||||||
setDeleteDialogOpen(false);
|
setDeleteDialogOpen(false);
|
||||||
setItemToDelete(null);
|
setItemToDelete(null);
|
||||||
@@ -94,36 +94,91 @@ export default function ListsAdmin() {
|
|||||||
if (loadingDepartments || loadingAllocations) {
|
if (loadingDepartments || loadingAllocations) {
|
||||||
return (
|
return (
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
<div className="flex items-center justify-center h-64">
|
<div className="flex flex-col items-center justify-center h-64 gap-4">
|
||||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
<Loader2 className="w-12 h-12 animate-spin text-primary" />
|
||||||
|
<p className="text-muted-foreground">Chargement des listes...</p>
|
||||||
</div>
|
</div>
|
||||||
</DashboardLayout>
|
</DashboardLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const departmentCount = departments?.length || 0;
|
||||||
|
const allocationCount = allocations?.length || 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
<div className="space-y-6">
|
<div className="max-w-5xl space-y-8">
|
||||||
<div>
|
{/* Header */}
|
||||||
<h1 className="text-3xl font-bold tracking-tight flex items-center gap-3">
|
<div className="space-y-2">
|
||||||
<Settings className="h-8 w-8" />
|
<div className="flex items-center gap-3">
|
||||||
Administration des listes
|
<div className="p-3 bg-gradient-to-br from-purple-500 to-pink-600 rounded-xl shadow-lg">
|
||||||
</h1>
|
<List className="w-7 h-7 text-white" />
|
||||||
<p className="text-muted-foreground mt-2">
|
</div>
|
||||||
Gérez les listes de valeurs pour les champs métier des factures
|
<div>
|
||||||
</p>
|
<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>
|
</div>
|
||||||
|
|
||||||
{/* Department List */}
|
{/* Department List */}
|
||||||
<Card>
|
<Card className="border-2 hover:border-primary/50 transition-colors">
|
||||||
<CardHeader>
|
<CardHeader className="bg-gradient-to-r from-blue-50 to-cyan-50 dark:from-blue-950/20 dark:to-cyan-950/20 border-b">
|
||||||
<CardTitle>Service concerné</CardTitle>
|
<div className="flex items-center gap-3">
|
||||||
<CardDescription>
|
<div className="p-2 bg-blue-500 rounded-lg">
|
||||||
Gérez la liste des services disponibles pour la classification des factures
|
<Building2 className="w-6 h-6 text-white" />
|
||||||
</CardDescription>
|
</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>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-6 pt-6">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-3">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<Input
|
<Input
|
||||||
placeholder="Nouveau service (ex: INFORMATIQUE)"
|
placeholder="Nouveau service (ex: INFORMATIQUE)"
|
||||||
@@ -134,58 +189,80 @@ export default function ListsAdmin() {
|
|||||||
handleCreateDepartment();
|
handleCreateDepartment();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
className="h-11"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleCreateDepartment}
|
onClick={handleCreateDepartment}
|
||||||
disabled={createDepartmentMutation.isPending || !newDepartment.trim()}
|
disabled={createDepartmentMutation.isPending || !newDepartment.trim()}
|
||||||
|
size="lg"
|
||||||
|
className="bg-blue-500 hover:bg-blue-600"
|
||||||
>
|
>
|
||||||
{createDepartmentMutation.isPending ? (
|
{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>
|
<span className="ml-2">Ajouter</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-3">
|
||||||
{departments && departments.length > 0 ? (
|
{departments && departments.length > 0 ? (
|
||||||
departments.map((dept) => (
|
departments.map((dept) => (
|
||||||
<div
|
<div
|
||||||
key={dept.id}
|
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
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => confirmDelete("department", dept.id, dept.name)}
|
onClick={() => confirmDelete("department", dept.id, dept.name)}
|
||||||
disabled={deleteDepartmentMutation.isPending}
|
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>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<p className="text-sm text-muted-foreground text-center py-4">
|
<div className="text-center py-12 bg-muted/30 rounded-lg border-2 border-dashed">
|
||||||
Aucun service configuré. Ajoutez-en un pour commencer.
|
<Building2 className="h-12 w-12 mx-auto text-muted-foreground/50 mb-3" />
|
||||||
</p>
|
<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>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Accounting Allocation List */}
|
{/* Accounting Allocation List */}
|
||||||
<Card>
|
<Card className="border-2 hover:border-primary/50 transition-colors">
|
||||||
<CardHeader>
|
<CardHeader className="bg-gradient-to-r from-green-50 to-emerald-50 dark:from-green-950/20 dark:to-emerald-950/20 border-b">
|
||||||
<CardTitle>Ventilation comptable</CardTitle>
|
<div className="flex items-center gap-3">
|
||||||
<CardDescription>
|
<div className="p-2 bg-green-500 rounded-lg">
|
||||||
Gérez la liste des ventilations comptables disponibles pour la classification des factures
|
<Calculator className="w-6 h-6 text-white" />
|
||||||
</CardDescription>
|
</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>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-6 pt-6">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-3">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<Input
|
<Input
|
||||||
placeholder="Nouvelle ventilation (ex: MAINTENANCE)"
|
placeholder="Nouvelle ventilation (ex: MAINTENANCE)"
|
||||||
@@ -196,43 +273,58 @@ export default function ListsAdmin() {
|
|||||||
handleCreateAllocation();
|
handleCreateAllocation();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
className="h-11"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleCreateAllocation}
|
onClick={handleCreateAllocation}
|
||||||
disabled={createAllocationMutation.isPending || !newAllocation.trim()}
|
disabled={createAllocationMutation.isPending || !newAllocation.trim()}
|
||||||
|
size="lg"
|
||||||
|
className="bg-green-500 hover:bg-green-600"
|
||||||
>
|
>
|
||||||
{createAllocationMutation.isPending ? (
|
{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>
|
<span className="ml-2">Ajouter</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-3">
|
||||||
{allocations && allocations.length > 0 ? (
|
{allocations && allocations.length > 0 ? (
|
||||||
allocations.map((alloc) => (
|
allocations.map((alloc) => (
|
||||||
<div
|
<div
|
||||||
key={alloc.id}
|
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
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => confirmDelete("allocation", alloc.id, alloc.name)}
|
onClick={() => confirmDelete("allocation", alloc.id, alloc.name)}
|
||||||
disabled={deleteAllocationMutation.isPending}
|
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>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<p className="text-sm text-muted-foreground text-center py-4">
|
<div className="text-center py-12 bg-muted/30 rounded-lg border-2 border-dashed">
|
||||||
Aucune ventilation configurée. Ajoutez-en une pour commencer.
|
<Calculator className="h-12 w-12 mx-auto text-muted-foreground/50 mb-3" />
|
||||||
</p>
|
<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>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -243,15 +335,20 @@ export default function ListsAdmin() {
|
|||||||
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>Confirmer la suppression</AlertDialogTitle>
|
<AlertDialogTitle className="text-xl">Confirmer la suppression</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription className="text-base">
|
||||||
Êtes-vous sûr de vouloir supprimer "{itemToDelete?.name}" ?
|
Êtes-vous sûr de vouloir supprimer <span className="font-semibold">"{itemToDelete?.name}"</span> ?
|
||||||
|
<br />
|
||||||
Cette action est irréversible.
|
Cette action est irréversible.
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel>Annuler</AlertDialogCancel>
|
<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
|
Supprimer
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
|
|||||||
10
todo.md
10
todo.md
@@ -395,4 +395,14 @@
|
|||||||
- [x] Améliorer le responsive design pour mobile
|
- [x] Améliorer le responsive design pour mobile
|
||||||
- [x] Ajouter des badges de statut (Actif/Inactif) avec couleurs
|
- [x] Ajouter des badges de statut (Actif/Inactif) avec couleurs
|
||||||
- [x] Tester localement l'interface modernisée
|
- [x] Tester localement l'interface modernisée
|
||||||
|
- [x] Déployer sur le VPS
|
||||||
|
|
||||||
|
## Modernisation des pages Administration et Import
|
||||||
|
- [x] Identifier les pages Administration des listes et Paramètres d'import
|
||||||
|
- [x] Analyser la structure actuelle de ces pages
|
||||||
|
- [x] Moderniser la page Administration des listes avec Tabs, icônes colorées, cards
|
||||||
|
- [x] Moderniser la page Paramètres d'import avec le même style
|
||||||
|
- [x] Appliquer les badges de statut et animations
|
||||||
|
- [x] Améliorer la typographie et les espacements
|
||||||
|
- [x] Tester localement les interfaces modernisées
|
||||||
- [ ] Déployer sur le VPS
|
- [ ] Déployer sur le VPS
|
||||||
|
|||||||
Reference in New Issue
Block a user