"use client"; import { useState } from "react"; import Link from "next/link"; import { useQuery } from "@tanstack/react-query"; import { useTranslations } from "next-intl"; import { fetchContentList, fetchCollections } from "@/lib/api"; import type { FieldDefinition } from "@/lib/api"; import { getOptionLabel } from "@/lib/referenceOptionLabel"; import { SearchableSelect, type SearchableSelectOption, } from "./SearchableSelect"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Button } from "@/components/ui/button"; type Props = { name: string; def: FieldDefinition; value: string[] | string; onChange: (value: string[]) => void; required?: boolean; error?: unknown; locale?: string; label: React.ReactNode; }; /** Referenced collection(s) from schema: single collection or list for polymorphic. */ function getCollections(def: FieldDefinition): string[] { const items = def.items as FieldDefinition | undefined; if (!items) return []; if (items.collection) return [items.collection]; if (Array.isArray(items.collections) && items.collections.length > 0) return items.collections; return []; } export function ReferenceArrayField({ name, def, value, onChange, required, error, locale, label, }: Props) { const t = useTranslations("ReferenceArrayField"); const schemaCollections = getCollections(def); const singleCollection = schemaCollections.length === 1 ? schemaCollections[0] : null; const multipleCollections = schemaCollections.length > 1 ? schemaCollections : []; /** Normalise to string[]: API may return resolved refs as objects with _slug. */ function toSlugList(raw: unknown): string[] { if (Array.isArray(raw)) { return raw .map((v) => typeof v === "object" && v !== null && "_slug" in v ? String((v as { _slug?: string })._slug ?? "") : typeof v === "string" ? v.trim() : "", ) .filter(Boolean); } if (typeof raw === "string") { return raw .split(/[\n,]+/) .map((s) => s.trim()) .filter(Boolean); } return []; } const valueList: string[] = toSlugList(value); const [pickedCollection, setPickedCollection] = useState( singleCollection ?? multipleCollections[0] ?? "", ); const { data: collectionsData } = useQuery({ queryKey: ["collections"], queryFn: fetchCollections, enabled: schemaCollections.length === 0, }); const availableCollections = schemaCollections.length > 0 ? schemaCollections : (collectionsData?.collections ?? []) .map((c) => c.name) .filter( (n) => n !== "content_layout" && n !== "component_layout" && n !== "seo", ); const effectiveCollection = singleCollection ?? pickedCollection; const listParams = { _per_page: 200, ...(locale ? { _locale: locale } : {}) }; const singleQuery = useQuery({ queryKey: ["content", effectiveCollection, listParams], queryFn: () => fetchContentList(effectiveCollection!, listParams), enabled: !!effectiveCollection && schemaCollections.length <= 1, }); const multiQueries = useQuery({ queryKey: ["content-multi", multipleCollections, listParams], queryFn: async () => { const results = await Promise.all( multipleCollections.map((coll) => fetchContentList(coll, listParams)), ); return multipleCollections.map((coll, i) => ({ collection: coll, items: results[i]?.items ?? [], })); }, enabled: multipleCollections.length > 1, }); type OptionItem = { slug: string; collection: string }; const options: OptionItem[] = multipleCollections.length > 1 && multiQueries.data ? multiQueries.data.flatMap(({ collection: coll, items }) => (items as { _slug?: string }[]) .map((o) => ({ slug: String(o._slug ?? ""), collection: coll, })) .filter((o) => o.slug), ) : ((singleQuery.data?.items ?? []) as { _slug?: string }[]) .map((o) => ({ slug: String(o._slug ?? ""), collection: effectiveCollection ?? "", })) .filter((o) => o.slug); const isLoading = schemaCollections.length <= 1 ? singleQuery.isLoading : multiQueries.isLoading; const add = (slug: string) => { if (!slug || valueList.includes(slug)) return; onChange([...valueList, slug]); }; const addFromOption = (optionValue: string) => { const slug = optionValue.includes(":") ? optionValue.split(":")[1]! : optionValue; add(slug); }; /** Options for the "add existing" SearchableSelect: full label, exclude already selected. */ const addSelectOptions: SearchableSelectOption[] = multipleCollections.length > 1 && multiQueries.data ? multiQueries.data.flatMap(({ collection: coll, items: list }) => (list as Record[]) .filter((i) => !valueList.includes(String(i._slug ?? ""))) .map((i) => ({ value: `${coll}:${i._slug ?? ""}`, label: getOptionLabel(i), })) ) : ((singleQuery.data?.items ?? []) as Record[]) .filter((i) => !valueList.includes(String(i._slug ?? ""))) .map((i) => ({ value: String(i._slug ?? ""), label: getOptionLabel(i), })); const remove = (index: number) => { onChange(valueList.filter((_, i) => i !== index)); }; const move = (index: number, dir: number) => { const next = index + dir; if (next < 0 || next >= valueList.length) return; const copy = [...valueList]; const tmp = copy[index]; copy[index] = copy[next]; copy[next] = tmp; onChange(copy); }; const collectionsForNew = multipleCollections.length > 1 ? multipleCollections : effectiveCollection ? [effectiveCollection] : []; return (
{label} {schemaCollections.length > 0 ? (

{schemaCollections.length === 1 ? t("typeLabel", { collection: schemaCollections[0] }) : t("typesLabel", { collections: schemaCollections.join(", ") })}

) : null}
{/* Selected entries */}
    {valueList.map((slug, index) => { const collForSlug = options.find((o) => o.slug === slug)?.collection ?? effectiveCollection ?? null; return (
  • {slug}
    {collForSlug && ( → )}
  • ); })}
{/* Without schema collection: choose component type first */} {!singleCollection && schemaCollections.length === 0 && availableCollections.length > 0 ? (
) : null} {/* Add: select from existing + create new component */} {effectiveCollection || multipleCollections.length > 1 ? (
v && addFromOption(v)} options={addSelectOptions} placeholder={t("selectFromExisting")} clearable={false} filterPlaceholder={t("filterPlaceholder")} emptyLabel={t("emptyLabel")} disabled={isLoading} ariaLabel={t("selectExistingAriaLabel")} />
{collectionsForNew.length > 0 ? ( {collectionsForNew.length === 1 ? ( ) : ( )} {t("openInNewTab")} ) : null}
) : null} {schemaCollections.length === 0 && availableCollections.length === 0 ? (

{t("noCollection", { collectionCode: "items.collection", collectionsCode: "items.collections", })}

) : null} {error ? (

{String((error as { message?: string })?.message ?? error)}

) : null}
); }