veylant/web/src/pages/PoliciesPage.tsx
2026-02-23 13:35:04 +01:00

209 lines
6.8 KiB
TypeScript

import { useState } from "react";
import { Plus, Pencil, Trash2, Download } from "lucide-react";
import {
useListPolicies,
useCreatePolicy,
useUpdatePolicy,
useDeletePolicy,
useSeedTemplate,
type PolicyPayload,
} from "@/api/policies";
import { PolicyForm } from "@/components/PolicyForm";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Skeleton } from "@/components/ui/skeleton";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import type { RoutingRule } from "@/types/api";
const TEMPLATES = ["hr", "finance", "engineering", "catchall"];
export function PoliciesPage() {
const [formOpen, setFormOpen] = useState(false);
const [editingRule, setEditingRule] = useState<RoutingRule | undefined>();
const { data: policies, isLoading } = useListPolicies();
const createMutation = useCreatePolicy();
const updateMutation = useUpdatePolicy();
const deleteMutation = useDeletePolicy();
const seedMutation = useSeedTemplate();
const handleSubmit = async (payload: PolicyPayload) => {
if (editingRule) {
await updateMutation.mutateAsync({ id: editingRule.id, payload });
} else {
await createMutation.mutateAsync(payload);
}
setFormOpen(false);
setEditingRule(undefined);
};
const handleEdit = (rule: RoutingRule) => {
setEditingRule(rule);
setFormOpen(true);
};
const handleDelete = async (id: string) => {
if (confirm("Supprimer cette politique ?")) {
await deleteMutation.mutateAsync(id);
}
};
const handleOpenCreate = () => {
setEditingRule(undefined);
setFormOpen(true);
};
const isPending =
createMutation.isPending || updateMutation.isPending;
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<h2 className="text-xl font-bold">Politiques de routage</h2>
<p className="text-sm text-muted-foreground">
Gérez les règles de routage intelligentes par tenant
</p>
</div>
<div className="flex gap-2">
{/* Seed template */}
<Select
onValueChange={(template) => seedMutation.mutate(template)}
value=""
>
<SelectTrigger className="w-44">
<Download className="h-4 w-4 mr-2" />
<SelectValue placeholder="Importer template" />
</SelectTrigger>
<SelectContent>
{TEMPLATES.map((t) => (
<SelectItem key={t} value={t} className="capitalize">
{t}
</SelectItem>
))}
</SelectContent>
</Select>
<Button onClick={handleOpenCreate}>
<Plus className="h-4 w-4 mr-2" />
Nouvelle politique
</Button>
</div>
</div>
{isLoading ? (
<div className="space-y-2">
{Array.from({ length: 4 }).map((_, i) => (
<Skeleton key={i} className="h-14 w-full" />
))}
</div>
) : !policies || policies.length === 0 ? (
<div className="flex flex-col items-center justify-center h-64 text-center border rounded-lg">
<p className="text-muted-foreground font-medium">Aucune politique configurée</p>
<p className="text-sm text-muted-foreground mt-1">
Créez votre première politique ou importez un template.
</p>
<Button onClick={handleOpenCreate} className="mt-4">
<Plus className="h-4 w-4 mr-2" /> Créer une politique
</Button>
</div>
) : (
<div className="rounded-lg border">
<Table>
<TableHeader>
<TableRow>
<TableHead>Nom</TableHead>
<TableHead>Priorité</TableHead>
<TableHead>Statut</TableHead>
<TableHead>Conditions</TableHead>
<TableHead>Action</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{policies.map((rule) => (
<TableRow key={rule.id}>
<TableCell>
<div>
<p className="font-medium text-sm">{rule.name}</p>
{rule.description && (
<p className="text-xs text-muted-foreground truncate max-w-xs">
{rule.description}
</p>
)}
</div>
</TableCell>
<TableCell>
<Badge variant="outline">{rule.priority}</Badge>
</TableCell>
<TableCell>
<Badge variant={rule.is_enabled ? "success" : "secondary"}>
{rule.is_enabled ? "Actif" : "Désactivé"}
</Badge>
</TableCell>
<TableCell>
<span className="text-sm text-muted-foreground">
{rule.conditions.length} condition{rule.conditions.length !== 1 ? "s" : ""}
</span>
</TableCell>
<TableCell>
<span className="text-sm font-mono">
{rule.action.provider}/{rule.action.model}
</span>
</TableCell>
<TableCell className="text-right">
<div className="flex justify-end gap-2">
<Button
variant="ghost"
size="icon"
onClick={() => handleEdit(rule)}
title="Modifier"
>
<Pencil className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => handleDelete(rule.id)}
title="Supprimer"
disabled={deleteMutation.isPending}
>
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
<PolicyForm
open={formOpen}
onOpenChange={(open) => {
setFormOpen(open);
if (!open) setEditingRule(undefined);
}}
rule={editingRule}
onSubmit={handleSubmit}
isPending={isPending}
/>
</div>
);
}