From 2878041687c2ec23f565a667f02bee91611d282d Mon Sep 17 00:00:00 2001 From: BOHA Date: Sun, 7 Jun 2026 08:15:21 +0200 Subject: [PATCH] feat(mui): migrate OrderDetail onto MUI kit Co-Authored-By: Claude Opus 4.8 (1M context) --- src/admin/pages/OrderDetail.tsx | 882 ++++++++++++++++++-------------- 1 file changed, 487 insertions(+), 395 deletions(-) diff --git a/src/admin/pages/OrderDetail.tsx b/src/admin/pages/OrderDetail.tsx index c4fd08b..38f83c9 100644 --- a/src/admin/pages/OrderDetail.tsx +++ b/src/admin/pages/OrderDetail.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useMemo, useRef, type ReactNode } from "react"; +import { useState, useEffect, useMemo, useRef } from "react"; import DOMPurify from "dompurify"; import { useQuery } from "@tanstack/react-query"; import { @@ -12,17 +12,32 @@ import { import { useApiMutation } from "../lib/queries/mutations"; import { useAlert } from "../context/AlertContext"; import { useAuth } from "../context/AuthContext"; -import { useParams, useNavigate, Link } from "react-router-dom"; +import { useParams, useNavigate, Link as RouterLink } from "react-router-dom"; import { motion } from "framer-motion"; -import ConfirmModal from "../components/ConfirmModal"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; import OrderConfirmationModal from "../components/OrderConfirmationModal"; -import FormField from "../components/FormField"; import Forbidden from "../components/Forbidden"; import apiFetch from "../utils/api"; import { formatCurrency, formatDate } from "../utils/formatters"; +import { + Button, + Card, + Field, + TextField, + DataTable, + StatusChip, + CheckboxField, + ConfirmDialog, + EmptyState, + LoadingState, + type DataColumn, +} from "../ui"; const API_BASE = "/api/admin"; +type ItemRow = OrderItem & { _index: number }; + const STATUS_LABELS: Record = { prijata: "Přijatá", v_realizaci: "V realizaci", @@ -30,11 +45,14 @@ const STATUS_LABELS: Record = { stornovana: "Stornována", }; -const STATUS_CLASSES: Record = { - prijata: "admin-badge-order-prijata", - v_realizaci: "admin-badge-order-realizace", - dokoncena: "admin-badge-order-dokoncena", - stornovana: "admin-badge-order-stornovana", +const STATUS_COLORS: Record< + string, + "default" | "success" | "error" | "warning" | "info" +> = { + prijata: "info", + v_realizaci: "warning", + dokoncena: "success", + stornovana: "error", }; const TRANSITION_LABELS: Record = { @@ -42,10 +60,32 @@ const TRANSITION_LABELS: Record = { dokoncena: "Dokončit", }; -const TRANSITION_CLASSES: Record = { - v_realizaci: "admin-btn admin-btn-primary", - dokoncena: "admin-btn admin-btn-primary", -}; +const BackIcon = ( + + + +); + +const FileIcon = ( + + + + +); export default function OrderDetail() { const { id } = useParams(); @@ -266,470 +306,534 @@ export default function OrderDetail() { } }; + if (loading) { + return ; + } + if (!order) return null; - if (loading) { - return ( -
-
-
- ); - } + const itemRows: ItemRow[] = (order.items ?? []).map((item, index) => ({ + ...item, + _index: index, + })); + + const itemColumns: DataColumn[] = [ + { + key: "index", + header: "#", + width: "2.5rem", + render: (item) => ( + + {item._index + 1} + + ), + }, + { + key: "description", + header: "Popis", + render: (item) => ( + + + {item.description || "—"} + + {item.item_description && ( + + {item.item_description} + + )} + + ), + }, + { + key: "quantity", + header: "Množství", + width: "5.5rem", + render: (item) => String(item.quantity), + }, + { + key: "unit", + header: "Jednotka", + width: "5.5rem", + render: (item) => item.unit || "—", + }, + { + key: "unit_price", + header: "Jedn. cena", + width: "8rem", + align: "right", + mono: true, + render: (item) => formatCurrency(item.unit_price, order.currency), + }, + { + key: "is_included_in_total", + header: "V ceně", + width: "4rem", + render: (item) => (Number(item.is_included_in_total) ? "Ano" : "Ne"), + }, + { + key: "total", + header: "Celkem", + width: "9rem", + align: "right", + mono: true, + bold: true, + render: (item) => + formatCurrency( + (Number(item.quantity) || 0) * (Number(item.unit_price) || 0), + order.currency, + ), + }, + ]; + return ( -
+ {/* Header */} -
- + + + + + Objednávka {order.order_number} + + + + + - - - - -
-

- Objednávka {order.order_number} - - {STATUS_LABELS[order.status] || order.status} - -

-
-
-
- {order.invoice ? ( - - - - - - Faktura {order.invoice.invoice_number} - - ) : ( - hasPermission("invoices.create") && - order.status === "dokoncena" && ( - - + ) : ( + hasPermission("invoices.create") && + order.status === "dokoncena" && ( + - Vytvořit fakturu - - ) - )} - {hasPermission("orders.export") && ( - - )} - {hasPermission("orders.edit") && - order.valid_transitions?.filter((s) => s !== "stornovana").length! > - 0 && - order - .valid_transitions!.filter((s) => s !== "stornovana") - .map((status) => ( - - ))} - {hasPermission("orders.delete") && ( - - )} -
+ Potvrzení objednávky + + )} + {hasPermission("orders.edit") && + order.valid_transitions?.filter((s) => s !== "stornovana") + .length! > 0 && + order + .valid_transitions!.filter((s) => s !== "stornovana") + .map((status) => ( + + ))} + {hasPermission("orders.delete") && ( + + )} +
+ {/* Info card */} -
-

Informace

-
- -
- + + Informace + + + + + Nabídka + + + {order.quotation_number} - + {order.project_code && ( - + ({order.project_code}) - + )} -
-
- -
+ + + + + Projekt + + {order.project ? ( - {order.project.project_number} — {order.project.name} - + ) : ( "—" )} -
-
- -
+ + + + + Zákazník + + {order.customer_name || "—"} -
-
-
-
- -
+ + + + + Číslo obj. zákazníka + + {order.customer_order_number || "—"} -
-
- -
{order.currency}
-
-
-
- -
{formatDate(order.created_at)}
-
- -
+ + + + + Měna + + + {order.currency} + + + + + Datum vytvoření + + + {formatDate(order.created_at)} + + + + + Příloha + + {order.attachment_name ? ( - + ) : ( - "—" + )} -
-
-
-
+ + + +
{/* Items (read-only) */} -
-

Položky

- {order.items?.length > 0 ? ( -
- - - - - - - - - - - - - - {order.items.map((item, index) => { - const lineTotal = - (Number(item.quantity) || 0) * - (Number(item.unit_price) || 0); - return ( - - - - - - - - - - ); - })} - -
#Popis - Množství - - Jednotka - - Jedn. cena - - V ceně - - Celkem -
- {index + 1} - -
- {item.description || "—"} -
- {item.item_description && ( -
- {item.item_description} -
- )} -
{item.quantity} - {item.unit || "—"} - - {formatCurrency(item.unit_price, order.currency)} - - {Number(item.is_included_in_total) ? "Ano" : "Ne"} - - {formatCurrency(lineTotal, order.currency)} -
-
- ) : ( -

Žádné položky.

- )} + + + Položky + + + columns={itemColumns} + rows={itemRows} + rowKey={(item) => item.id ?? item._index} + empty={} + /> {/* Totals */} -
-
- Mezisoučet: - {formatCurrency(totals.subtotal, order.currency)} -
+ + + + Mezisoučet: + + + {formatCurrency(totals.subtotal, order.currency)} + + {Number(order.apply_vat) > 0 && ( -
- DPH ({order.vat_rate}%): - {formatCurrency(totals.vatAmount, order.currency)} -
+ + + DPH ({order.vat_rate}%): + + + {formatCurrency(totals.vatAmount, order.currency)} + + )} -
- Celkem k úhradě: - {formatCurrency(totals.total, order.currency)} -
-
-
+ + + Celkem k úhradě: + + + {formatCurrency(totals.total, order.currency)} + + + +
{/* Sections (read-only) */} {order.sections?.length > 0 && ( -
-

Rozsah projektu

+ + + Rozsah projektu + {order.scope_title && ( -
+ {order.scope_title} -
+ )} {order.scope_description && ( -
+ {order.scope_description} -
+ )} -
+ {order.sections.map((section, index) => ( -
-
- {index + 1}. - + + + {index + 1}. + + {(order.language === "CZ" ? section.title_cz || section.title : section.title || section.title_cz) || `Sekce ${index + 1}`} - -
+ + {section.content && ( -
)} -
+ ))} -
-
+ +
)} {/* Notes (editable) */} -
-

Poznámky

- -