import { useMemo, useState } from "react"; import { ActionIcon, Badge, Box, Button, Card, Code, Group, SimpleGrid, Stack, Table, Tabs, Text, TextInput, ThemeIcon, } from "@mantine/core"; import { ArrowDownToLine, ArrowUpFromLine, FileUp, FolderOpen, HardDrive, Inbox, Layers, Paperclip, Search, Settings, TrainFront, X, } from "lucide-react"; import { useQuery } from "@tanstack/react-query"; import { KpiStrip, PageContainer, PageHeader } from "@/components/page"; import { api } from "@/services/api"; import { getMinFiles, type FileUploadSetting } from "@/types/fileUploadSettings"; import ManageFileUploadFieldsDialog from "./ManageFileUploadFieldsDialog"; type DocCategory = "import" | "export" | "intercity" | "other"; function categorize(setting: FileUploadSetting): DocCategory { const code = setting.code.toLowerCase(); if (code.includes("intercity")) return "intercity"; if (code.includes("export")) return "export"; if (code.includes("import")) return "import"; return "other"; } export default function FileUploadSettingsPage() { const [query, setQuery] = useState(""); const { data, isLoading, isError, error, refetch } = useQuery( api.fileUploadSettings.list.queryOptions(), ); const fileUploadSettings = useMemo( () => (Array.isArray(data) ? data : []), [data], ); const filtered = useMemo(() => { const q = query.trim().toLowerCase(); if (!q) return fileUploadSettings; return fileUploadSettings.filter( (s) => s.code.toLowerCase().includes(q) || s.label.toLowerCase().includes(q) || (s.description ?? "").toLowerCase().includes(q) || s.fields.some( (f) => f.fileKey.toLowerCase().includes(q) || f.fileLabel.toLowerCase().includes(q), ), ); }, [fileUploadSettings, query]); const byCategory = useMemo(() => { const groups: Record = { import: [], export: [], intercity: [], other: [], }; for (const s of filtered) groups[categorize(s)].push(s); return groups; }, [filtered]); const totalFields = fileUploadSettings.reduce( (sum, s) => sum + s.fields.length, 0, ); const requiredFields = fileUploadSettings.reduce( (sum, s) => sum + s.fields.filter((f) => f.isRequired).length, 0, ); const multiFields = fileUploadSettings.reduce( (sum, s) => sum + s.fields.filter((f) => f.isMultiple).length, 0, ); const gridStatus = isLoading ? "loading" : isError ? "error" : "success"; return ( }> Import }> Export }> Intercity }> Other {(["import", "export", "intercity", "other"] as const).map((tab) => ( } value={query} onChange={(e) => setQuery(e.currentTarget.value)} rightSection={ query ? ( setQuery("")} aria-label="Clear search" > ) : null } style={{ maxWidth: 420 }} /> {gridStatus === "error" ? ( Failed to load settings. {error instanceof Error ? error.message : "Unknown error."} ) : gridStatus === "loading" ? ( {Array.from({ length: 6 }).map((_, i) => ( ))} ) : byCategory[tab].length === 0 ? ( {query.trim() ? "No file upload settings match your search." : "No file upload settings in this category."} ) : ( {byCategory[tab].map((setting) => ( ))} )} ))} ); } function SettingCard({ setting }: { setting: FileUploadSetting }) { const required = setting.fields.filter((f) => f.isRequired).length; const multi = setting.fields.filter((f) => f.isMultiple).length; const maxSize = Math.max(0, ...setting.fields.map((f) => f.maxSizeMb)); return ( {setting.label} {setting.description ?? "No description"} {setting.entity ?? "—"} {setting.fields.length} field{setting.fields.length === 1 ? "" : "s"} {required > 0 && ( {required} required )} {multi > 0 && ( {multi} multi )} {setting.code} {maxSize ? `Max ${maxSize} MB per file` : "No size limit set"} ); } /** * Static reference: how `min_files` / `max_files` are derived from the * Required × Multiple toggles. Documentation aid for whoever wires uploaders. */ function BehaviorReferenceCard() { const rows: { required: boolean; multiple: boolean; min: string; max: string }[] = [ { required: false, multiple: false, min: "0", max: "1" }, { required: true, multiple: false, min: "1", max: "1" }, { required: false, multiple: true, min: "0", max: "field.maxFiles" }, { required: true, multiple: true, min: "1", max: "field.maxFiles" }, ]; return ( Required × Multiple behavior Min and max file counts are derived from these two toggles. The "Max files" you set on a field is only used when{" "} Multiple {" "} is on. Required Multiple min_files max_files {rows.map((r) => ( {r.required ? "Required" : "Optional"} {r.multiple ? "Multiple" : "Single"} {r.min} {r.max} ))}
Helpers getMinFiles and getEffectiveMaxFiles{" "} live in @/types/fileUploadSettings — use them when wiring real uploaders. Example: a field with isRequired=false,{" "} isMultiple=true, maxFiles=5 gives min{" "} {getMinFiles({ isRequired: false, })} {" "} …5.
); }