8.1 KiB
MUI Migration — Phase 5: Projects (Projekty) — Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: superpowers:subagent-driven-development. Steps use checkbox (
- [ ]) syntax.
Goal: Migrate src/admin/pages/Projects.tsx onto the MUI kit. It's a paginated, server-sorted list with a create modal (customer/user Selects, DateFields, multiline notes) and a delete confirm that has an extra "delete files from disk" checkbox. Mirror Vehicles.tsx/Users.tsx; preserve ALL data logic.
Prereq kit tweak (Task 1): ConfirmDialog needs to accept optional extra content below the message (for the delete-files checkbox), since message is a string.
Tech Stack: MUI v7 kit (src/admin/ui/): Button, Card, DataTable (now sortable), Pagination, Modal, ConfirmDialog, Field, TextField, Select, DateField, StatusChip, CheckboxField, type DataColumn. MUI Box/Typography/IconButton direct.
Reference: Vehicles.tsx + Users.tsx (migrated). The Projects data layer is authoritative — keep it.
Gates: npx tsc -b --noEmit, npm run build, npx vitest run (148). Visual check is the user's.
Behavior to preserve VERBATIM (from the scout)
- List state:
const { sort, order, handleSort, activeSort } = useTableSort("project_number");search/pagestate; resetsetPage(1)when search changes.usePaginatedQuery<Project>(projectListOptions({ search, sort, order, page }))→{ items, pagination, isPending, isFetching }. - isFetching dimming: the list Card gets
sx={{ opacity: isFetching ? 0.6 : 1, transition: "opacity .2s" }}. - Create-only (edit navigates to the
/projects/:iddetail route — NOT a modal).createForm = { name, customer_id, responsible_user_id, status: "aktivni", start_date, end_date, notes },createErrors,creating. Dependent queries enabled only while the modal is open:offerCustomersOptions(),userListOptions("projects.view")(filter tois_active),projectNextNumberOptions().createMutation: POST/projects,invalidate: ["projects","orders","offers","invoices","attendance"], onSuccess close + "Projekt byl vytvořen". Validation: onlynamerequired ("Zadejte název projektu"). - Delete:
deleteTarget/deleteFilesstate.deleteMutation: DELETE/projects/:id,invalidate: ["projects","warehouse","orders","offers","invoices","attendance"], called asmutateAsync({ id, delete_files: deleteFiles }), onSuccess toastdata?.message || "Projekt byl smazán"+ reset. - Permissions:
projects.view→<Forbidden/>;projects.createhides the add button;projects.deletehides the per-row delete; delete also hidden whenp.order_id(order-linked projects can't be manually deleted). - Status constants:
STATUS_LABELS = { aktivni:"Aktivní", dokonceny:"Dokončený", zruseny:"Zrušený" }. Map toStatusChipcolors:aktivni→success,dokonceny→info,zruseny→default. - Next number:
nextNumberQuery.data?.number ?? nextNumberQuery.data?.next_number ?? "…".
Task 1: Extend ConfirmDialog with optional extra content
Files: Modify src/admin/ui/ConfirmDialog.tsx.
- Step 1: Add
children?: ReactNodetoConfirmDialogProps(importtype { ReactNode }). Render it inDialogContentBELOW the message (live — not frozen, so the checkbox stays interactive):
<DialogContent>
<DialogContentText>{shown.message}</DialogContentText>
{children}
</DialogContent>
- Step 2: Verify
tsc -bclean;build:clientOK; existing callers (Vozidla, Users) unaffected (nochildren→ unchanged). Commit (feat(mui): ConfirmDialog optional children (extra content below message); + trailer).
Task 2: Migrate Projects.tsx
Files: Modify src/admin/pages/Projects.tsx (read it first — authoritative for the logic above).
-
Step 1: Read
src/admin/pages/Projects.tsxfully +Users.tsx/Vehicles.tsx. Keep ALL hooks/queries/mutations/handlers/permission-guards/state. Change only presentation. -
Step 2: Imports — from
../ui:Button, Card, DataTable, Pagination, Modal, ConfirmDialog, Field, TextField, Select, DateField, StatusChip, CheckboxField, type DataColumn. MUIBox, Typography, IconButtondirect. Keepmotion,useNavigate, the query options,useApiMutation,useAlert,useAuth,Forbidden. RemoveFormModal/ConfirmModal/FormField/AdminDatePicker/legacyPaginationimports. -
Step 3: Page shell — Box + motion.div header (
Typography variant="h4""Projekty" +Button startIcon={PlusIcon}"Nový projekt", hidden withoutprojects.create). A searchTextField(noFieldwrapper) that sets search +setPage(1). -
Step 4: List —
Card(with theisFetchingopacity sx) wrapping a sortableDataTable<Project>:- Wire sort:
sortBy={sort} sortDir={order} onSort={handleSort}. Give sortable columns asortKey(e.g.project_number,name— match the server's accepted sort keys from the originalSortIconusage). - Columns: project number (
mono,sortKey), name (sortKey), customer, responsible user, status (StatusChipper the color map), linked order number (iforder_id), start/end dates (mono, via the existing date formatter), actions — a detail link/IconButtonthatnavigate(\/projects/${p.id}`)for edit, and a deleteIconButton color="error"shown only whenhasPermission("projects.delete") && !p.order_id`. - Below the table:
<Pagination page={page} pageCount={pagination?.totalPages ?? 1} onChange={setPage} />(use the actual page-count field frompagination— check the shape).
- Wire sort:
-
Step 5: Create
Modal(isOpen={showCreate},loading={creating}, title "Nový projekt", subtitle/hint with the next number):Field+TextFieldfor name (required, error fromcreateErrors);Select(in aField) for customer (offerCustomersOptions) and responsible user (active users) — options{ value: String(id), label }, convert at the boundary;Selectfor status (the STATUS_LABELS options); twoDateFields for start/end (string in/out — matchescreateForm.start_date);TextField multiline minRows={3}for notes. Validation: only name required. -
Step 6: Delete
ConfirmDialog— title "Smazat projekt", message with the project name,confirmVariant="danger", and as children aCheckboxField"Smazat i soubory z disku" bound todeleteFiles.onConfirmcalls the existing delete handler (mutateAsync({ id, delete_files: deleteFiles })). -
Step 7: Verify
tsc -bclean;npm run buildOK;npx vitest run(148). Dropadmin-*classes. (Projects has no own CSS file; it uses shared classes.) -
Step 8: Commit (
feat(mui): migrate Projects (Projekty) page onto MUI kit; + trailer). -
Step 9: MANUAL (USER) — deferred: list renders + sorts (click headers) + paginates + dims while fetching; create modal (customer/user/status selects, date pickers, notes; name-required validation; next number shown) saves; delete confirm with the files checkbox works; order-linked projects show no delete; permission gating; dark+light.
Task 3: Review + finish
- Spec-compliance review (server sort/pagination wiring, the create
invalidatearrays, name-only validation, delete{id, delete_files}body + the checkbox, permission/order-link gating, status color map, the Select string boundaries) → code-quality review. - finishing-a-development-branch after the user's visual check.
Self-review notes
- This is the first page to exercise the sortable DataTable + Pagination + DateField + Select together — confirm the sort keys match what the server accepts and the pagination page-count field name is correct (read the query options / a working paginated page).
Selectis string-based; convert customer/user/status ids at the form boundary (String(...)for options/value; send what the server expects).- The delete-files checkbox rides in
ConfirmDialogchildren(Task 1) — it's live (not frozen), so it toggles while open; it resets via the caller on close. - Edit is a route navigation (
/projects/:id), NOT a modal — don't add an edit modal.