"use client" import { useState, type ReactNode } from "react" import { useController, useFormContext, type FieldValues, type FieldPath } from "react-hook-form" import { Button } from "@/shared/components/ui/button" import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card" import { Field, FieldError } from "@/shared/components/ui/field" import { Plus } from "lucide-react" import { ResourceSelectorDialog, type ResourceSelectorDialogProps, } from "./resource-selector-dialog" import type { ResourcePageClient, ResourceItem } from "@/shared/data-view/resource-page" export type RhfResourceFieldProps< TValues extends FieldValues, TName extends FieldPath, TClient extends ResourcePageClient, > = { /** RHF field name — value is an array of items after mapping */ name: TName /** Label displayed on the card header */ label: string /** Button text for the trigger */ triggerLabel?: string /** Map a selected resource row into the form item shape */ mapSelected: (row: ResourceItem) => TValues[TName][number] /** Render the list of selected items inside the card */ renderItems: ( items: TValues[TName], helpers: { remove: (index: number) => void update: (index: number, item: TValues[TName][number]) => void replace: (items: TValues[TName]) => void }, ) => ReactNode /** Props forwarded to ResourceSelectorDialog (columns, routeKey, getClient, etc.) */ dialogProps: Omit< ResourceSelectorDialogProps, "open" | "onOpenChange" | "onConfirm" > /** Deduplicate by this key when merging new selections. Defaults to "id" */ itemKey?: string } export function RhfResourceField< TValues extends FieldValues, TName extends FieldPath, TClient extends ResourcePageClient, >({ name, label, triggerLabel, mapSelected, renderItems, dialogProps, itemKey = "id", }: RhfResourceFieldProps) { const { control } = useFormContext() const { field, fieldState: { error }, } = useController({ name, control }) const [open, setOpen] = useState(false) const items: TValues[TName] = field.value ?? ([] as unknown as TValues[TName]) const handleConfirm = (rows: ResourceItem[]) => { const mapped = rows.map(mapSelected) // Merge: keep existing items, add new ones (deduplicate by itemKey) const existingKeys = new Set( (items as any[]).map((item: any) => String(item[itemKey])), ) const newItems = mapped.filter( (item: any) => !existingKeys.has(String(item[itemKey])), ) field.onChange([...items, ...newItems]) } const helpers = { remove: (index: number) => { const next = [...(items as any[])] next.splice(index, 1) field.onChange(next) }, update: (index: number, item: TValues[TName][number]) => { const next = [...(items as any[])] next[index] = item field.onChange(next) }, replace: (newItems: TValues[TName]) => { field.onChange(newItems) }, } return ( {label} { items.length > 0 && {(items as any[]).length > 0 && renderItems(items, helpers) } } {error && {error.message}} {...dialogProps} open={open} onOpenChange={setOpen} onConfirm={handleConfirm} /> ) }