From 5855c94b508cb9d7a789d5129e0189880ec7822b Mon Sep 17 00:00:00 2001 From: BOHA Date: Sat, 6 Jun 2026 21:20:01 +0200 Subject: [PATCH] docs(plan): MUI migration Phase 3 (kit expansion) Co-Authored-By: Claude Opus 4.8 (1M context) --- ...-06-mui-migration-phase-3-kit-expansion.md | 362 ++++++++++++++++++ 1 file changed, 362 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-06-mui-migration-phase-3-kit-expansion.md diff --git a/docs/superpowers/plans/2026-06-06-mui-migration-phase-3-kit-expansion.md b/docs/superpowers/plans/2026-06-06-mui-migration-phase-3-kit-expansion.md new file mode 100644 index 0000000..82cd555 --- /dev/null +++ b/docs/superpowers/plans/2026-06-06-mui-migration-phase-3-kit-expansion.md @@ -0,0 +1,362 @@ +# MUI Migration — Phase 3: Kit Expansion — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:subagent-driven-development (or direct execution with gates). Steps use checkbox (`- [ ]`) syntax. + +**Goal:** Build the reusable `src/admin/ui/` components the page migrations keep needing, so later phases are fast and consistent. No page is migrated in this phase; the dev-only `/ui-kit` route is extended to showcase + visually verify each new component. + +**Why these (scout reuse counts):** `Select` ~34 pages, `Tabs` ~41 files, `StatusChip` ~34, plus paginated/sortable lists, dates, checkboxes, inline alerts. + +**Tech Stack:** MUI v7 (`@mui/material`) + `@mui/x-date-pickers` v8 + `date-fns` (cs locale) + Emotion. TypeScript strict. + +**Gates:** `npx tsc -b --noEmit`, `npm run build`, `npx vitest run` (148). Visual check (`/ui-kit`) is the user's. No dev server started by implementers. + +--- + +## File Structure + +| File | Component(s) | API | +| ------------------------------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ------- | --------- | ---------------------------------------- | +| `src/admin/ui/Select.tsx` | `Select` | `{ value, onChange(v), options?: {value,label}[], children?, ...TextFieldProps }` over `` — composes inside `Field` | +| `src/admin/ui/StatusChip.tsx` | `StatusChip` | `{ label, color?: "default" | "success" | "error" | "warning" | "info", onClick?, size?, ...ChipProps }` | +| `src/admin/ui/Tabs.tsx` | `Tabs`, `TabPanel` | `Tabs: { value, onChange(v), tabs: {value,label}[] }`; `TabPanel: { value, current, children }` | +| `src/admin/ui/Pagination.tsx` | `Pagination` | `{ page, pageCount, onChange(p) }` (renders nothing if `pageCount<=1`) | +| `src/admin/ui/Checkbox.tsx` | `CheckboxField` | `{ label, checked, onChange(b) }` (FormControlLabel + Checkbox) | +| `src/admin/ui/Alert.tsx` | `Alert` | `{ severity, children, onClose? }` (inline MUI Alert) | +| `src/admin/ui/DataTable.tsx` | extend | add `DataColumn.sortKey?`; `DataTableProps.{ sortBy?, sortDir?, onSort?(key) }`; render `TableSortLabel` in sortable headers | +| `src/admin/ui/DateField.tsx` | `DateField` | `{ value: string("yyyy-MM-dd" | ""), onChange(v: string), ...}`over MUI X`DatePicker`, format `dd.MM.yyyy` | +| `src/admin/ui/MuiProvider.tsx` | extend | wrap children in `LocalizationProvider` (`AdapterDateFns`, cs locale) so date pickers work app-wide | +| `src/admin/ui/index.ts` | extend | export all new components | +| `src/admin/pages/UiKit.tsx` | extend | showcase each new component in both themes | + +Deferred (build when their page is migrated): `Autocomplete` (warehouse pickers), `FilterBar` layout helper. + +--- + +## Task 1: `Select`, `StatusChip`, `CheckboxField`, `Alert` (simple wrappers) + +**Files:** Create the four files; modify `index.ts`. + +- [ ] **Step 1: `Select.tsx`** + +```tsx +import MuiTextField, { type TextFieldProps } from "@mui/material/TextField"; +import MenuItem from "@mui/material/MenuItem"; + +export interface SelectOption { + value: string | number; + label: string; +} +type Props = Omit & { + value: string | number; + onChange: (value: string) => void; + options?: SelectOption[]; +}; + +/** Dropdown styled like the kit TextField; composes inside . */ +export default function Select({ + value, + onChange, + options, + children, + ...props +}: Props) { + return ( + onChange(e.target.value)} + {...props} + > + {options + ? options.map((o) => ( + + {o.label} + + )) + : children} + + ); +} +``` + +- [ ] **Step 2: `StatusChip.tsx`** + +```tsx +import Chip, { type ChipProps } from "@mui/material/Chip"; + +type Props = Omit & { + color?: "default" | "success" | "error" | "warning" | "info"; +}; + +/** Semantic status chip. Pass onClick to make it a toggle. */ +export default function StatusChip({ + color = "default", + size = "small", + onClick, + sx, + ...props +}: Props) { + return ( + + ); +} +``` + +- [ ] **Step 3: `Checkbox.tsx`** + +```tsx +import FormControlLabel from "@mui/material/FormControlLabel"; +import MuiCheckbox from "@mui/material/Checkbox"; + +export function CheckboxField({ + label, + checked, + onChange, +}: { + label: React.ReactNode; + checked: boolean; + onChange: (v: boolean) => void; +}) { + return ( + onChange(e.target.checked)} + /> + } + label={label} + /> + ); +} +``` + +(Import `React` type or use `import type { ReactNode } from "react"` and type `label: ReactNode`.) + +- [ ] **Step 4: `Alert.tsx`** + +```tsx +import type { ReactNode } from "react"; +import MuiAlert from "@mui/material/Alert"; + +export default function Alert({ + severity = "info", + children, + onClose, +}: { + severity?: "success" | "error" | "warning" | "info"; + children: ReactNode; + onClose?: () => void; +}) { + return ( + + {children} + + ); +} +``` + +- [ ] **Step 5:** export from `index.ts`: `Select` (+ `type SelectOption`), `StatusChip`, `{ CheckboxField }`, `Alert`. +- [ ] **Step 6: Verify** `tsc -b` clean, `build:client` OK. **Commit** (`feat(mui): kit — Select, StatusChip, CheckboxField, Alert`; + trailer). + +--- + +## Task 2: `Tabs` + `TabPanel`, `Pagination` + +**Files:** Create `Tabs.tsx`, `Pagination.tsx`; modify `index.ts`. + +- [ ] **Step 1: `Tabs.tsx`** + +```tsx +import type { ReactNode } from "react"; +import MuiTabs from "@mui/material/Tabs"; +import Tab from "@mui/material/Tab"; +import Box from "@mui/material/Box"; + +export interface TabDef { + value: string; + label: string; +} + +export function Tabs({ + value, + onChange, + tabs, +}: { + value: string; + onChange: (v: string) => void; + tabs: TabDef[]; +}) { + return ( + onChange(v)} + sx={{ + borderBottom: 1, + borderColor: "divider", + minHeight: 44, + "& .MuiTab-root": { textTransform: "none", fontWeight: 600 }, + }} + > + {tabs.map((t) => ( + + ))} + + ); +} + +export function TabPanel({ + value, + current, + children, +}: { + value: string; + current: string; + children: ReactNode; +}) { + if (value !== current) return null; + return {children}; +} +``` + +- [ ] **Step 2: `Pagination.tsx`** + +```tsx +import Box from "@mui/material/Box"; +import MuiPagination from "@mui/material/Pagination"; + +export default function Pagination({ + page, + pageCount, + onChange, +}: { + page: number; + pageCount: number; + onChange: (p: number) => void; +}) { + if (pageCount <= 1) return null; + return ( + + onChange(p)} + color="primary" + shape="rounded" + /> + + ); +} +``` + +- [ ] **Step 3:** export `{ Tabs, TabPanel }` (+ `type TabDef`) and `Pagination` from `index.ts`. +- [ ] **Step 4: Verify** `tsc -b` clean, `build:client` OK. **Commit** (`feat(mui): kit — Tabs/TabPanel + Pagination`; + trailer). + +--- + +## Task 3: Sortable `DataTable` + +**Files:** Modify `src/admin/ui/DataTable.tsx`. + +- [ ] **Step 1:** Extend the types: + - `DataColumn`: add `sortKey?: string;` + - `DataTableProps`: add `sortBy?: string; sortDir?: "asc" | "desc"; onSort?: (key: string) => void;` +- [ ] **Step 2:** In the header cell render, if `c.sortKey && onSort` is set, wrap the header in `TableSortLabel`: + +```tsx +import TableSortLabel from "@mui/material/TableSortLabel"; +// ...header cell: +{ + c.sortKey && onSort ? ( + onSort(c.sortKey!)} + > + {c.header} + + ) : ( + c.header + ); +} +``` + +Non-sortable columns (no `sortKey`) render the plain header (unchanged). The existing `bold`/`mono`/`align`/`rowInactive`/`empty` behavior is untouched. + +- [ ] **Step 3: Verify** `tsc -b` clean, `build:client` OK, `vitest run` (148). **Commit** (`feat(mui): kit — sortable DataTable headers (TableSortLabel)`; + trailer). + +--- + +## Task 4: `DateField` + LocalizationProvider + +**Files:** Create `src/admin/ui/DateField.tsx`; modify `MuiProvider.tsx`, `index.ts`. + +- [ ] **Step 1: Add `LocalizationProvider` to `MuiProvider.tsx`** wrapping its children, so date pickers work everywhere: + +```tsx +import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; +import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFnsV3"; +import { cs } from "date-fns/locale"; +// inside MuiProvider, wrap {children} (and the sync) : + + + {children} +; +``` + +> Verify the exact adapter import path for the installed `@mui/x-date-pickers@8` against its docs (it may be `@mui/x-date-pickers/AdapterDateFns` rather than `...V3`). Pick the one that resolves; report which. + +- [ ] **Step 2: `DateField.tsx`** — string-in / string-out (`yyyy-MM-dd`) to match existing form state: + +```tsx +import { DatePicker } from "@mui/x-date-pickers/DatePicker"; +import { parseISO, format, isValid } from "date-fns"; + +export default function DateField({ + value, + onChange, + ...props +}: { value: string; onChange: (v: string) => void } & Record) { + const dateValue = value ? parseISO(value) : null; + return ( + onChange(d && isValid(d) ? format(d, "yyyy-MM-dd") : "")} + format="dd.MM.yyyy" + slotProps={{ textField: { size: "small", fullWidth: true } }} + {...props} + /> + ); +} +``` + +- [ ] **Step 3:** export `DateField` from `index.ts`. +- [ ] **Step 4: Verify** `tsc -b` clean, `build:client` OK. **Commit** (`feat(mui): kit — DateField over MUI X + cs LocalizationProvider`; + trailer). + +--- + +## Task 5: Extend `/ui-kit` showcase + final review + +**Files:** Modify `src/admin/pages/UiKit.tsx`. + +- [ ] **Step 1:** Add a section rendering each new component with sample data, in the existing `ScopedCssBaseline` page: a `Select` (3 options), `StatusChip`s (each color), `Tabs`+`TabPanel` (2 tabs), `Pagination` (page 2 of 5), `CheckboxField`, `Alert` (one of each severity), a sortable `DataTable` (3 columns, one `sortKey`, local sort state), and a `DateField`. +- [ ] **Step 2: Verify** `tsc -b` clean, `npm run build` (server+client) OK, `npx vitest run` (148). **Commit** (`feat(mui): showcase new kit components in /ui-kit`; + trailer). +- [ ] **Step 3:** Final review of all new components (parallel/adversarial), then finishing-a-development-branch after the user's `/ui-kit` visual check. + +--- + +## Self-review notes + +- All new components compose with the existing `Field` (Select/DateField are inputs; Field provides the label) and follow the size=small/outlined/fullWidth defaults. +- `DataTable` sort is **additive** — existing callers (Vehicles) are unaffected (no `sortKey`/`onSort` → plain headers). +- `DateField` is string-in/string-out so it drops into existing form state (`start_date: ""`), and uses cs locale + `dd.MM.yyyy` (matching the date convention in CLAUDE.md). +- Deferred: `Autocomplete`, `FilterBar` — built when warehouse/list pages are migrated.