import { useState } from "react"; import DashboardLayout from "@/components/DashboardLayout"; 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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Badge } from "@/components/ui/badge"; import { trpc } from "@/lib/trpc"; import { toast } from "sonner"; import { Plus, Edit, Trash2, Power, PowerOff } from "lucide-react"; interface Condition { field: string; operator: string; value: string; } interface Actions { typeAchat?: string; serviceConcerne?: string; ventilationComptable?: string; } export default function AutomationRules() { const { data: rules = [], isLoading } = trpc.automationRules.list.useQuery(); const { data: departments } = trpc.departments.getByUser.useQuery(); const { data: allocations } = trpc.accountingAllocations.getByUser.useQuery(); const utils = trpc.useUtils(); const [dialogOpen, setDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [ruleToDelete, setRuleToDelete] = useState(null); const [editingRule, setEditingRule] = useState(null); // Form state const [ruleName, setRuleName] = useState(""); const [conditionsLogic, setConditionsLogic] = useState<"AND" | "OR">("AND"); const [conditions, setConditions] = useState([{ field: "supplierName", operator: "contains", value: "" }]); const [actions, setActions] = useState({}); const createMutation = trpc.automationRules.create.useMutation({ onSuccess: () => { toast.success("Règle créée avec succès"); utils.automationRules.list.invalidate(); closeDialog(); }, onError: (error) => { toast.error(error.message || "Erreur lors de la création"); }, }); const updateMutation = trpc.automationRules.update.useMutation({ onSuccess: () => { toast.success("Règle mise à jour"); utils.automationRules.list.invalidate(); closeDialog(); }, onError: (error) => { toast.error(error.message || "Erreur lors de la mise à jour"); }, }); const deleteMutation = trpc.automationRules.delete.useMutation({ onSuccess: () => { toast.success("Règle supprimée"); utils.automationRules.list.invalidate(); setDeleteDialogOpen(false); setRuleToDelete(null); }, onError: (error) => { toast.error(error.message || "Erreur lors de la suppression"); }, }); const toggleActiveMutation = trpc.automationRules.update.useMutation({ onSuccess: () => { toast.success("Statut mis à jour"); utils.automationRules.list.invalidate(); }, onError: (error) => { toast.error(error.message || "Erreur lors de la mise à jour"); }, }); const openCreateDialog = () => { setEditingRule(null); setRuleName(""); setConditionsLogic("AND"); setConditions([{ field: "supplierName", operator: "contains", value: "" }]); setActions({}); setDialogOpen(true); }; const openEditDialog = (rule: any) => { setEditingRule(rule); setRuleName(rule.name); setConditionsLogic(rule.conditionsLogic || "AND"); try { setConditions(JSON.parse(rule.conditions)); } catch { setConditions([{ field: "supplierName", operator: "contains", value: "" }]); } try { setActions(JSON.parse(rule.actions)); } catch { setActions({}); } setDialogOpen(true); }; const closeDialog = () => { setDialogOpen(false); setEditingRule(null); }; const handleSave = () => { if (!ruleName.trim()) { toast.error("Le nom de la règle est obligatoire"); return; } const conditionsJSON = JSON.stringify(conditions); const actionsJSON = JSON.stringify(actions); if (editingRule) { updateMutation.mutate({ id: editingRule.id, name: ruleName, conditionsLogic, conditions: conditionsJSON, actions: actionsJSON, }); } else { createMutation.mutate({ name: ruleName, conditionsLogic, conditions: conditionsJSON, actions: actionsJSON, isActive: 1, priority: rules.length, }); } }; const handleDelete = (id: number) => { setRuleToDelete(id); setDeleteDialogOpen(true); }; const confirmDelete = () => { if (ruleToDelete) { deleteMutation.mutate({ id: ruleToDelete }); } }; const toggleActive = (rule: any) => { toggleActiveMutation.mutate({ id: rule.id, isActive: rule.isActive === 1 ? 0 : 1, }); }; const addCondition = () => { setConditions([...conditions, { field: "supplierName", operator: "contains", value: "" }]); }; const removeCondition = (index: number) => { setConditions(conditions.filter((_, i) => i !== index)); }; const updateCondition = (index: number, key: keyof Condition, value: string) => { const newConditions = [...conditions]; newConditions[index][key] = value; setConditions(newConditions); }; const fieldOptions = [ { value: "supplierName", label: "Fournisseur" }, { value: "invoiceNumber", label: "N° Facture" }, { value: "totalAmount", label: "Montant" }, { value: "invoiceDate", label: "Date facture" }, { value: "orderNumber", label: "N° Commande" }, { value: "deliveryNoteNumber", label: "N° Bon de livraison" }, ]; const operatorOptions = [ { value: "contains", label: "contient" }, { value: "equals", label: "est égal à" }, { value: "startsWith", label: "commence par" }, { value: "endsWith", label: "finit par" }, { value: ">", label: "supérieur à" }, { value: "<", label: "inférieur à" }, { value: ">=", label: "supérieur ou égal à" }, { value: "<=", label: "inférieur ou égal à" }, ]; return (

Automatismes

Gérez les règles de remplissage automatique des champs

Règles d'automatisme Les règles sont appliquées dans l'ordre de priorité lors de l'import des factures {isLoading ? (
Chargement...
) : rules.length === 0 ? (
Aucune règle d'automatisme configurée
) : ( Nom Conditions Actions Statut Actions {rules.map((rule) => { let conditionsDisplay = ""; let actionsDisplay = ""; try { const conds = JSON.parse(rule.conditions); conditionsDisplay = conds.map((c: Condition) => `${fieldOptions.find(f => f.value === c.field)?.label || c.field} ${operatorOptions.find(o => o.value === c.operator)?.label || c.operator} "${c.value}"` ).join(` ${rule.conditionsLogic} `); } catch {} try { const acts = JSON.parse(rule.actions); const actionsArr = []; if (acts.typeAchat) actionsArr.push(`Type: ${acts.typeAchat}`); if (acts.serviceConcerne) actionsArr.push(`Service: ${acts.serviceConcerne}`); if (acts.ventilationComptable) actionsArr.push(`Ventilation: ${acts.ventilationComptable}`); actionsDisplay = actionsArr.join(", "); } catch {} return ( {rule.name} {conditionsDisplay} {actionsDisplay} {rule.isActive === 1 ? ( Actif ) : ( Inactif )}
); })}
)}
{/* Create/Edit Dialog */} {editingRule ? "Modifier la règle" : "Nouvelle règle"} Définissez les conditions (SI) et les actions (ALORS) pour le remplissage automatique
{/* Rule Name */}
setRuleName(e.target.value)} placeholder="Ex: Factures Microsoft CAPEX" />
{/* Conditions */}
{conditions.map((condition, index) => (
updateCondition(index, "value", e.target.value)} placeholder="Valeur" className="flex-1" />
))}
{/* Actions */}
{/* Delete Confirmation Dialog */} Confirmer la suppression Êtes-vous sûr de vouloir supprimer cette règle ? Cette action est irréversible. Annuler Supprimer
); }