'use client' import { useState } from 'react' import { useTranslations } from 'next-intl' import { Settings2, ChevronDown, ChevronRight, RotateCcw } from 'lucide-react' import { Button } from '@/components/ui/button' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogTrigger, DialogClose, } from '@/components/ui/dialog' import { Switch } from '@/components/ui/switch' import { KPI_DEFINITIONS, getDefaultPreferences } from '@/lib/reports/kpi-definitions' import type { KPIPreferences } from '@/types' interface KPISettingsDialogProps { preferences: KPIPreferences /** * Resolves true when the layout was actually stored. The dialog awaits it, so * `saving` gets a chance to render, and stays open on false so the draft is * still there to retry with. */ onSave: (prefs: KPIPreferences) => Promise saving: boolean } export function KPISettingsDialog({ preferences, onSave, saving }: KPISettingsDialogProps) { const t = useTranslations('kpi') const tCommon = useTranslations('common') const [draft, setDraft] = useState(preferences) const [expandedKpi, setExpandedKpi] = useState(null) const [open, setOpen] = useState(false) function handleOpen(isOpen: boolean) { // Esc and click-outside must not discard a draft that is mid-save: the save // could still fail, and the draft is the only copy of what the user picked. // Bounded wait, the request carries a 15s deadline. if (!isOpen && saving) return if (isOpen) setDraft(preferences) setOpen(isOpen) } function toggleKpi(id: string) { setDraft((prev) => { const visible = prev.visibleKpis.includes(id) ? prev.visibleKpis.filter((k) => k !== id) : [...prev.visibleKpis, id] return { ...prev, visibleKpis: visible } }) } function setAccountOverride(kpiId: string, value: string) { const accounts = value .split(',') .map((s) => s.trim()) .filter((s) => /^\d{4}$/.test(s)) setDraft((prev) => ({ ...prev, accountOverrides: { ...prev.accountOverrides, [kpiId]: accounts, }, })) } function clearAccountOverride(kpiId: string) { setDraft((prev) => { const overrides = { ...prev.accountOverrides } delete overrides[kpiId] return { ...prev, accountOverrides: overrides } }) } function handleReset() { setDraft(getDefaultPreferences()) } /** * Await the save and close only if it landed. * * Closing first made the parent's `saving` state unrenderable and, worse, made * a failed save look identical to a successful one: the dialog vanished, the * grid showed the draft, and the layout was gone on the next page load. On * failure the parent shows the one toast that says why and this dialog stays * open with the draft intact. */ async function handleSave() { // Closes the double-click race before React has re-rendered the disabled // button; a second PUT would race the first over the same row. if (saving) return const saved = await onSave(draft) if (saved) setOpen(false) } return ( {t('settings_title')} {t('settings_subtitle')}
{KPI_DEFINITIONS.map((def) => { const isVisible = draft.visibleKpis.includes(def.id) const isExpanded = expandedKpi === def.id const hasOverride = def.customizableAccounts && draft.accountOverrides[def.id] && draft.accountOverrides[def.id].length > 0 const overrideValue = draft.accountOverrides[def.id]?.join(', ') ?? '' return (
toggleKpi(def.id)} />
{isExpanded && (

{t(`def_${def.id}_description`)}

{t('settings_formula_label')}

{t(`def_${def.id}_formula`)}

{t('settings_accounts_label')}

{t(`def_${def.id}_accounts`)}

{def.customizableAccounts && (
setAccountOverride(def.id, e.target.value) } placeholder={def.defaultAccounts.join(', ')} className="w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-xs font-mono tabular-nums placeholder:text-muted-foreground/50" />

{t('settings_account_hint', { example: def.defaultAccounts.slice(0, 3).join(', ') })}

{hasOverride && ( )}
)}
)}
) })}
) }