"use client"; import { useState, useTransition } from "react"; import { X, ChevronLeft } from "lucide-react"; import type { SerializedWidgetMeta } from "@/modules/_core/registry"; import { resolveWidgetConfigOptions } from "@/app/d/actions"; import { Button } from "@/components/ui/button"; type Step = "pick" | "configure"; export function WidgetPicker({ onClose, widgetMetas, onAdd, initialWidgetId, initialConfig, }: { onClose: () => void; widgetMetas: SerializedWidgetMeta[]; onAdd: (widgetId: string, config: unknown) => void; initialWidgetId?: string; initialConfig?: unknown; }) { const [step, setStep] = useState(initialWidgetId ? "configure" : "pick"); const [selectedId, setSelectedId] = useState(initialWidgetId ?? null); const [options, setOptions] = useState(null); const [config, setConfig] = useState(initialConfig ?? null); const [, startTransition] = useTransition(); function selectWidget(id: string) { const meta = widgetMetas.find((m) => m.id === id); if (!meta) return; setSelectedId(id); setConfig(meta.defaultConfig); setOptions(null); setStep("configure"); startTransition(async () => { const opts = await resolveWidgetConfigOptions(id); setOptions(opts); }); } function handleAdd() { if (!selectedId) return; onAdd(selectedId, config); } const grouped = widgetMetas.reduce>((acc, m) => { const cat = m.category ?? "Other"; (acc[cat] ??= []).push(m); return acc; }, {}); const selected = widgetMetas.find((m) => m.id === selectedId); return (
e.target === e.currentTarget && onClose()} >
{/* Header */}
{step === "configure" && !initialWidgetId && ( )}

{step === "pick" ? "Add widget" : selected ? `Configure: ${selected.title}` : "Configure"}

{/* Body */}
{step === "pick" && (
{Object.entries(grouped) .sort(([a], [b]) => a.localeCompare(b)) .map(([cat, items]) => (

{cat}

{items.map((meta) => ( ))}
))}
)} {step === "configure" && selected && ( )}
{/* Footer */} {step === "configure" && (
)}
); } // ── Configurator ────────────────────────────────────────────────────────────── type FieldOption = { id: string; name: string }; function WidgetConfigurator({ meta, config, options, onChange, }: { meta: SerializedWidgetMeta; config: unknown; options: unknown; onChange: (c: unknown) => void; }) { const cfg = (config ?? meta.defaultConfig) as Record; const opts = options as Record | null | undefined; function set(key: string, value: unknown) { onChange({ ...cfg, [key]: value }); } const entries = Object.entries(cfg); if (entries.length === 0) { return

No options available.

; } return (
{entries.map(([key, value]) => { // "all" | string[] — multi-select with All toggle if ( value === "all" || (Array.isArray(value) && (key.endsWith("Ids") || key.endsWith("ids"))) ) { const optKey = key.replace(/Ids?$/i, "s"); const items: FieldOption[] = (opts?.[optKey] as FieldOption[] | undefined) ?? []; const isAll = value === "all"; const selected = isAll ? [] : (value as string[]); return (
{!isAll && (
{items.length === 0 && (

None available

)} {items.map((item) => ( ))}
)}
); } // boolean if (typeof value === "boolean") { return ( ); } // number if (typeof value === "number") { return (
set(key, Number(e.target.value))} className="w-24 rounded-md border border-input bg-background px-3 py-1.5 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" />
); } // string (enum / plain) if (typeof value === "string") { // Detect enum-like: same key appears in options as an array of strings const enumOpts = opts?.[key] as string[] | undefined; if (Array.isArray(enumOpts)) { return (
); } // Hardcoded enum fallback for known fields const knownEnums: Record = { filter: ["pinned", "all"], }; const known = knownEnums[key]; if (known) { return (
); } } return null; })}
); }