import type { FieldPathList, FieldProps, RJSFSchema } from "@rjsf/utils"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { cn } from "@/lib/utils"; import { LuChevronDown, LuChevronRight, LuPlus, LuTrash2, } from "react-icons/lu"; import type { ConfigFormContext } from "@/types/configForm"; import get from "lodash/get"; import { isSubtreeModified } from "../utils"; type KnownPlatesData = Record; export function KnownPlatesField(props: FieldProps) { const { schema, formData, onChange, idSchema, disabled, readonly } = props; const formContext = props.registry?.formContext as | ConfigFormContext | undefined; const { t } = useTranslation(["views/settings", "common"]); const data: KnownPlatesData = useMemo(() => { if (!formData || typeof formData !== "object" || Array.isArray(formData)) { return {}; } return formData as KnownPlatesData; }, [formData]); const entries = useMemo(() => Object.entries(data), [data]); const title = (schema as RJSFSchema).title; const description = (schema as RJSFSchema).description; const hasItems = entries.length > 0; const emptyPath = useMemo(() => [] as FieldPathList, []); const fieldPath = (props as { fieldPathId?: { path?: FieldPathList } }).fieldPathId?.path ?? emptyPath; const isModified = useMemo(() => { const baselineRoot = formContext?.baselineFormData; const baselineValue = baselineRoot ? get(baselineRoot, fieldPath) : undefined; return isSubtreeModified( data, baselineValue, formContext?.overrides, fieldPath, formContext?.formData, ); }, [fieldPath, formContext, data]); const [open, setOpen] = useState(hasItems || isModified); useEffect(() => { if (isModified) { setOpen(true); } }, [isModified]); useEffect(() => { if (hasItems) { setOpen(true); } }, [hasItems]); const handleAddEntry = useCallback(() => { const next = { ...data, "": [""] }; onChange(next, fieldPath); }, [data, fieldPath, onChange]); const handleRemoveEntry = useCallback( (key: string) => { const next = { ...data }; delete next[key]; onChange(next, fieldPath); }, [data, fieldPath, onChange], ); const handleRenameKey = useCallback( (oldKey: string, newKey: string) => { if (oldKey === newKey) return; // Preserve order by rebuilding the object const next: KnownPlatesData = {}; for (const [k, v] of Object.entries(data)) { if (k === oldKey) { next[newKey] = v; } else { next[k] = v; } } onChange(next, fieldPath); }, [data, fieldPath, onChange], ); const handleAddPlate = useCallback( (key: string) => { const next = { ...data, [key]: [...(data[key] || []), ""] }; onChange(next, fieldPath); }, [data, fieldPath, onChange], ); const handleRemovePlate = useCallback( (key: string, plateIndex: number) => { const plates = [...(data[key] || [])]; plates.splice(plateIndex, 1); const next = { ...data, [key]: plates }; onChange(next, fieldPath); }, [data, fieldPath, onChange], ); const handleUpdatePlate = useCallback( (key: string, plateIndex: number, value: string) => { const plates = [...(data[key] || [])]; plates[plateIndex] = value; const next = { ...data, [key]: plates }; onChange(next, fieldPath); }, [data, fieldPath, onChange], ); const baseId = idSchema?.$id || "known_plates"; const deleteLabel = t("button.delete", { ns: "common", defaultValue: "Delete", }); const namePlaceholder = t("configForm.knownPlates.namePlaceholder", { ns: "views/settings", }); const platePlaceholder = t("configForm.knownPlates.platePlaceholder", { ns: "views/settings", }); return (
{title} {description && (

{description}

)}
{open ? ( ) : ( )}
{entries.map(([key, plates], entryIndex) => { const entryId = `${baseId}-${entryIndex}`; return (
handleRenameKey(key, e.target.value)} className="flex-1" />
{plates.map((plate, plateIndex) => (
handleUpdatePlate(key, plateIndex, e.target.value) } className="flex-1" /> {plates.length > 1 && ( )}
))}
); })}
); } export default KnownPlatesField;