feat(tickets): suppression d'un ticket (backend + frontend)
Ajoute la suppression complète d'un ticket (#6). Backend Rust : - port IssueStore::delete (NotFound si absent) + event IssueDeleted{freed_sprint} - FsIssueStore::delete : supprime .ideai/tickets/<N>/ et l'index sous lock - use case DeleteIssue + adaptations des tests sprint/ticket_assistant au port - commande Tauri ticket_delete + câblage events/state/lib Frontend : - gateway delete (ports, adapter ticket + mock, domain) - useTicketDetail : retrait de la liste et fermeture du détail via event issueDeleted - intégration TicketDetail + tests Tests verts : application/issue_usecases (6), infrastructure/issue_store (7), app-tauri --lib (56), frontend vitest (503), npm run build (exit 0). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -104,6 +104,14 @@ export function TicketDetail({
|
||||
const [copied, setCopied] = useState(false);
|
||||
// Confirmation shown when the user tries to close with unsaved changes.
|
||||
const [confirmClose, setConfirmClose] = useState(false);
|
||||
// Confirmation shown before deleting the ticket (#6).
|
||||
const [confirmDelete, setConfirmDelete] = useState(false);
|
||||
|
||||
// The ticket was deleted (by this view or another actor): close the surface.
|
||||
// The removal from lists flows from the same `issueDeleted` event (#6).
|
||||
useEffect(() => {
|
||||
if (vm.deleted) onClose();
|
||||
}, [vm.deleted, onClose]);
|
||||
|
||||
const reloadCount = vm.reloadCount;
|
||||
const seededReload = useRef(-1);
|
||||
@ -205,6 +213,16 @@ export function TicketDetail({
|
||||
>
|
||||
{copied ? "Copied" : "Delegation context"}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
disabled={!t || vm.busy}
|
||||
onClick={() => setConfirmDelete(true)}
|
||||
className="text-danger hover:text-danger"
|
||||
title="Supprimer définitivement ce ticket"
|
||||
>
|
||||
Supprimer
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" onClick={requestClose}>
|
||||
Close
|
||||
</Button>
|
||||
@ -559,6 +577,52 @@ export function TicketDetail({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── Delete confirmation (#6) ── */}
|
||||
{confirmDelete && (
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="confirmer la suppression"
|
||||
className="fixed inset-0 z-[60] flex items-center justify-center bg-black/55 p-4"
|
||||
>
|
||||
<div className="w-full max-w-sm rounded-lg border border-border bg-surface p-5 shadow-xl">
|
||||
<h4 className="text-sm font-semibold text-content">
|
||||
Supprimer le ticket
|
||||
</h4>
|
||||
<p className="mt-2 text-sm text-muted">
|
||||
Supprimer définitivement le ticket {ticketRef} ? Cette
|
||||
action est irréversible.
|
||||
</p>
|
||||
<div className="mt-4 flex flex-wrap items-center justify-end gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
aria-label="cancel delete"
|
||||
disabled={vm.busy}
|
||||
onClick={() => setConfirmDelete(false)}
|
||||
>
|
||||
Annuler
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="danger"
|
||||
aria-label="confirm delete"
|
||||
loading={vm.busy}
|
||||
disabled={vm.busy}
|
||||
onClick={async () => {
|
||||
const ok = await vm.remove();
|
||||
// On success the surface closes via the `issueDeleted` event
|
||||
// (the `deleted` flag → onClose effect); just drop the dialog.
|
||||
if (ok) setConfirmDelete(false);
|
||||
}}
|
||||
>
|
||||
Supprimer
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user