From 088fe39babb8ec4430cc88435f337dbb90015164 Mon Sep 17 00:00:00 2001 From: BOHA Date: Sun, 7 Jun 2026 08:34:58 +0200 Subject: [PATCH] chore(mui): delete 8 dead legacy components replaced by the kit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed (zero importers, build-verified): FormField, Pagination, AdminDatePicker, SortIcon, warehouse Supplier/Location/Batch selects, WarehouseMovementTable. FormModal + ConfirmModal kept (still used by OrderConfirmationModal + ProjectFileManager — to be migrated before full removal). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/admin/components/AdminDatePicker.tsx | 233 ---------------- src/admin/components/FormField.tsx | 43 --- src/admin/components/Pagination.tsx | 113 -------- src/admin/components/SortIcon.tsx | 36 --- .../components/warehouse/BatchPicker.tsx | 74 ------ .../components/warehouse/LocationSelect.tsx | 31 --- .../components/warehouse/SupplierSelect.tsx | 33 --- .../warehouse/WarehouseMovementTable.tsx | 249 ------------------ 8 files changed, 812 deletions(-) delete mode 100644 src/admin/components/AdminDatePicker.tsx delete mode 100644 src/admin/components/FormField.tsx delete mode 100644 src/admin/components/Pagination.tsx delete mode 100644 src/admin/components/SortIcon.tsx delete mode 100644 src/admin/components/warehouse/BatchPicker.tsx delete mode 100644 src/admin/components/warehouse/LocationSelect.tsx delete mode 100644 src/admin/components/warehouse/SupplierSelect.tsx delete mode 100644 src/admin/components/warehouse/WarehouseMovementTable.tsx diff --git a/src/admin/components/AdminDatePicker.tsx b/src/admin/components/AdminDatePicker.tsx deleted file mode 100644 index 3e59f18..0000000 --- a/src/admin/components/AdminDatePicker.tsx +++ /dev/null @@ -1,233 +0,0 @@ -import { forwardRef } from "react"; -import DatePicker, { registerLocale } from "react-datepicker"; -import { cs } from "date-fns/locale"; -import { parse, format } from "date-fns"; -import "react-datepicker/dist/react-datepicker.css"; - -registerLocale("cs", cs); - -// Ensure portal root exists -if ( - typeof document !== "undefined" && - !document.getElementById("datepicker-portal") -) { - const el = document.createElement("div"); - el.id = "datepicker-portal"; - document.body.appendChild(el); -} - -const isTouchDevice = () => - typeof window !== "undefined" && - ("ontouchstart" in window || navigator.maxTouchPoints > 0); - -interface CustomInputProps { - value?: string; - onClick?: () => void; - onChange?: (e: React.ChangeEvent) => void; - placeholder?: string; - required?: boolean; - readOnly?: boolean; - disabled?: boolean; -} - -const CustomInput = forwardRef( - ( - { value, onClick, onChange, placeholder, required, readOnly, disabled }, - ref, - ) => ( - - ), -); - -interface NativeInputProps { - mode: string; - value: string; - onChange: (value: string) => void; - required?: boolean; - minDate?: string; - maxDate?: string; - disabled?: boolean; - placeholder?: string; -} - -const modeToInputType: Record = { - month: "month", - time: "time", -}; - -function NativeInput({ - mode, - value, - onChange, - required, - minDate, - maxDate, - disabled, - placeholder, -}: NativeInputProps) { - const type = modeToInputType[mode] || "date"; - // For time inputs, min/max must be in HH:mm format, not date format - const formatTimeMinMax = (val: string | undefined): string | undefined => { - if (!val) return undefined; - // If it looks like a date string (yyyy-MM-dd), extract time portion if present, - // otherwise it's not a valid time min/max — return undefined - if (val.includes("T")) return val.split("T")[1]?.substring(0, 5); - if (val.includes(":")) return val.substring(0, 5); - return undefined; - }; - const minProp = - mode === "time" ? formatTimeMinMax(minDate) : minDate || undefined; - const maxProp = - mode === "time" ? formatTimeMinMax(maxDate) : maxDate || undefined; - return ( - onChange(e.target.value)} - className="admin-form-input" - placeholder={placeholder} - required={required} - disabled={disabled} - min={minProp} - max={maxProp} - /> - ); -} - -interface AdminDatePickerProps { - mode?: "date" | "month" | "time"; - value: string; - onChange: (value: string) => void; - minDate?: string; - maxDate?: string; - disabled?: boolean; - placeholder?: string; - required?: boolean; -} - -export default function AdminDatePicker({ - mode = "date", - value, - onChange, - required, - minDate, - maxDate, - disabled, - placeholder, -}: AdminDatePickerProps) { - const useNative = isTouchDevice(); - - // Mode-aware default placeholder so empty date fields always show a format - // hint (callers can still override, e.g. "Od"/"Do" on range filters). - const effectivePlaceholder = - placeholder ?? - (mode === "time" ? "hh:mm" : mode === "month" ? "mm.rrrr" : "dd.mm.rrrr"); - - if (useNative) { - return ( - - ); - } - - const toDate = (val: string | null | undefined): Date | null => { - if (!val) return null; - try { - if (mode === "date") return parse(val, "yyyy-MM-dd", new Date()); - if (mode === "time") { - const [h, m] = val.split(":"); - const d = new Date(); - d.setHours(parseInt(h, 10), parseInt(m, 10), 0, 0); - return d; - } - if (mode === "month") return parse(val, "yyyy-MM", new Date()); - } catch { - return null; - } - return null; - }; - - const handleChange = (date: Date | null) => { - if (!date) { - onChange(""); - return; - } - if (mode === "date") onChange(format(date, "yyyy-MM-dd")); - else if (mode === "time") onChange(format(date, "HH:mm")); - else if (mode === "month") onChange(format(date, "yyyy-MM")); - }; - - const parseMinMax = (val: string | undefined): Date | undefined => { - if (!val) return undefined; - try { - if (mode === "date") return parse(val, "yyyy-MM-dd", new Date()); - if (mode === "month") return parse(val, "yyyy-MM", new Date()); - } catch { - return undefined; - } - return undefined; - }; - - const customInput = ( - - ); - - const commonProps = { - selected: toDate(value), - onChange: handleChange, - locale: "cs", - customInput, - placeholderText: effectivePlaceholder, - minDate: parseMinMax(minDate), - maxDate: parseMinMax(maxDate), - popperPlacement: "bottom-start" as const, - portalId: "datepicker-portal", - disabled, - }; - - if (mode === "time") { - return ( - - ); - } - - if (mode === "month") { - return ( - - ); - } - - return ; -} diff --git a/src/admin/components/FormField.tsx b/src/admin/components/FormField.tsx deleted file mode 100644 index 8384e41..0000000 --- a/src/admin/components/FormField.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { - type CSSProperties, - type ReactNode, - isValidElement, - cloneElement, - useId, -} from "react"; - -interface FormFieldProps { - label: ReactNode; - children: ReactNode; - error?: string; - required?: boolean; - style?: React.CSSProperties; -} - -export default function FormField({ - label, - children, - error, - required, - style, -}: FormFieldProps) { - const generatedId = useId(); - const childProps = isValidElement(children) - ? (children.props as Record) - : null; - const childId = childProps?.id ? String(childProps.id) : generatedId; - const childWithId = isValidElement(children) - ? cloneElement(children, { id: childId } as React.Attributes) - : children; - - return ( -
- - {childWithId} - {error && {error}} -
- ); -} diff --git a/src/admin/components/Pagination.tsx b/src/admin/components/Pagination.tsx deleted file mode 100644 index 3f54bab..0000000 --- a/src/admin/components/Pagination.tsx +++ /dev/null @@ -1,113 +0,0 @@ -interface PaginationProps { - pagination: { - total: number; - page: number; - per_page: number; - total_pages: number; - } | null; - onPageChange: (page: number) => void; - onPerPageChange?: (perPage: number) => void; -} - -export default function Pagination({ - pagination, - onPageChange, - onPerPageChange, -}: PaginationProps) { - if (!pagination || pagination.total_pages <= 1) return null; - - const { page, total_pages, total } = pagination; - - const getPages = () => { - const pages: (number | string)[] = []; - const delta = 2; - for (let i = 1; i <= total_pages; i++) { - if ( - i === 1 || - i === total_pages || - (i >= page - delta && i <= page + delta) - ) { - pages.push(i); - } else if (pages[pages.length - 1] !== "...") { - pages.push("..."); - } - } - return pages; - }; - - return ( -
-
{total} záznamů
-
- - {getPages().map((p, i) => - typeof p === "string" ? ( - - ... - - ) : ( - - ), - )} - -
- {onPerPageChange && ( - - )} -
- ); -} diff --git a/src/admin/components/SortIcon.tsx b/src/admin/components/SortIcon.tsx deleted file mode 100644 index 093ed22..0000000 --- a/src/admin/components/SortIcon.tsx +++ /dev/null @@ -1,36 +0,0 @@ -interface SortIconProps { - column: string; - sort: string | null; - order: string; -} - -export default function SortIcon({ column, sort, order }: SortIconProps) { - if (sort !== column) { - return ( - - - - ); - } - return ( - - {order === "asc" ? : } - - ); -} diff --git a/src/admin/components/warehouse/BatchPicker.tsx b/src/admin/components/warehouse/BatchPicker.tsx deleted file mode 100644 index 5365486..0000000 --- a/src/admin/components/warehouse/BatchPicker.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; -import { jsonQuery } from "../../lib/apiAdapter"; - -interface Batch { - id: number; - quantity: number; - unit_price: number; - received_at: string; - is_consumed: boolean; -} - -interface BatchesResponse { - batches: Batch[]; - total_stock: number; - reserved_quantity: number; - available_quantity: number; -} - -interface BatchPickerProps { - itemId: number | null; - value: number | null; - onChange: (batchId: number, unitPrice: number) => void; -} - -export default function BatchPicker({ - itemId, - value, - onChange, -}: BatchPickerProps) { - const { data: response } = useQuery({ - queryKey: ["warehouse", "batches", itemId], - queryFn: () => - jsonQuery( - `/api/admin/warehouse/items/${itemId}/batches`, - ), - enabled: !!itemId, - }); - - const batches = response?.batches ?? []; - - return ( -
- {response && response.reserved_quantity > 0 && ( -
- Dostupné: {response.available_quantity} ks (z toho rezervováno:{" "} - {response.reserved_quantity} ks) -
- )} - -
- ); -} diff --git a/src/admin/components/warehouse/LocationSelect.tsx b/src/admin/components/warehouse/LocationSelect.tsx deleted file mode 100644 index f57e51d..0000000 --- a/src/admin/components/warehouse/LocationSelect.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; -import { - warehouseLocationListOptions, - type WarehouseLocation, -} from "../../lib/queries/warehouse"; - -interface LocationSelectProps { - value: number | null; - onChange: (locationId: number | null) => void; -} - -export default function LocationSelect({ - value, - onChange, -}: LocationSelectProps) { - const { data: locations } = useQuery(warehouseLocationListOptions()); - return ( - - ); -} diff --git a/src/admin/components/warehouse/SupplierSelect.tsx b/src/admin/components/warehouse/SupplierSelect.tsx deleted file mode 100644 index 9640339..0000000 --- a/src/admin/components/warehouse/SupplierSelect.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; -import { - warehouseSupplierListOptions, - type WarehouseSupplier, -} from "../../lib/queries/warehouse"; - -interface SupplierSelectProps { - value: number | null; - onChange: (supplierId: number | null) => void; -} - -export default function SupplierSelect({ - value, - onChange, -}: SupplierSelectProps) { - const { data } = useQuery(warehouseSupplierListOptions({ perPage: 100 })); - const suppliers = data?.data ?? []; - return ( - - ); -} diff --git a/src/admin/components/warehouse/WarehouseMovementTable.tsx b/src/admin/components/warehouse/WarehouseMovementTable.tsx deleted file mode 100644 index 69dc984..0000000 --- a/src/admin/components/warehouse/WarehouseMovementTable.tsx +++ /dev/null @@ -1,249 +0,0 @@ -import { ReactNode, useId, useRef } from "react"; -import ItemPicker from "./ItemPicker"; -import LocationSelect from "./LocationSelect"; -import BatchPicker from "./BatchPicker"; - -export interface MovementItem { - key: string; - item_id: number | null; - item_name?: string; - quantity: number; - unit_price: number; - location_id: number | null; - batch_id: number | null; - reservation_id: number | null; - notes: string | null; -} - -export type MovementRowRenderer = ( - item: T, - index: number, - updateItem: (field: string, value: unknown) => void, - removeItem: () => void, -) => ReactNode; - -interface WarehouseMovementTableProps { - items: T[]; - onChange: (items: T[]) => void; - mode: "receipt" | "issue" | "inventory"; - defaultItem?: () => Omit; - renderRow?: MovementRowRenderer; - renderHeader?: () => ReactNode; -} - -function parseDecimal(raw: string): number { - // Strip leading minus if you want negative support; here we want only positive - const cleaned = raw.replace(/[eE+\-]/g, ""); - const n = Number(cleaned); - return Number.isFinite(n) ? n : 0; -} - -const builtInDefaultMovementItem = (): Omit => ({ - item_id: null, - quantity: 0, - unit_price: 0, - location_id: null, - batch_id: null, - reservation_id: null, - notes: null, -}); - -export default function WarehouseMovementTable({ - items, - onChange, - mode, - defaultItem, - renderRow, - renderHeader, -}: WarehouseMovementTableProps) { - const baseId = useId(); - const counterRef = useRef(0); - const nextKey = () => `${baseId}-item-${counterRef.current++}`; - - const addItem = () => { - const factory = - defaultItem ?? - (builtInDefaultMovementItem as unknown as () => Omit); - onChange([...items, { ...(factory() as object), key: nextKey() } as T]); - }; - const removeItem = (index: number) => { - onChange(items.filter((_, i) => i !== index)); - }; - const updateItem = (index: number, field: string, value: unknown) => { - const updated = [...items]; - updated[index] = { ...updated[index], [field]: value }; - onChange(updated); - }; - - // If a custom row renderer is supplied, the caller is fully in charge of - // the row layout (and typically the column headers too — see inventory mode). - if (renderRow) { - return ( -
-
- - {renderHeader && ( - - {renderHeader()} - - )} - - {items.map((item, index) => ( - - {renderRow( - item, - index, - (field, value) => updateItem(index, field, value), - () => removeItem(index), - )} - - ))} - -
-
- -
- ); - } - - // Default rendering: only valid for "receipt" | "issue" — narrow at runtime. - const movementItems = items as unknown as MovementItem[]; - - return ( -
-
- - - - - {mode === "issue" && ( - - )} - - - - - - - - - {movementItems.map((item, index) => ( - - - {mode === "issue" && ( - - )} - - - - - - - ))} - -
PoložkaŠaržeMnožstvíCena/ksLokacePoznámkaAkce
- updateItem(index, "item_id", id)} - /> - - { - updateItem(index, "batch_id", batchId); - updateItem(index, "unit_price", unitPrice); - }} - /> - - - updateItem( - index, - "quantity", - parseDecimal(e.target.value), - ) - } - min="0" - step="0.001" - inputMode="decimal" - pattern="[0-9]+([\.,][0-9]+)?" - aria-label={`Množství, řádek ${index + 1}`} - /> - - - updateItem( - index, - "unit_price", - parseDecimal(e.target.value), - ) - } - min="0" - step="0.01" - disabled={mode === "issue"} - inputMode="decimal" - pattern="[0-9]+([\.,][0-9]+)?" - aria-label={`Cena za kus, řádek ${index + 1}`} - /> - - updateItem(index, "location_id", id)} - /> - - updateItem(index, "notes", e.target.value)} - /> - -
- -
-
-
- -
- ); -}