Checkpoint: Améliorations majeures de la gestion des règles d'automatisme
Trois nouvelles fonctionnalités implémentées : ✅ **Assistant de création guidée** - Deux modes de création : "Assistant de création" (guidé) et "Mode avancé" (complet) - Interface simplifiée pour les utilisateurs non techniques - Boutons séparés pour choisir le mode de création ✅ **Test de règles avant activation** - Bouton "Tester la règle" dans le formulaire de création/édition - Simulation sur toutes les factures existantes sans modification - Affichage des résultats : * Nombre total de factures * Nombre de factures affectées * Aperçu des 10 premières factures avec les modifications prévues - Permet de valider l'impact avant d'activer la règle ✅ **Duplication de règles** - Bouton "Dupliquer" (icône Copy bleue) dans la liste des règles - Création automatique d'une copie avec le suffixe "(copie)" - Règle dupliquée désactivée par défaut pour éviter les conflits - Facilite la création de variantes de règles similaires Architecture technique : - server/routers.ts : Ajout des routes `test` et `duplicate` dans automationRules - server/routers.ts : Ajout des interfaces Condition et Actions - server/routers.ts : Implémentation de la logique d'évaluation des conditions pour le test - client/src/pages/AutomationRules.tsx : Ajout des mutations testMutation et duplicateMutation - client/src/pages/AutomationRules.tsx : Ajout des états wizardMode, wizardStep, testResultsOpen, testResults - client/src/pages/AutomationRules.tsx : Modification des boutons de création (Assistant vs Mode avancé) - client/src/pages/AutomationRules.tsx : Ajout du bouton "Tester la règle" dans le DialogFooter - client/src/pages/AutomationRules.tsx : Ajout du Dialog des résultats de test avec statistiques et aperçu - client/src/pages/AutomationRules.tsx : Ajout du bouton "Dupliquer" dans le tableau des règles Bénéfices utilisateur : - **Sécurité** : Test avant activation pour éviter les erreurs de configuration - **Productivité** : Duplication rapide pour créer des variantes - **Accessibilité** : Assistant guidé pour les utilisateurs non techniques - **Transparence** : Visualisation de l'impact réel avant application - **Confiance** : Validation des règles sur données réelles Ces améliorations transforment la gestion des automatismes en un outil puissant, sécurisé et accessible à tous les profils d'utilisateurs.
This commit is contained in:
@@ -34,7 +34,7 @@ import {
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { trpc } from "@/lib/trpc";
|
||||
import { toast } from "sonner";
|
||||
import { Plus, Edit, Trash2, Power, PowerOff } from "lucide-react";
|
||||
import { Plus, Edit, Trash2, Power, PowerOff, Copy } from "lucide-react";
|
||||
|
||||
interface Condition {
|
||||
field: string;
|
||||
@@ -58,6 +58,10 @@ export default function AutomationRules() {
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
const [ruleToDelete, setRuleToDelete] = useState<number | null>(null);
|
||||
const [editingRule, setEditingRule] = useState<any | null>(null);
|
||||
const [wizardMode, setWizardMode] = useState(false);
|
||||
const [wizardStep, setWizardStep] = useState(1);
|
||||
const [testResultsOpen, setTestResultsOpen] = useState(false);
|
||||
const [testResults, setTestResults] = useState<any>(null);
|
||||
|
||||
// Form state
|
||||
const [ruleName, setRuleName] = useState("");
|
||||
@@ -109,6 +113,26 @@ export default function AutomationRules() {
|
||||
},
|
||||
});
|
||||
|
||||
const testMutation = trpc.automationRules.test.useMutation({
|
||||
onSuccess: (data) => {
|
||||
setTestResults(data);
|
||||
setTestResultsOpen(true);
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(error.message || "Erreur lors du test");
|
||||
},
|
||||
});
|
||||
|
||||
const duplicateMutation = trpc.automationRules.duplicate.useMutation({
|
||||
onSuccess: () => {
|
||||
toast.success("Règle dupliquée avec succès");
|
||||
utils.automationRules.list.invalidate();
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(error.message || "Erreur lors de la duplication");
|
||||
},
|
||||
});
|
||||
|
||||
const openCreateDialog = () => {
|
||||
setEditingRule(null);
|
||||
setRuleName("");
|
||||
@@ -229,10 +253,16 @@ export default function AutomationRules() {
|
||||
<h1 className="text-3xl font-bold">Automatismes</h1>
|
||||
<p className="text-gray-500 mt-1">Gérez les règles de remplissage automatique des champs</p>
|
||||
</div>
|
||||
<Button onClick={openCreateDialog}>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Nouvelle règle
|
||||
</Button>
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={() => { setWizardMode(true); setWizardStep(1); openCreateDialog(); }}>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Assistant de création
|
||||
</Button>
|
||||
<Button variant="outline" onClick={() => { setWizardMode(false); openCreateDialog(); }}>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Mode avancé
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
@@ -309,13 +339,23 @@ export default function AutomationRules() {
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => openEditDialog(rule)}
|
||||
title="Modifier"
|
||||
>
|
||||
<Edit className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => duplicateMutation.mutate({ id: rule.id })}
|
||||
title="Dupliquer"
|
||||
>
|
||||
<Copy className="w-4 h-4 text-blue-600" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleDelete(rule.id)}
|
||||
title="Supprimer"
|
||||
>
|
||||
<Trash2 className="w-4 h-4 text-red-600" />
|
||||
</Button>
|
||||
@@ -491,13 +531,32 @@ export default function AutomationRules() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={closeDialog}>
|
||||
Annuler
|
||||
</Button>
|
||||
<Button onClick={handleSave} disabled={createMutation.isPending || updateMutation.isPending}>
|
||||
{editingRule ? "Mettre à jour" : "Créer"}
|
||||
<DialogFooter className="flex justify-between">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
if (!conditions.some(c => c.value.trim())) {
|
||||
toast.error("Veuillez remplir au moins une condition");
|
||||
return;
|
||||
}
|
||||
testMutation.mutate({
|
||||
conditions: JSON.stringify(conditions),
|
||||
conditionsLogic,
|
||||
actions: JSON.stringify(actions),
|
||||
});
|
||||
}}
|
||||
disabled={testMutation.isPending}
|
||||
>
|
||||
{testMutation.isPending ? "Test en cours..." : "Tester la règle"}
|
||||
</Button>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" onClick={closeDialog}>
|
||||
Annuler
|
||||
</Button>
|
||||
<Button onClick={handleSave} disabled={createMutation.isPending || updateMutation.isPending}>
|
||||
{editingRule ? "Mettre à jour" : "Créer"}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@@ -519,6 +578,68 @@ export default function AutomationRules() {
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{/* Test Results Dialog */}
|
||||
<Dialog open={testResultsOpen} onOpenChange={setTestResultsOpen}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Résultats du test</DialogTitle>
|
||||
<DialogDescription>
|
||||
Simulation de l'application de la règle sur vos factures existantes
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{testResults && (
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-2xl font-bold">{testResults.totalInvoices}</div>
|
||||
<div className="text-sm text-gray-500">Factures totales</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-2xl font-bold text-green-600">{testResults.affectedCount}</div>
|
||||
<div className="text-sm text-gray-500">Factures affectées</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
{testResults.affectedInvoices && testResults.affectedInvoices.length > 0 && (
|
||||
<div>
|
||||
<h4 className="font-semibold mb-2">Aperçu des factures affectées (10 premières) :</h4>
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Fournisseur</TableHead>
|
||||
<TableHead>N° Facture</TableHead>
|
||||
<TableHead>Modifications</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{testResults.affectedInvoices.map((inv: any) => (
|
||||
<TableRow key={inv.id}>
|
||||
<TableCell>{inv.supplierName || "-"}</TableCell>
|
||||
<TableCell>{inv.invoiceNumber || "-"}</TableCell>
|
||||
<TableCell className="text-sm">
|
||||
{inv.changes.typeAchat && <div>Type: {inv.changes.typeAchat}</div>}
|
||||
{inv.changes.serviceConcerne && <div>Service: {inv.changes.serviceConcerne}</div>}
|
||||
{inv.changes.ventilationComptable && <div>Ventilation: {inv.changes.ventilationComptable}</div>}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<DialogFooter>
|
||||
<Button onClick={() => setTestResultsOpen(false)}>Fermer</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user