import { useEffect, useMemo, useState } from "react"; import { Button } from "@govoplan/core-webui"; import { Dialog } from "@govoplan/core-webui"; import { DismissibleAlert } from "@govoplan/core-webui"; import type { TemplateNamespace, TemplatePlaceholder, UndefinedPlaceholder } from "../utils/templatePlaceholders"; export type TemplateFieldOption = { name: string; label: string; }; export function TemplateFieldChipList({ namespace, fields, usedPlaceholders = [], empty, disabled = false, onInsert }: { namespace: TemplateNamespace; fields: TemplateFieldOption[]; usedPlaceholders?: TemplatePlaceholder[]; empty: string; disabled?: boolean; onInsert: (namespace: TemplateNamespace, name: string) => void; }) { if (fields.length === 0) return

{empty}

; return (
{fields.map((field) => { const used = usedPlaceholders.some((placeholder) => placeholder.validNamespace && placeholder.namespace === namespace && placeholder.name === field.name); return ( ); })}
); } export function UndefinedPlaceholderList({ items, empty = "No undefined placeholders detected.", onSelect }: { items: UndefinedPlaceholder[]; empty?: string; onSelect: (item: UndefinedPlaceholder) => void; }) { if (items.length === 0) return

{empty}

; return (
{items.map((item) => ( ))}
); } export function UndefinedPlaceholderDecisionDialog({ field, contextLabel = "template", removeLabel = "Remove placeholder", localFields = [], globalFields = [], onCancel, onRemove, onReplace, onAddField, addDisabled = false }: { field: UndefinedPlaceholder | null; contextLabel?: string; removeLabel?: string; localFields?: TemplateFieldOption[]; globalFields?: TemplateFieldOption[]; onCancel: () => void; onRemove: (field: UndefinedPlaceholder) => void; onReplace?: (field: UndefinedPlaceholder, namespace: TemplateNamespace, name: string) => void; onAddField: (field: UndefinedPlaceholder) => void; addDisabled?: boolean; }) { const replacementOptions = useMemo( () => [ ...localFields.map((item) => ({ namespace: "local" as const, ...item })), ...globalFields.map((item) => ({ namespace: "global" as const, ...item })) ], [globalFields, localFields] ); const [replacement, setReplacement] = useState(""); const firstReplacement = replacementOptions[0]; const effectiveReplacement = replacement || (firstReplacement ? `${firstReplacement.namespace}:${firstReplacement.name}` : ""); useEffect(() => { setReplacement(firstReplacement ? `${firstReplacement.namespace}:${firstReplacement.name}` : ""); }, [field?.raw, firstReplacement?.namespace, firstReplacement?.name]); function replaceWithSelected() { if (!field || !onReplace || !effectiveReplacement) return; const separator = effectiveReplacement.indexOf(":"); if (separator < 0) return; const namespace = effectiveReplacement.slice(0, separator) as TemplateNamespace; const name = effectiveReplacement.slice(separator + 1); onReplace(field, namespace, name); } return ( {onReplace && replacementOptions.length > 0 && } ) : undefined} > {field && ( <>

The {contextLabel} uses {`{{${field.raw}}}`}, but it cannot be matched to a known field.

{field.reason === "invalid-namespace" && ( Use the namespace global: or local:. )} {field.reason === "missing-field" && (

You can add {field.name} as a campaign field, remove this placeholder, or continue editing.

)} {onReplace && replacementOptions.length > 0 && ( )} )}
); }