'use client'; import { useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Bookmark, Loader2, Save, Trash2 } from 'lucide-react'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Checkbox } from '@/components/ui/checkbox'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { apiFetch } from '@/lib/api/client'; import { toastError } from '@/lib/api/toast-error'; export interface SavedTemplate { id: string; portId: string; kind: string; name: string; description: string | null; // eslint-disable-next-line @typescript-eslint/no-explicit-any config: Record; } interface Props { kind: 'dashboard' | 'clients' | 'berths' | 'interests'; /** Called when the rep picks a template from the dropdown - the * parent hydrates its form from the returned config. */ onApply: (template: SavedTemplate) => void; /** Used by the "Save as template" toggle to capture the current * dialog state when the rep checks the box. */ currentConfig: Record; /** When set, allow saving the current dialog state as a new * template. The dialog manages the toggle + name input * inline so reps don't need to leave the export flow. */ showSave?: boolean; } /** * Reusable Saved-templates picker. Mounted at the top of both * dashboard / list export dialogs. * * - Dropdown lists existing templates for this port + kind. * - "Save as template" expands to a name field + Save button when * showSave is true. * - Delete action sits next to the dropdown when a template is * selected, so the rep can prune the list without leaving the * dialog. */ export function SavedTemplatesPicker({ kind, onApply, currentConfig, showSave = true }: Props) { const qc = useQueryClient(); const [selectedId, setSelectedId] = useState(''); const [saving, setSaving] = useState(false); const [saveName, setSaveName] = useState(''); const [saveOpen, setSaveOpen] = useState(false); const { data, isLoading } = useQuery<{ data: SavedTemplate[] }>({ queryKey: ['report-templates', kind], queryFn: () => apiFetch<{ data: SavedTemplate[] }>( `/api/v1/reports/templates?kind=${encodeURIComponent(kind)}`, ), staleTime: 30_000, }); const deleteMutation = useMutation({ mutationFn: async (id: string) => { await apiFetch(`/api/v1/reports/templates/${id}`, { method: 'DELETE' }); }, onSuccess: () => { toast.success('Template deleted'); setSelectedId(''); void qc.invalidateQueries({ queryKey: ['report-templates', kind] }); }, onError: (err) => toastError(err), }); async function handleSave() { if (!saveName.trim()) return; setSaving(true); try { await apiFetch('/api/v1/reports/templates', { method: 'POST', body: { kind, name: saveName.trim(), config: { ...currentConfig, kind }, }, }); toast.success('Template saved'); setSaveName(''); setSaveOpen(false); void qc.invalidateQueries({ queryKey: ['report-templates', kind] }); } catch (err) { toastError(err); } finally { setSaving(false); } } function handleApply(id: string) { setSelectedId(id); const template = data?.data.find((t) => t.id === id); if (template) onApply(template); } const templates = data?.data ?? []; return (
{selectedId ? ( ) : null}
{showSave ? ( saveOpen ? (
setSaveName(e.target.value)} placeholder="Template name" className="h-8" />
) : ( ) ) : null}
); }