// Object Field Template - renders nested object fields import type { ObjectFieldTemplateProps } from "@rjsf/utils"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; import { Button } from "@/components/ui/button"; import { useState } from "react"; import { LuChevronDown, LuChevronRight } from "react-icons/lu"; import { useTranslation } from "react-i18next"; import { cn } from "@/lib/utils"; export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) { const { title, description, properties, uiSchema } = props; const formContext = (props as Record).formContext as | Record | undefined; // Check if this is a root-level object const isRoot = !title; const [isOpen, setIsOpen] = useState(true); const { t } = useTranslation([ (formContext?.i18nNamespace as string | undefined) || "common", ]); const groupDefinitions = (uiSchema?.["ui:groups"] as Record | undefined) || {}; const isHiddenProp = (prop: (typeof properties)[number]) => prop.content.props.uiSchema?.["ui:widget"] === "hidden"; const visibleProps = properties.filter((prop) => !isHiddenProp(prop)); // Check for advanced section grouping const advancedProps = visibleProps.filter( (p) => p.content.props.uiSchema?.["ui:options"]?.advanced === true, ); const regularProps = visibleProps.filter( (p) => p.content.props.uiSchema?.["ui:options"]?.advanced !== true, ); const [showAdvanced, setShowAdvanced] = useState(false); const toTitle = (value: string) => value.replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase()); const renderGroupedFields = (items: (typeof properties)[number][]) => { if (!items.length) { return null; } const grouped = new Set(); const groups = Object.entries(groupDefinitions) .map(([groupKey, fields]) => { const ordered = fields .map((field) => items.find((item) => item.name === field)) .filter(Boolean) as (typeof properties)[number][]; if (ordered.length === 0) { return null; } ordered.forEach((item) => grouped.add(item.name)); const label = t(`groups.${groupKey}`, { defaultValue: toTitle(groupKey), }); return { key: groupKey, label, items: ordered, }; }) .filter(Boolean) as Array<{ key: string; label: string; items: (typeof properties)[number][]; }>; const ungrouped = items.filter((item) => !grouped.has(item.name)); return (
{groups.map((group) => (
{group.label}
{group.items.map((element) => (
{element.content}
))}
))} {ungrouped.length > 0 && (
0 && "pt-2")}> {ungrouped.map((element) => (
{element.content}
))}
)}
); }; // Root level renders children directly if (isRoot) { return (
{renderGroupedFields(regularProps)} {advancedProps.length > 0 && ( {renderGroupedFields(advancedProps)} )}
); } // Nested objects render as collapsible cards return (
{title} {description && (

{description}

)}
{isOpen ? ( ) : ( )}
{renderGroupedFields(regularProps)} {advancedProps.length > 0 && ( {renderGroupedFields(advancedProps)} )}
); }