From ea6c943a5151661a7cf1037115a0fa3fc23e1b6c Mon Sep 17 00:00:00 2001 From: BOHA Date: Sat, 6 Jun 2026 22:27:20 +0200 Subject: [PATCH] feat(mui): migrate Projects (Projekty) page onto MUI kit Co-Authored-By: Claude Opus 4.8 (1M context) --- src/admin/pages/Projects.tsx | 684 +++++++++++++++++------------------ 1 file changed, 328 insertions(+), 356 deletions(-) diff --git a/src/admin/pages/Projects.tsx b/src/admin/pages/Projects.tsx index 2194b45..418c8d2 100644 --- a/src/admin/pages/Projects.tsx +++ b/src/admin/pages/Projects.tsx @@ -1,17 +1,14 @@ import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; +import { useNavigate } from "react-router-dom"; +import { motion } from "framer-motion"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import IconButton from "@mui/material/IconButton"; import { useAlert } from "../context/AlertContext"; import { useAuth } from "../context/AuthContext"; -import { Link } from "react-router-dom"; import Forbidden from "../components/Forbidden"; -import { motion } from "framer-motion"; -import ConfirmModal from "../components/ConfirmModal"; -import FormModal from "../components/FormModal"; -import FormField from "../components/FormField"; -import AdminDatePicker from "../components/AdminDatePicker"; - -import { formatDate, czechPlural } from "../utils/formatters"; -import SortIcon from "../components/SortIcon"; +import { formatDate } from "../utils/formatters"; import useTableSort from "../hooks/useTableSort"; import { usePaginatedQuery } from "../hooks/usePaginatedQuery"; import { @@ -21,7 +18,21 @@ import { import { offerCustomersOptions } from "../lib/queries/offers"; import { userListOptions } from "../lib/queries/users"; import { useApiMutation } from "../lib/queries/mutations"; -import Pagination from "../components/Pagination"; +import { + Button, + Card, + DataTable, + Pagination, + Modal, + ConfirmDialog, + Field, + TextField, + Select, + DateField, + StatusChip, + CheckboxField, + type DataColumn, +} from "../ui"; const API_BASE = "/api/admin"; @@ -31,10 +42,13 @@ const STATUS_LABELS: Record = { zruseny: "Zrušený", }; -const STATUS_CLASSES: Record = { - aktivni: "admin-badge-project-aktivni", - dokonceny: "admin-badge-project-dokonceny", - zruseny: "admin-badge-project-zruseny", +const STATUS_COLORS: Record< + string, + "default" | "success" | "error" | "warning" | "info" +> = { + aktivni: "success", + dokonceny: "info", + zruseny: "default", }; interface Project { @@ -50,12 +64,56 @@ interface Project { order_number?: string; } +const PlusIcon = ( + + + + +); +const EditIcon = ( + + + + +); +const DeleteIcon = ( + + + + +); + export default function Projects() { const alert = useAlert(); + const navigate = useNavigate(); const { hasPermission } = useAuth(); - const { sort, order, handleSort, activeSort } = - useTableSort("project_number"); + const { sort, order, handleSort } = useTableSort("project_number"); const [search, setSearch] = useState(""); const [page, setPage] = useState(1); const [deleteTarget, setDeleteTarget] = useState(null); @@ -183,260 +241,175 @@ export default function Projects() { ); } + const columns: DataColumn[] = [ + { + key: "project_number", + header: "Číslo", + sortKey: "project_number", + mono: true, + render: (p) => p.project_number, + }, + { + key: "name", + header: "Název", + sortKey: "name", + bold: true, + render: (p) => p.name || "—", + }, + { + key: "customer", + header: "Zákazník", + render: (p) => p.customer_name || "—", + }, + { + key: "responsible", + header: "Zodpovědná osoba", + render: (p) => p.responsible_user_name || "—", + }, + { + key: "status", + header: "Stav", + sortKey: "status", + render: (p) => ( + + ), + }, + { + key: "order", + header: "Objednávka", + render: (p) => + p.order_id ? ( + { + e.preventDefault(); + navigate(`/orders/${p.order_id}`); + }} + sx={{ cursor: "pointer", color: "text.secondary" }} + > + {p.order_number} + + ) : ( + "—" + ), + }, + { + key: "start_date", + header: "Začátek", + mono: true, + render: (p) => formatDate(p.start_date), + }, + { + key: "end_date", + header: "Konec", + mono: true, + render: (p) => formatDate(p.end_date), + }, + { + key: "actions", + header: "Akce", + align: "right", + render: (p) => ( + + navigate(`/projects/${p.id}`)} + aria-label="Upravit" + title="Upravit" + > + {EditIcon} + + {hasPermission("projects.delete") && !p.order_id && ( + setDeleteTarget(p)} + aria-label="Smazat" + title="Smazat projekt" + > + {DeleteIcon} + + )} + + ), + }, + ]; + return ( -
+ -
-

Projekty

-

- {pagination?.total ?? projects.length}{" "} - {czechPlural( - pagination?.total ?? projects.length, - "projekt", - "projekty", - "projektů", - )} -

-
- {hasPermission("projects.create") && ( -
- -
- )} + + )} +
+ + { + setSearch(e.target.value); + setPage(1); + }} + placeholder="Hledat podle čísla, názvu nebo zákazníka..." + fullWidth + /> + + -
-
- { - setSearch(e.target.value); - setPage(1); - }} - className="admin-form-input" - placeholder="Hledat podle čísla, názvu nebo zákazníka..." - /> -
- - {projects.length === 0 ? ( -
-
- - - -
-

Zatím nejsou žádné projekty.

-

- Projekty vznikají z objednávek nebo je můžete vytvořit ručně - tlačítkem „Přidat projekt". -

-
- ) : ( -
- - - - - - - - - - - - - - - - - {(projects as Project[]).map((p) => ( - - - - - - - - - - - - - ))} - -
handleSort("project_number")} - > - Číslo{" "} - - handleSort("name")} - > - Název{" "} - - ZákazníkZodpovědná osoba handleSort("status")} - > - Stav{" "} - - handleSort("start_date")} - > - Začátek{" "} - - handleSort("end_date")} - > - Konec{" "} - - TypObjednávkaAkce
- - {p.project_number} - - {p.name || "—"}{p.customer_name || "—"}{p.responsible_user_name || "—"} - - {STATUS_LABELS[p.status] || p.status} - - {formatDate(p.start_date)}{formatDate(p.end_date)} - - {p.order_id ? "Z objednávky" : "Samostatný"} - - - {p.order_id ? ( - - {p.order_number} - - ) : ( - "—" - )} - -
- - - - - - - {!p.order_id && hasPermission("projects.delete") && ( - - )} -
-
-
- )} - -
+ + + columns={columns} + rows={projects} + rowKey={(p) => p.id} + sortBy={sort} + sortDir={order} + onSort={handleSort} + empty={ + + + Zatím nejsou žádné projekty. + + + Projekty vznikají z objednávek nebo je můžete vytvořit ručně + tlačítkem „Přidat projekt". + + + } + /> + +
- { setDeleteTarget(null); @@ -445,133 +418,132 @@ export default function Projects() { onConfirm={handleDelete} title="Smazat projekt" message={ - <> - Opravdu chcete smazat projekt {deleteTarget?.project_number}? - - + deleteTarget + ? `Opravdu chcete smazat projekt ${deleteTarget.project_number}?` + : "" } confirmText="Smazat" - type="danger" + confirmVariant="danger" loading={deleteMutation.isPending} - /> + > + + - setShowCreate(false)} onSubmit={handleCreate} - title="Přidat projekt" - submitLabel="Vytvořit" + title="Nový projekt" + subtitle={`Číslo projektu: ${ + nextNumberQuery.data?.number ?? + nextNumberQuery.data?.next_number ?? + "…" + }`} + submitText="Vytvořit" loading={creating} > -
- - { - setCreateForm({ ...createForm, name: e.target.value }); - setCreateErrors((p) => ({ ...p, name: "" })); - }} - className="admin-form-input" - placeholder="Název projektu" - aria-invalid={!!createErrors.name} - /> - + + { + setCreateForm({ ...createForm, name: e.target.value }); + setCreateErrors((p) => ({ ...p, name: "" })); + }} + /> + -
- - - setCreateForm({ ...createForm, customer_id: e.target.value }) + onChange={(value) => + setCreateForm({ ...createForm, customer_id: value }) } - className="admin-form-input" - > - - {(customersQuery.data ?? []).map((c) => ( - - ))} - - - - - - setCreateForm({ - ...createForm, - responsible_user_id: e.target.value, - }) + onChange={(value) => + setCreateForm({ ...createForm, responsible_user_id: value }) } - className="admin-form-input" - > - - {(usersQuery.data ?? []) - .filter((u) => u.is_active) - .map((u) => ( - - ))} - - -
+ options={[ + { value: "", label: "— nepřiřazeno —" }, + ...(usersQuery.data ?? []) + .filter((u) => u.is_active) + .map((u) => ({ + value: String(u.id), + label: `${u.first_name} ${u.last_name}`, + })), + ]} + /> + + + -
- - +