feat(drafts): two-button create (Vytvořit / Uložit koncept) + draft finalize UX across offers/invoices/issued-orders
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -74,6 +74,7 @@ import {
|
||||
INVOICE_STATUS,
|
||||
statusLabel,
|
||||
statusColor,
|
||||
documentNumberLabel,
|
||||
} from "../lib/documentStatus";
|
||||
|
||||
const API_BASE = "/api/admin";
|
||||
@@ -107,6 +108,10 @@ function addDaysLocalStr(days: number, base?: string): string {
|
||||
|
||||
const TRANSITION_LABELS: Record<string, string> = { paid: "Zaplaceno" };
|
||||
|
||||
// The live (finalized) status for an invoice — assigning it (on create or on
|
||||
// finalizing a draft) makes the backend consume the official invoice number.
|
||||
const LIVE_STATUS = "issued";
|
||||
|
||||
const BackIcon = (
|
||||
<svg
|
||||
width="20"
|
||||
@@ -943,9 +948,7 @@ export default function InvoiceDetail() {
|
||||
invalidate: ["invoices", "orders"],
|
||||
});
|
||||
|
||||
const handleCreateSubmit = async (e?: React.FormEvent) => {
|
||||
e?.preventDefault();
|
||||
|
||||
const handleCreateSubmit = async (targetStatus?: string) => {
|
||||
const newErrors: Record<string, string> = {};
|
||||
if (!form.customer_id) newErrors.customer_id = "Vyberte zákazníka";
|
||||
if (!form.issue_date) newErrors.issue_date = "Zadejte datum";
|
||||
@@ -974,7 +977,14 @@ export default function InvoiceDetail() {
|
||||
position: i,
|
||||
})),
|
||||
};
|
||||
if (isEdit) payload.invoice_number = invoiceNumber;
|
||||
// Only set status when a target is given (create-as-draft / create-as-live
|
||||
// / finalize). Editing a live invoice sends no status — the backend keeps
|
||||
// its current status.
|
||||
if (targetStatus) payload.status = targetStatus;
|
||||
// The invoice number is NOT user-editable for drafts (it is NULL until
|
||||
// finalized). Send it back only when editing an already-numbered invoice.
|
||||
if (isEdit && invoice?.status !== "draft")
|
||||
payload.invoice_number = invoiceNumber;
|
||||
|
||||
const data = await saveMutation.mutateAsync(payload);
|
||||
alert.success(isEdit ? "Faktura byla uložena" : "Faktura byla vytvořena");
|
||||
@@ -995,6 +1005,16 @@ export default function InvoiceDetail() {
|
||||
}
|
||||
};
|
||||
|
||||
// Native form submit (e.g. Enter): route to the right save path. Create →
|
||||
// finalize live; editing a draft → save draft (the visible primary button);
|
||||
// editing a live invoice → plain save (no status change).
|
||||
const handleFormSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!isEdit) void handleCreateSubmit(LIVE_STATUS);
|
||||
else if (invoice?.status === "draft") void handleCreateSubmit("draft");
|
||||
else void handleCreateSubmit();
|
||||
};
|
||||
|
||||
// ─── Edit mode: status change ───
|
||||
const handleStatusChange = async () => {
|
||||
if (!statusConfirm.status) return;
|
||||
@@ -1098,7 +1118,7 @@ export default function InvoiceDetail() {
|
||||
}}
|
||||
>
|
||||
<Typography variant="h4">
|
||||
Faktura {invoice.invoice_number}
|
||||
Faktura {documentNumberLabel(invoice.invoice_number)}
|
||||
</Typography>
|
||||
<StatusChip
|
||||
label={statusLabel(INVOICE_STATUS, invoice.status)}
|
||||
@@ -1619,7 +1639,7 @@ export default function InvoiceDetail() {
|
||||
}}
|
||||
>
|
||||
<Typography variant="h4">
|
||||
Faktura {invoice.invoice_number}
|
||||
Faktura {documentNumberLabel(invoice.invoice_number)}
|
||||
</Typography>
|
||||
<StatusChip
|
||||
label={statusLabel(INVOICE_STATUS, invoice.status)}
|
||||
@@ -1667,18 +1687,105 @@ export default function InvoiceDetail() {
|
||||
Zobrazit fakturu
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={handleCreateSubmit} disabled={saving}>
|
||||
{saving ? (
|
||||
<>
|
||||
<CircularProgress size={16} color="inherit" sx={{ mr: 1 }} />
|
||||
Ukládání...
|
||||
</>
|
||||
) : (
|
||||
"Uložit"
|
||||
)}
|
||||
</Button>
|
||||
{isEdit && invoice && (
|
||||
{/* ── Create mode: two-button save ── */}
|
||||
{!isEdit && (
|
||||
<>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="inherit"
|
||||
onClick={() => handleCreateSubmit("draft")}
|
||||
disabled={saving}
|
||||
>
|
||||
{saving ? (
|
||||
<CircularProgress size={16} color="inherit" />
|
||||
) : (
|
||||
"Uložit koncept"
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleCreateSubmit(LIVE_STATUS)}
|
||||
disabled={saving}
|
||||
>
|
||||
{saving ? (
|
||||
<>
|
||||
<CircularProgress
|
||||
size={16}
|
||||
color="inherit"
|
||||
sx={{ mr: 1 }}
|
||||
/>
|
||||
Ukládání...
|
||||
</>
|
||||
) : (
|
||||
"Vytvořit"
|
||||
)}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* ── Edit mode: draft → save-draft + finalize ── */}
|
||||
{isEdit && invoice && invoice.status === "draft" && (
|
||||
<>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="inherit"
|
||||
onClick={() => handleCreateSubmit("draft")}
|
||||
disabled={saving}
|
||||
>
|
||||
{saving ? (
|
||||
<CircularProgress size={16} color="inherit" />
|
||||
) : (
|
||||
"Uložit koncept"
|
||||
)}
|
||||
</Button>
|
||||
{hasPermission("invoices.edit") && (
|
||||
<Button
|
||||
onClick={() => handleCreateSubmit(LIVE_STATUS)}
|
||||
disabled={saving}
|
||||
>
|
||||
{saving ? (
|
||||
<>
|
||||
<CircularProgress
|
||||
size={16}
|
||||
color="inherit"
|
||||
sx={{ mr: 1 }}
|
||||
/>
|
||||
Ukládání...
|
||||
</>
|
||||
) : (
|
||||
"Vystavit"
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
{/* A draft is deleted rather than cancelled. */}
|
||||
{hasPermission("invoices.delete") && (
|
||||
<Button
|
||||
onClick={() => setDeleteConfirm(true)}
|
||||
variant="outlined"
|
||||
color="error"
|
||||
>
|
||||
Smazat
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* ── Edit mode: live (non-draft) doc — unchanged ── */}
|
||||
{isEdit && invoice && invoice.status !== "draft" && (
|
||||
<>
|
||||
<Button onClick={() => handleCreateSubmit()} disabled={saving}>
|
||||
{saving ? (
|
||||
<>
|
||||
<CircularProgress
|
||||
size={16}
|
||||
color="inherit"
|
||||
sx={{ mr: 1 }}
|
||||
/>
|
||||
Ukládání...
|
||||
</>
|
||||
) : (
|
||||
"Uložit"
|
||||
)}
|
||||
</Button>
|
||||
{hasPermission("invoices.edit") &&
|
||||
invoice.valid_transitions?.map((status) => (
|
||||
<Button
|
||||
@@ -1723,7 +1830,7 @@ export default function InvoiceDetail() {
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<form onSubmit={handleCreateSubmit}>
|
||||
<form onSubmit={handleFormSubmit}>
|
||||
{/* Basic info */}
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
|
||||
Reference in New Issue
Block a user