ultisuite-client/components/gmail/settings/automation/rule-simulator-panel.tsx
R3D347HR4Y 2a0958b70d
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
feat: update agenda references to use ULTICAL_APP_NAME and enhance AI usage sections
- Replaced hardcoded "Agenda" labels with dynamic ULTICAL_APP_NAME in various components for consistency.
- Introduced new AiUsageSection and CompteAiUsageSection components to track AI usage and costs.
- Updated settings and metadata to reflect changes in AI cost policies and usage limits.
- Enhanced user interface elements for better accessibility and user experience across admin settings.
2026-06-16 10:46:31 +02:00

219 lines
9.4 KiB
TypeScript

'use client'
import { useMemo, useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Play } from 'lucide-react'
import { useSimulateMailRule } from '@/lib/api/hooks/use-mail-automation-queries'
import type { RuleEditorState, RuleSimulationResult } from '@/lib/mail-automation/types'
import {
DEFAULT_SIMULATION_CALENDAR_EVENT,
DEFAULT_SIMULATION_CONTACT,
DEFAULT_SIMULATION_DRIVE_FILE,
DEFAULT_SIMULATION_MESSAGE,
workflowToApiPayload,
} from '@/lib/mail-automation/defaults'
import { inferDomainsFromTriggers } from '@/lib/mail-automation/domains'
import { AUTOMATION_DOMAIN_LABELS } from '@/lib/mail-automation/domains'
interface RuleSimulatorPanelProps {
state: RuleEditorState
ruleId?: string
}
export function RuleSimulatorPanel({ state, ruleId }: RuleSimulatorPanelProps) {
const simulate = useSimulateMailRule()
const [message, setMessage] = useState(DEFAULT_SIMULATION_MESSAGE)
const [driveFile, setDriveFile] = useState(DEFAULT_SIMULATION_DRIVE_FILE)
const [contact, setContact] = useState(DEFAULT_SIMULATION_CONTACT)
const [calendarEvent, setCalendarEvent] = useState(DEFAULT_SIMULATION_CALENDAR_EVENT)
const [result, setResult] = useState<RuleSimulationResult | null>(null)
const domains = useMemo(
() => inferDomainsFromTriggers(state.workflow.triggers),
[state.workflow.triggers]
)
async function runSimulation() {
const payload = workflowToApiPayload(state)
const res = await simulate.mutateAsync({
message,
...(ruleId
? { rule_id: ruleId }
: {
rule: {
conditions: payload.conditions,
actions: payload.actions,
workflow: payload.workflow,
},
}),
})
setResult(res)
}
return (
<div className="space-y-3 rounded-lg border border-border bg-muted/10 p-3">
<p className="text-xs font-medium">
Tester avec un événement exemple
{domains.length > 0 ? (
<span className="ml-1 font-normal text-muted-foreground">
({domains.map((d) => AUTOMATION_DOMAIN_LABELS[d]).join(', ')})
</span>
) : null}
</p>
{domains.includes('mail') ? (
<div className="space-y-2 rounded-md border border-border/50 p-2">
<p className="text-[10px] font-medium uppercase tracking-wide text-muted-foreground">Mail</p>
<div className="grid gap-2 sm:grid-cols-2">
<div>
<Label className="text-[10px]">De</Label>
<Input className="h-8 text-xs" value={message.from} onChange={(e) => setMessage({ ...message, from: e.target.value })} />
</div>
<div>
<Label className="text-[10px]">Sujet</Label>
<Input className="h-8 text-xs" value={message.subject} onChange={(e) => setMessage({ ...message, subject: e.target.value })} />
</div>
</div>
<div>
<Label className="text-[10px]">Corps</Label>
<textarea
className="mt-1 min-h-16 w-full rounded-md border border-input bg-background px-2 py-1.5 text-xs"
value={message.body_text}
onChange={(e) => setMessage({ ...message, body_text: e.target.value })}
/>
</div>
</div>
) : null}
{domains.includes('drive') ? (
<div className="space-y-2 rounded-md border border-border/50 p-2">
<p className="text-[10px] font-medium uppercase tracking-wide text-muted-foreground">Drive</p>
<div className="grid gap-2 sm:grid-cols-2">
<div>
<Label className="text-[10px]">Nom fichier</Label>
<Input className="h-8 text-xs" value={driveFile.file_name} onChange={(e) => setDriveFile({ ...driveFile, file_name: e.target.value })} />
</div>
<div>
<Label className="text-[10px]">Chemin</Label>
<Input className="h-8 text-xs" value={driveFile.file_path} onChange={(e) => setDriveFile({ ...driveFile, file_path: e.target.value })} />
</div>
<div>
<Label className="text-[10px]">Type MIME</Label>
<Input className="h-8 text-xs" value={driveFile.mime_type} onChange={(e) => setDriveFile({ ...driveFile, mime_type: e.target.value })} />
</div>
<div>
<Label className="text-[10px]">Taille (octets)</Label>
<Input type="number" className="h-8 text-xs" value={driveFile.file_size} onChange={(e) => setDriveFile({ ...driveFile, file_size: Number(e.target.value) || 0 })} />
</div>
</div>
</div>
) : null}
{domains.includes('contacts') ? (
<div className="space-y-2 rounded-md border border-border/50 p-2">
<p className="text-[10px] font-medium uppercase tracking-wide text-muted-foreground">Contacts</p>
<div className="grid gap-2 sm:grid-cols-2">
<div>
<Label className="text-[10px]">Nom</Label>
<Input className="h-8 text-xs" value={contact.name} onChange={(e) => setContact({ ...contact, name: e.target.value })} />
</div>
<div>
<Label className="text-[10px]">E-mail</Label>
<Input className="h-8 text-xs" value={contact.email} onChange={(e) => setContact({ ...contact, email: e.target.value })} />
</div>
<div>
<Label className="text-[10px]">Téléphone</Label>
<Input className="h-8 text-xs" value={contact.phone} onChange={(e) => setContact({ ...contact, phone: e.target.value })} />
</div>
<div>
<Label className="text-[10px]">Organisation</Label>
<Input className="h-8 text-xs" value={contact.org} onChange={(e) => setContact({ ...contact, org: e.target.value })} />
</div>
</div>
</div>
) : null}
{domains.includes('agenda') ? (
<div className="space-y-2 rounded-md border border-border/50 p-2">
<p className="text-[10px] font-medium uppercase tracking-wide text-muted-foreground">
{AUTOMATION_DOMAIN_LABELS.agenda}
</p>
<div className="grid gap-2 sm:grid-cols-2">
<div>
<Label className="text-[10px]">Titre</Label>
<Input className="h-8 text-xs" value={calendarEvent.title} onChange={(e) => setCalendarEvent({ ...calendarEvent, title: e.target.value })} />
</div>
<div>
<Label className="text-[10px]">Lieu</Label>
<Input className="h-8 text-xs" value={calendarEvent.location} onChange={(e) => setCalendarEvent({ ...calendarEvent, location: e.target.value })} />
</div>
<div>
<Label className="text-[10px]">Organisateur</Label>
<Input className="h-8 text-xs" value={calendarEvent.organizer} onChange={(e) => setCalendarEvent({ ...calendarEvent, organizer: e.target.value })} />
</div>
<div>
<Label className="text-[10px]">Participant</Label>
<Input className="h-8 text-xs" value={calendarEvent.attendee} onChange={(e) => setCalendarEvent({ ...calendarEvent, attendee: e.target.value })} />
</div>
<div>
<Label className="text-[10px]">Calendrier</Label>
<Input className="h-8 text-xs" value={calendarEvent.calendar_id} onChange={(e) => setCalendarEvent({ ...calendarEvent, calendar_id: e.target.value })} />
</div>
</div>
</div>
) : null}
{domains.includes('drive') || domains.includes('contacts') || domains.includes('agenda') ? (
<p className="text-[10px] text-muted-foreground italic">
La simulation API reste orientée mail ; l&apos;exécution réelle Drive, contacts et agenda est active
côté serveur à la réception des événements.
</p>
) : null}
<Button type="button" size="sm" disabled={simulate.isPending} onClick={runSimulation}>
<Play className="mr-1 size-3.5" />
Simuler
</Button>
{result ? (
<div className="space-y-2 rounded-md border border-border/60 bg-background p-2 text-xs">
<p>
Correspondance :{' '}
<span className={result.matched ? 'text-emerald-600' : 'text-muted-foreground'}>
{result.matched ? 'oui' : 'non'}
</span>
</p>
{result.steps?.length ? (
<div>
<p className="font-medium">Parcours</p>
<ol className="mt-1 list-decimal pl-4 text-muted-foreground">
{result.steps.map((s, i) => (
<li key={i}>
{s.node_type}
{s.handle ? `${s.handle}` : ''}
</li>
))}
</ol>
</div>
) : null}
{result.actions?.length ? (
<div>
<p className="font-medium">Actions</p>
<ul className="mt-1 space-y-0.5 text-muted-foreground">
{result.actions.map((a, i) => (
<li key={i}>
{a.type}
{a.value ? `: ${a.value}` : ''} {a.ok ? '✓' : `${a.error ?? ''}`}
</li>
))}
</ul>
</div>
) : null}
</div>
) : null}
</div>
)
}