diff --git a/src/admin/pages/InvoiceDetail.tsx b/src/admin/pages/InvoiceDetail.tsx index cd4c3a6..6139784 100644 --- a/src/admin/pages/InvoiceDetail.tsx +++ b/src/admin/pages/InvoiceDetail.tsx @@ -3,18 +3,25 @@ import { useNavigate, useSearchParams, useParams, - Link, + Link as RouterLink, } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; import DOMPurify from "dompurify"; +import { motion } from "framer-motion"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import IconButton from "@mui/material/IconButton"; +import CircularProgress from "@mui/material/CircularProgress"; +import Table from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import TableContainer from "@mui/material/TableContainer"; +import TableHead from "@mui/material/TableHead"; +import TableRow from "@mui/material/TableRow"; import { useApiMutation } from "../lib/queries/mutations"; import { useAlert } from "../context/AlertContext"; import { useAuth } from "../context/AuthContext"; import Forbidden from "../components/Forbidden"; -import FormField from "../components/FormField"; -import AdminDatePicker from "../components/AdminDatePicker"; -import ConfirmModal from "../components/ConfirmModal"; -import { motion } from "framer-motion"; import { DndContext, closestCenter, @@ -37,18 +44,24 @@ import { } from "@dnd-kit/modifiers"; import { CSS } from "@dnd-kit/utilities"; import apiFetch from "../utils/api"; -import { - companySettingsOptions, - type CompanySettingsData, -} from "../lib/queries/settings"; -import { - invoiceDetailOptions, - type InvoiceDetail, -} from "../lib/queries/invoices"; +import { companySettingsOptions } from "../lib/queries/settings"; +import { invoiceDetailOptions } from "../lib/queries/invoices"; import { offerCustomersOptions, type Customer } from "../lib/queries/offers"; -import { bankAccountsOptions, type BankAccount } from "../lib/queries/common"; +import { bankAccountsOptions } from "../lib/queries/common"; import { jsonQuery } from "../lib/apiAdapter"; import { formatCurrency, formatDate, todayLocalStr } from "../utils/formatters"; +import { + Button, + Card, + TextField, + Select, + DateField, + Field, + StatusChip, + ConfirmDialog, + EmptyState, + LoadingState, +} from "../ui"; const API_BASE = "/api/admin"; @@ -58,16 +71,68 @@ const STATUS_LABELS: Record = { overdue: "Po splatnosti", }; -const STATUS_CLASSES: Record = { - issued: "admin-badge-invoice-issued", - paid: "admin-badge-invoice-paid", - overdue: "admin-badge-invoice-overdue", +const STATUS_COLORS: Record< + string, + "default" | "success" | "error" | "warning" | "info" +> = { + issued: "info", + paid: "success", + overdue: "error", }; const TRANSITION_LABELS: Record = { paid: "Zaplaceno" }; -const TRANSITION_CLASSES: Record = { - paid: "admin-btn admin-btn-primary", -}; + +const BackIcon = ( + + + +); + +const FileIcon = ( + + + + +); + +const DragIcon = ( + + + + + + + + +); + +const RemoveIcon = ( + + + + +); interface InvoiceItem { id?: number; @@ -139,150 +204,115 @@ function SortableInvoiceRow({ transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.5 : 1, - background: isDragging ? "var(--bg-secondary)" : undefined, + background: isDragging ? "var(--mui-palette-action-hover)" : undefined, position: "relative" as const, zIndex: isDragging ? 10 : undefined, }; const lineTotal = (Number(item.quantity) || 0) * (Number(item.unit_price) || 0); return ( - - - - - {index + 1} - -
- + + + {index + 1} + + + + onUpdate(index, "description", e.target.value)} - className="admin-form-input fw-500" placeholder="Popis položky..." + sx={{ "& input": { fontWeight: 500 } }} /> - onUpdate(index, "item_description", e.target.value) } - className="admin-form-input" placeholder="Podrobný popis (volitelný)" - style={{ fontSize: "0.8rem", opacity: 0.8 }} + sx={{ "& input": { fontSize: "0.8rem", opacity: 0.8 } }} /> -
- - - + + + onUpdate(index, "quantity", Number(e.target.value))} - className="admin-form-input" - min="0" - step="any" - style={{ - textAlign: "center", - height: "2.25rem", - padding: "0.375rem 0.5rem", - }} + slotProps={{ htmlInput: { min: "0", step: "any" } }} + sx={{ "& input": { textAlign: "center" } }} /> - - - + + onUpdate(index, "unit", e.target.value)} - className="admin-form-input" placeholder="ks" - style={{ - textAlign: "center", - height: "2.25rem", - padding: "0.375rem 0.5rem", - }} + sx={{ "& input": { textAlign: "center" } }} /> - - - + + onUpdate(index, "unit_price", Number(e.target.value)) } - className="admin-form-input" - step="any" - style={{ - textAlign: "right", - height: "2.25rem", - padding: "0.375rem 0.5rem", - }} + slotProps={{ htmlInput: { step: "any" } }} + sx={{ "& input": { textAlign: "right" } }} /> - + {apply_vat ? ( - - - + + + + Základní údaje + + + + - - + + {form.customer_id ? ( -
- {form.customer_name} - -
+ +
) : ( -
e.stopPropagation()} > - { setCustomerSearch(e.target.value); setShowCustomerDropdown(true); }} onFocus={() => setShowCustomerDropdown(true)} - className="admin-form-input" placeholder="Hledat zákazníka (název, IČ, město)..." autoComplete="off" /> {showCustomerDropdown && ( -
+ {filteredCustomers.length === 0 ? ( -
+ Žádní zákazníci -
+
) : ( filteredCustomers.slice(0, 10).map((c) => ( -
selectCustomer(c)} + sx={{ + px: 1.5, + py: 1, + cursor: "pointer", + "&:hover": { bgcolor: "action.hover" }, + }} > -
{c.name}
+ {c.name} {(c.company_id || c.city) && ( -
+ {c.company_id && `IČ: ${c.company_id}`} {c.city && ` · ${c.city}`} -
+ )} -
+ )) )} -
+ )} -
+ )} - - - + + - - + + - - + setForm((prev) => ({ @@ -1488,222 +1657,241 @@ export default function InvoiceDetail() { billing_text: e.target.value, })) } - className="admin-form-input" placeholder="Fakturujeme Vám za: (ponechte prázdné pro výchozí)" /> - + -
- - + + { + onChange={(val) => { setForm((prev) => ({ ...prev, issue_date: val })); setErrors((prev) => ({ ...prev, issue_date: "" })); }} /> - - - + + + + + - - - - setForm((prev) => ({ ...prev, currency: e.target.value })) + onChange={(val) => + setForm((prev) => ({ ...prev, currency: val })) } - className="admin-form-select" - > - {( + options={( companySettings?.available_currencies || [ "CZK", "EUR", "USD", "GBP", ] - ).map((c) => ( - - ))} - - - - - setForm((prev) => ({ - ...prev, - language: e.target.value, - })) + onChange={(val) => + setForm((prev) => ({ ...prev, language: val })) } - className="admin-form-select" + options={[ + { value: "cs", label: "Čeština" }, + { value: "en", label: "English" }, + ]} + /> + + + - - - - - -
- -
-
-
+ + setForm((prev) => ({ + ...prev, + apply_vat: e.target.checked ? 1 : 0, + })) + } + /> + Uplatnit DPH + + + - - { + selectBankAccount(val); setErrors((prev) => ({ ...prev, bank_account_id: "" })); }} - className="admin-form-select" - > - - {bankAccounts.map((acc) => ( - - ))} - - - + options={[ + { + value: "", + label: `— Vyberte účet —`, + }, + ...bankAccounts.map((acc) => ({ + value: String(acc.id), + label: `${acc.account_name}${ + acc.account_number ? ` (${acc.account_number})` : "" + }${acc.is_default ? " ★" : ""}`, + })), + ]} + /> + + {/* Items */} -
-
-
-

Položky

+ + + + + Položky + {errors.items && ( - {errors.items} + + {errors.items} + )} -
- -
+ + - - i._key)} - strategy={verticalListSortingStrategy} + + -
- - - -
- + i._key)} + strategy={verticalListSortingStrategy} + > + + + + + # - - - - - + {form.apply_vat ? ( - + ) : null} - - - - - + + + + + {items.map((item, index) => ( 1} /> ))} - -
Popis + + Popis + Množství - + + Jednotka - + + Jedn. cena - + DPH - + Celkem -
- -
- + +
+ + + {/* Totals */} -
-
- Mezisoučet: - + + + + Mezisoučet: + + {formatCurrency(createTotals.subtotal, form.currency)} - -
+ + {form.apply_vat && Object.entries(createTotals.vatByRate).map(([rate, amount]) => ( -
- DPH {rate}%: - {formatCurrency(amount, form.currency)} -
+ + + DPH {rate}%: + + + {formatCurrency(amount, form.currency)} + + ))} -
- Celkem k úhradě: - {formatCurrency(createTotals.total, form.currency)} -
-
-
+ + + Celkem k úhradě: + + + {formatCurrency(createTotals.total, form.currency)} + + + + {/* Notes */} -

Veřejné poznámky na faktuře

-