fix(estimates): add cancel confirmation and permissions

This commit is contained in:
Schubert Ferenc 2026-07-05 12:10:09 +02:00
parent 663bdc41b1
commit 31bdc1047d
2 changed files with 21 additions and 6 deletions

View file

@ -183,6 +183,7 @@ export default function RepairEstimatesSection({
const [saving, setSaving] = useState(false);
const [pendingId, setPendingId] = useState<number | null>(null);
const [deleteTarget, setDeleteTarget] = useState<RepairEstimate | null>(null);
const [cancelTarget, setCancelTarget] = useState<RepairEstimate | null>(null);
const [inventoryDialogOpen, setInventoryDialogOpen] = useState(false);
const [inventoryItems, setInventoryItems] = useState<InventoryItem[]>([]);
const [inventoryCategories, setInventoryCategories] = useState<InventoryCategory[]>([]);
@ -372,11 +373,13 @@ export default function RepairEstimatesSection({
}
}
async function cancelEstimate(estimate: RepairEstimate) {
setPendingId(estimate.id);
async function cancelEstimate() {
if (!cancelTarget) return;
setPendingId(cancelTarget.id);
try {
await api.post<RepairEstimate>(`/repairs/${repairId}/estimates/${estimate.id}/cancel`);
await api.post<RepairEstimate>(`/repairs/${repairId}/estimates/${cancelTarget.id}/cancel`);
await loadEstimates();
setCancelTarget(null);
showToast({ type: "success", title: "Kostenvoranschlag storniert" });
} catch (err) {
showToast({ type: "error", title: "Stornierung fehlgeschlagen", description: getErrorMessage(err) });
@ -472,7 +475,7 @@ export default function RepairEstimatesSection({
<div className="mt-4 flex flex-wrap gap-2">
{canUpdate && ["draft", "sent"].includes(estimate.status) && <Button type="button" variant="outline" size="sm" onClick={() => openEditDialog(estimate)}>Bearbeiten</Button>}
{canSend && ["draft", "sent"].includes(estimate.status) && <Button type="button" size="sm" onClick={() => void sendEstimate(estimate)} disabled={pendingId === estimate.id || !customerEmail}><Send />Senden</Button>}
{canUpdate && ["draft", "sent"].includes(estimate.status) && <Button type="button" variant="outline" size="sm" onClick={() => void cancelEstimate(estimate)} disabled={pendingId === estimate.id}><XCircle />Stornieren</Button>}
{(canUpdate || canSend) && ["draft", "sent"].includes(estimate.status) && <Button type="button" variant="outline" size="sm" onClick={() => setCancelTarget(estimate)} disabled={pendingId === estimate.id}><XCircle />Stornieren</Button>}
{canDelete && ["draft", "cancelled"].includes(estimate.status) && <Button type="button" variant="destructive" size="sm" onClick={() => setDeleteTarget(estimate)} disabled={pendingId === estimate.id}><Trash2 />Löschen</Button>}
</div>
</article>
@ -606,6 +609,18 @@ export default function RepairEstimatesSection({
</DialogContent>
</Dialog>
<ConfirmDialog
open={Boolean(cancelTarget)}
title="Kostenvoranschlag stornieren?"
description="Der Kostenvoranschlag wird storniert. Reservierte Lagerartikel werden freigegeben."
confirmLabel="Stornieren"
pending={pendingId === cancelTarget?.id}
onOpenChange={(open) => !open && setCancelTarget(null)}
onConfirm={() => void cancelEstimate()}
>
{cancelTarget && <p className="text-sm text-slate-600">{cancelTarget.estimate_number} · {cancelTarget.title}</p>}
</ConfirmDialog>
<ConfirmDialog
open={Boolean(deleteTarget)}
title="Kostenvoranschlag löschen?"