/** * Home dashboard widget visibility — role presets + per-widget toggles. * Persisted locally until per-user API exists. */ export type DashboardWidgetId = | 'hero' | 'milestone' | 'performance' | 'tasks_alerts' | 'charts' | 'brokerage_health' | 'quotes_line' | 'notes' | 'calendar' | 'quick_leads' | 'sales_leads' | 'client_favorites' | 'drafts' export type DashboardRolePresetId = | 'sales_manager' | 'executive_manager' | 'director' | 'financial' | 'admin_manager' | 'customer_service_manager' export type DashboardWidgetMeta = { id: DashboardWidgetId label: string description: string } export const DASHBOARD_WIDGETS: DashboardWidgetMeta[] = [ { id: 'hero', label: 'Welcome banner', description: 'Greeting, CTAs, workspace strip' }, { id: 'milestone', label: 'MTD milestone', description: 'Plan vs actual snapshot' }, { id: 'tasks_alerts', label: 'Tasks & alerts', description: 'Daily work + exceptions' }, { id: 'performance', label: 'Today at a glance', description: 'Headline KPIs + sparklines' }, { id: 'charts', label: 'Charts', description: 'GWP trend & quoted pipeline' }, { id: 'brokerage_health', label: 'Brokerage health', description: 'YTD / trailing book metrics' }, { id: 'quotes_line', label: 'Sent quotes', description: 'Sortable list of quotes sent to clients' }, { id: 'notes', label: 'Notes', description: 'Personal scratchpad and reminders' }, { id: 'calendar', label: 'Calendar', description: 'Agenda, renewals, alerts & reminders' }, { id: 'quick_leads', label: 'Quick leads', description: 'Recent quick leads from the last 10 days' }, { id: 'sales_leads', label: 'Sales leads', description: 'All leads by source — filter by channel, campaign, or API' }, { id: 'client_favorites', label: 'Favorite clients', description: 'Starred clients for quick access' }, { id: 'drafts', label: 'Drafts', description: 'Resume in-progress quotes, solicitudes & registrations' } ] const STORAGE_KEY = 'policy-ui.dashboard.widgets.v4' export const DEFAULT_WIDGET_ORDER: DashboardWidgetId[] = DASHBOARD_WIDGETS.map((w) => w.id) function normalizeWidgetOrder(raw: unknown): DashboardWidgetId[] { const base = [...DEFAULT_WIDGET_ORDER] if (!Array.isArray(raw)) return base const seen = new Set() const out: DashboardWidgetId[] = [] for (const x of raw) { if (typeof x === 'string' && base.includes(x as DashboardWidgetId) && !seen.has(x as DashboardWidgetId)) { const id = x as DashboardWidgetId seen.add(id) out.push(id) } } for (const id of base) { if (!seen.has(id)) out.push(id) } return out } const ALL_ON: Record = { hero: true, milestone: true, performance: false, tasks_alerts: true, charts: true, brokerage_health: true, quotes_line: true, notes: true, calendar: true, quick_leads: true, sales_leads: true, client_favorites: true, drafts: true } export const DASHBOARD_ROLE_PRESETS: Record< DashboardRolePresetId, { label: string; hint: string; widgets: Record } > = { sales_manager: { label: 'Sales manager', hint: 'Pipeline, tasks, quotes — lighter book-of-business tile.', widgets: { ...ALL_ON, brokerage_health: false } }, executive_manager: { label: 'Executive manager', hint: 'Balanced operational + book view.', widgets: { ...ALL_ON } }, director: { label: 'Director', hint: 'Strategic KPIs & health; fewer operational tiles.', widgets: { ...ALL_ON, tasks_alerts: false, quotes_line: false } }, financial: { label: 'Financial', hint: 'Premium, AR, health metrics; fewer sales shortcuts.', widgets: { ...ALL_ON, quotes_line: false, tasks_alerts: true, performance: false, brokerage_health: true, charts: true, sales_leads: false } }, admin_manager: { label: 'Admin / operations', hint: 'Permissions, forms, and carrier setup — fewer quote shortcuts.', widgets: { ...ALL_ON, quotes_line: false, charts: false, brokerage_health: true, sales_leads: false } }, customer_service_manager: { label: 'Customer service manager', hint: 'Queues, tasks, and exceptions — lighter GWP / book tiles.', widgets: { ...ALL_ON, charts: false, brokerage_health: false, quotes_line: false } } } /** Stable order for selects. */ export const DASHBOARD_PRESET_ORDER: DashboardRolePresetId[] = [ 'sales_manager', 'executive_manager', 'director', 'financial', 'admin_manager', 'customer_service_manager' ] function cloneWidgets(w: Record): Record { return { ...w } } export function useDashboardHomeWidgets() { const activePreset = ref('executive_manager') const widgets = ref>( cloneWidgets(DASHBOARD_ROLE_PRESETS.executive_manager.widgets) ) const widgetOrder = ref([...DEFAULT_WIDGET_ORDER]) const layoutUnlocked = ref(false) const hydrated = ref(false) function persist() { if (typeof localStorage === 'undefined') return try { localStorage.setItem( STORAGE_KEY, JSON.stringify({ preset: activePreset.value, widgets: widgets.value, widgetOrder: widgetOrder.value, layoutUnlocked: layoutUnlocked.value }) ) } catch { /* quota */ } } function load() { if (typeof localStorage === 'undefined') return try { const raw = localStorage.getItem(STORAGE_KEY) if (!raw) return const data = JSON.parse(raw) as { preset?: DashboardRolePresetId widgets?: Partial> widgetOrder?: DashboardWidgetId[] layoutUnlocked?: boolean } if (data.preset && DASHBOARD_ROLE_PRESETS[data.preset]) { activePreset.value = data.preset } if (data.widgets) { const merged = { ...widgets.value } for (const k of Object.keys(merged) as DashboardWidgetId[]) { if (data.widgets[k] !== undefined) merged[k] = data.widgets[k]! } widgets.value = merged } widgetOrder.value = normalizeWidgetOrder(data.widgetOrder) if (typeof data.layoutUnlocked === 'boolean') { layoutUnlocked.value = data.layoutUnlocked } } catch { /* ignore */ } } onMounted(() => { load() hydrated.value = true }) watch( [activePreset, widgets, widgetOrder, layoutUnlocked], () => { if (hydrated.value) persist() }, { deep: true } ) const isPresetDirty = computed(() => { const preset = DASHBOARD_ROLE_PRESETS[activePreset.value] if (!preset) return false return DASHBOARD_WIDGETS.some((w) => widgets.value[w.id] !== preset.widgets[w.id]) }) function applyPreset(id: DashboardRolePresetId) { activePreset.value = id const p = DASHBOARD_ROLE_PRESETS[id] if (p) widgets.value = cloneWidgets(p.widgets) } function setWidget(id: DashboardWidgetId, on: boolean) { widgets.value = { ...widgets.value, [id]: on } } function reapplySelectedPreset() { applyPreset(activePreset.value) } function reorderWidgets(fromId: DashboardWidgetId, toId: DashboardWidgetId) { if (fromId === toId) return const arr = [...widgetOrder.value] const fromI = arr.indexOf(fromId) const toI = arr.indexOf(toId) if (fromI === -1 || toI === -1) return arr.splice(fromI, 1) arr.splice(toI, 0, fromId) widgetOrder.value = arr } return { widgets, widgetOrder, layoutUnlocked, activePreset, isPresetDirty, applyPreset, setWidget, reapplySelectedPreset, reorderWidgets } }