Checkpoint: Implémentation de l'assistant de création guidée avec interface différenciée
✅ **Assistant de création guidée en 3 étapes**
L'interface de l'assistant est maintenant complètement différente du mode avancé, offrant une expérience simplifiée et guidée pour les utilisateurs non techniques.
**Étape 1 - Conditions simples**
- Nom de la règle
- Question guidée : "Quand appliquer cette règle ?"
- Interface simplifiée : "Le fournisseur [contient/est égal à/commence par] [valeur]"
- Validation avant passage à l'étape suivante
**Étape 2 - Sélection des actions**
- Question guidée : "Que voulez-vous remplir automatiquement ?"
- Sélection simple des 3 champs : Type d'achat, Service concerné, Ventilation comptable
- Option "Ne pas modifier" pour chaque champ
**Étape 3 - Révision et validation**
- Récapitulatif visuel dans une Card
- Affichage clair du nom, des conditions et des actions
- Bouton "Créer la règle" pour finaliser
**Navigation intuitive**
- Boutons "Annuler" / "Précédent" / "Suivant"
- Badge indiquant l'étape courante (Étape X/3)
- Validation à chaque étape avant progression
- Messages d'erreur clairs si champs obligatoires manquants
**Différences Assistant vs Mode avancé**
- **Assistant** : Interface simplifiée, 1 condition sur le fournisseur uniquement, navigation par étapes, questions en français naturel
- **Mode avancé** : Formulaire complet, conditions multiples avec opérateurs complexes, logique AND/OR, bouton "Tester la règle", tout sur une seule page
Architecture technique :
- client/src/pages/AutomationRules.tsx : Ajout de la structure conditionnelle wizardMode
- client/src/pages/AutomationRules.tsx : Implémentation des 3 étapes avec wizardStep
- client/src/pages/AutomationRules.tsx : Navigation avec validation entre les étapes
- client/src/pages/AutomationRules.tsx : Séparation complète de l'UI wizard vs avancé
- client/src/pages/AutomationRules.tsx : Utilisation de Card pour le récapitulatif
Bénéfices utilisateur :
- **Accessibilité** : Interface guidée pour les non-techniciens
- **Simplicité** : Une seule condition à la fois, focus sur l'essentiel
- **Clarté** : Questions en langage naturel, pas de jargon technique
- **Sécurité** : Validation à chaque étape, récapitulatif avant création
- **Flexibilité** : Choix entre assistant simple et mode avancé selon le besoin
This commit is contained in:
@@ -374,23 +374,204 @@ export default function AutomationRules() {
|
|||||||
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||||
<DialogContent className="max-w-3xl max-h-[80vh] overflow-y-auto">
|
<DialogContent className="max-w-3xl max-h-[80vh] overflow-y-auto">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{editingRule ? "Modifier la règle" : "Nouvelle règle"}</DialogTitle>
|
<DialogTitle>
|
||||||
|
{wizardMode && !editingRule ? (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span>Assistant de création</span>
|
||||||
|
<Badge variant="outline">Étape {wizardStep}/3</Badge>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
editingRule ? "Modifier la règle" : "Nouvelle règle"
|
||||||
|
)}
|
||||||
|
</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Définissez les conditions (SI) et les actions (ALORS) pour le remplissage automatique
|
{wizardMode && !editingRule ? (
|
||||||
|
wizardStep === 1 ? "Définissez les conditions pour déclencher la règle" :
|
||||||
|
wizardStep === 2 ? "Choisissez les champs à remplir automatiquement" :
|
||||||
|
"Vérifiez et validez votre règle"
|
||||||
|
) : (
|
||||||
|
"Définissez les conditions (SI) et les actions (ALORS) pour le remplissage automatique"
|
||||||
|
)}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Rule Name */}
|
{wizardMode && !editingRule ? (
|
||||||
<div className="space-y-2">
|
// WIZARD MODE - Étapes guidées
|
||||||
<Label htmlFor="ruleName">Nom de la règle</Label>
|
<div className="space-y-6">
|
||||||
<Input
|
{wizardStep === 1 && (
|
||||||
id="ruleName"
|
// ÉTAPE 1: Nom et conditions simples
|
||||||
value={ruleName}
|
<div className="space-y-4">
|
||||||
onChange={(e) => setRuleName(e.target.value)}
|
<div className="space-y-2">
|
||||||
placeholder="Ex: Factures Microsoft CAPEX"
|
<Label htmlFor="ruleName">Nom de la règle</Label>
|
||||||
/>
|
<Input
|
||||||
</div>
|
id="ruleName"
|
||||||
|
value={ruleName}
|
||||||
|
onChange={(e) => setRuleName(e.target.value)}
|
||||||
|
placeholder="Ex: Factures Microsoft CAPEX"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<h3 className="font-semibold">Quand appliquer cette règle ?</h3>
|
||||||
|
<p className="text-sm text-gray-500">Définissez les conditions pour déclencher la règle</p>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Le fournisseur</Label>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Select
|
||||||
|
value={conditions[0]?.operator || "contains"}
|
||||||
|
onValueChange={(value) => updateCondition(0, "operator", value)}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-[160px]">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="contains">contient</SelectItem>
|
||||||
|
<SelectItem value="equals">est égal à</SelectItem>
|
||||||
|
<SelectItem value="startsWith">commence par</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Input
|
||||||
|
value={conditions[0]?.value || ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (conditions.length === 0) {
|
||||||
|
setConditions([{ field: "supplierName", operator: "contains", value: e.target.value }]);
|
||||||
|
} else {
|
||||||
|
updateCondition(0, "value", e.target.value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
placeholder="Ex: Microsoft, Amazon, etc."
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{wizardStep === 2 && (
|
||||||
|
// ÉTAPE 2: Actions
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold mb-1">Que voulez-vous remplir automatiquement ?</h3>
|
||||||
|
<p className="text-sm text-gray-500">Sélectionnez les champs à compléter</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="typeAchat">Type d'achat</Label>
|
||||||
|
<Select
|
||||||
|
value={actions.typeAchat || "__NONE__"}
|
||||||
|
onValueChange={(value) => setActions({ ...actions, typeAchat: value === "__NONE__" ? undefined : value })}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Ne pas modifier" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="__NONE__">Ne pas modifier</SelectItem>
|
||||||
|
<SelectItem value="CAPEX">CAPEX</SelectItem>
|
||||||
|
<SelectItem value="OPEX">OPEX</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="serviceConcerne">Service concerné</Label>
|
||||||
|
<Select
|
||||||
|
value={actions.serviceConcerne || "__NONE__"}
|
||||||
|
onValueChange={(value) => setActions({ ...actions, serviceConcerne: value === "__NONE__" ? undefined : value })}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Ne pas modifier" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="__NONE__">Ne pas modifier</SelectItem>
|
||||||
|
{departments?.map((dept) => (
|
||||||
|
<SelectItem key={dept.id} value={dept.name}>
|
||||||
|
{dept.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="ventilationComptable">Ventilation comptable</Label>
|
||||||
|
<Select
|
||||||
|
value={actions.ventilationComptable || "__NONE__"}
|
||||||
|
onValueChange={(value) => setActions({ ...actions, ventilationComptable: value === "__NONE__" ? undefined : value })}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Ne pas modifier" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="__NONE__">Ne pas modifier</SelectItem>
|
||||||
|
{allocations?.map((alloc) => (
|
||||||
|
<SelectItem key={alloc.id} value={alloc.name}>
|
||||||
|
{alloc.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{wizardStep === 3 && (
|
||||||
|
// ÉTAPE 3: Révision
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold mb-1">Récapitulatif de la règle</h3>
|
||||||
|
<p className="text-sm text-gray-500">Vérifiez les informations avant de créer</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardContent className="pt-6 space-y-4">
|
||||||
|
<div>
|
||||||
|
<div className="text-sm font-semibold text-gray-500">Nom</div>
|
||||||
|
<div className="text-lg">{ruleName || "(non défini)"}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="text-sm font-semibold text-gray-500">Condition</div>
|
||||||
|
<div className="text-lg">
|
||||||
|
Le fournisseur {conditions[0]?.operator === "contains" ? "contient" : conditions[0]?.operator === "equals" ? "est égal à" : "commence par"} "{conditions[0]?.value || "(vide)"}"
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="text-sm font-semibold text-gray-500">Actions</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
{actions.typeAchat && <div>• Type d'achat : {actions.typeAchat}</div>}
|
||||||
|
{actions.serviceConcerne && <div>• Service : {actions.serviceConcerne}</div>}
|
||||||
|
{actions.ventilationComptable && <div>• Ventilation : {actions.ventilationComptable}</div>}
|
||||||
|
{!actions.typeAchat && !actions.serviceConcerne && !actions.ventilationComptable && (
|
||||||
|
<div className="text-gray-500 italic">Aucune action définie</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
// MODE AVANCÉ - Formulaire complet
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* Rule Name */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="ruleName">Nom de la règle</Label>
|
||||||
|
<Input
|
||||||
|
id="ruleName"
|
||||||
|
value={ruleName}
|
||||||
|
onChange={(e) => setRuleName(e.target.value)}
|
||||||
|
placeholder="Ex: Factures Microsoft CAPEX"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Conditions */}
|
{/* Conditions */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -529,9 +710,50 @@ export default function AutomationRules() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogFooter className="flex justify-between">
|
<DialogFooter className="flex justify-between">
|
||||||
|
{wizardMode && !editingRule ? (
|
||||||
|
// Navigation wizard
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
if (wizardStep > 1) {
|
||||||
|
setWizardStep(wizardStep - 1);
|
||||||
|
} else {
|
||||||
|
closeDialog();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{wizardStep === 1 ? "Annuler" : "Précédent"}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (wizardStep < 3) {
|
||||||
|
if (wizardStep === 1 && !ruleName.trim()) {
|
||||||
|
toast.error("Le nom de la règle est obligatoire");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (wizardStep === 1 && !conditions[0]?.value?.trim()) {
|
||||||
|
toast.error("Veuillez définir au moins une condition");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setWizardStep(wizardStep + 1);
|
||||||
|
} else {
|
||||||
|
handleSave();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={createMutation.isPending}
|
||||||
|
>
|
||||||
|
{wizardStep === 3 ? "Créer la règle" : "Suivant"}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
// Navigation mode avancé
|
||||||
|
<>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -553,10 +775,12 @@ export default function AutomationRules() {
|
|||||||
<Button variant="outline" onClick={closeDialog}>
|
<Button variant="outline" onClick={closeDialog}>
|
||||||
Annuler
|
Annuler
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleSave} disabled={createMutation.isPending || updateMutation.isPending}>
|
<Button onClick={handleSave} disabled={createMutation.isPending || updateMutation.isPending}>
|
||||||
{editingRule ? "Mettre à jour" : "Créer"}
|
{editingRule ? "Mettre à jour" : "Créer"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
8
todo.md
8
todo.md
@@ -325,3 +325,11 @@
|
|||||||
- [x] Afficher les résultats du test (nombre de factures affectées)
|
- [x] Afficher les résultats du test (nombre de factures affectées)
|
||||||
- [x] Ajouter le bouton "Dupliquer" dans la liste des règles
|
- [x] Ajouter le bouton "Dupliquer" dans la liste des règles
|
||||||
- [x] Tester toutes les nouvelles fonctionnalités
|
- [x] Tester toutes les nouvelles fonctionnalités
|
||||||
|
|
||||||
|
## Assistant de création guidée (interface différenciée)
|
||||||
|
- [x] Créer l'interface de l'assistant avec navigation par étapes (1/2/3)
|
||||||
|
- [x] Implémenter l'étape 1 : Questions simples pour les conditions
|
||||||
|
- [x] Implémenter l'étape 2 : Sélection des actions à appliquer
|
||||||
|
- [x] Implémenter l'étape 3 : Récapitulatif et validation
|
||||||
|
- [x] Ajouter la navigation Précédent/Suivant entre les étapes
|
||||||
|
- [x] Tester le parcours complet de l'assistant
|
||||||
|
|||||||
Reference in New Issue
Block a user