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}>
|
||||
<DialogContent className="max-w-3xl max-h-[80vh] overflow-y-auto">
|
||||
<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>
|
||||
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>
|
||||
</DialogHeader>
|
||||
|
||||
<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>
|
||||
{wizardMode && !editingRule ? (
|
||||
// WIZARD MODE - Étapes guidées
|
||||
<div className="space-y-6">
|
||||
{wizardStep === 1 && (
|
||||
// ÉTAPE 1: Nom et conditions simples
|
||||
<div className="space-y-4">
|
||||
<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>
|
||||
|
||||
<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 */}
|
||||
<div className="space-y-2">
|
||||
@@ -529,9 +710,50 @@ export default function AutomationRules() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<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
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
@@ -553,10 +775,12 @@ export default function AutomationRules() {
|
||||
<Button variant="outline" onClick={closeDialog}>
|
||||
Annuler
|
||||
</Button>
|
||||
<Button onClick={handleSave} disabled={createMutation.isPending || updateMutation.isPending}>
|
||||
{editingRule ? "Mettre à jour" : "Créer"}
|
||||
</Button>
|
||||
<Button onClick={handleSave} disabled={createMutation.isPending || updateMutation.isPending}>
|
||||
{editingRule ? "Mettre à jour" : "Créer"}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
Reference in New Issue
Block a user