206 lines
5.6 KiB
TypeScript
206 lines
5.6 KiB
TypeScript
import { computed } from 'vue'
|
|
import { useLocalStorageRef } from '~/utils/useLocalStorageRef'
|
|
|
|
/* ── Types ── */
|
|
|
|
export type ProfileRole = 'sales' | 'claims' | 'renewals' | 'general_service' | 'management' | 'superadmin'
|
|
|
|
export interface ProfileSection {
|
|
id: string
|
|
label: string
|
|
visible: boolean
|
|
order: number
|
|
}
|
|
|
|
export interface ProfileLayout {
|
|
id: string
|
|
role: ProfileRole | string
|
|
name: string
|
|
description: string
|
|
icon: string
|
|
sections: ProfileSection[]
|
|
defaultTab: 'policies' | 'claims' | 'payments' | 'activity' | 'history' | 'relationships' | 'notes'
|
|
isCustom: boolean
|
|
}
|
|
|
|
/* ── Section catalog ── */
|
|
|
|
const ALL_SECTION_IDS = [
|
|
'orientation',
|
|
'kpi_strip',
|
|
'quick_policies',
|
|
'service_actions',
|
|
'personal_details',
|
|
'tabbed_content',
|
|
'documents',
|
|
] as const
|
|
|
|
const SECTION_LABELS: Record<string, string> = {
|
|
orientation: 'Account Orientation',
|
|
kpi_strip: 'KPI Strip',
|
|
quick_policies: 'Quick Policies',
|
|
service_actions: 'Service Actions',
|
|
personal_details: 'Personal Details',
|
|
tabbed_content: 'Tabbed Content',
|
|
documents: 'Documents',
|
|
}
|
|
|
|
function makeSections(order: string[], hidden: string[] = []): ProfileSection[] {
|
|
return order.map((id, i) => ({
|
|
id,
|
|
label: SECTION_LABELS[id] ?? id,
|
|
visible: !hidden.includes(id),
|
|
order: i,
|
|
}))
|
|
}
|
|
|
|
/* ── Built-in layouts ── */
|
|
|
|
function defaultLayouts(): ProfileLayout[] {
|
|
return [
|
|
{
|
|
id: 'sales',
|
|
role: 'sales',
|
|
name: 'Sales',
|
|
description: 'Focus on policies, quotes, and pipeline.',
|
|
icon: 'i-heroicons-currency-dollar',
|
|
sections: makeSections([
|
|
'orientation', 'quick_policies', 'kpi_strip', 'tabbed_content',
|
|
'service_actions', 'personal_details', 'documents',
|
|
]),
|
|
defaultTab: 'policies',
|
|
isCustom: false,
|
|
},
|
|
{
|
|
id: 'claims',
|
|
role: 'claims',
|
|
name: 'Claims',
|
|
description: 'Focus on claims and service actions.',
|
|
icon: 'i-heroicons-shield-exclamation',
|
|
sections: makeSections([
|
|
'service_actions', 'orientation', 'kpi_strip', 'tabbed_content',
|
|
'quick_policies', 'personal_details', 'documents',
|
|
]),
|
|
defaultTab: 'claims',
|
|
isCustom: false,
|
|
},
|
|
{
|
|
id: 'renewals',
|
|
role: 'renewals',
|
|
name: 'Renewals',
|
|
description: 'Focus on upcoming events and policies.',
|
|
icon: 'i-heroicons-arrow-path',
|
|
sections: makeSections([
|
|
'orientation', 'quick_policies', 'kpi_strip', 'tabbed_content',
|
|
'service_actions', 'personal_details', 'documents',
|
|
]),
|
|
defaultTab: 'policies',
|
|
isCustom: false,
|
|
},
|
|
{
|
|
id: 'general_service',
|
|
role: 'general_service',
|
|
name: 'General Service',
|
|
description: 'Balanced default for service representatives.',
|
|
icon: 'i-heroicons-lifebuoy',
|
|
sections: makeSections([
|
|
'orientation', 'kpi_strip', 'quick_policies', 'service_actions',
|
|
'personal_details', 'tabbed_content', 'documents',
|
|
]),
|
|
defaultTab: 'policies',
|
|
isCustom: false,
|
|
},
|
|
{
|
|
id: 'management',
|
|
role: 'management',
|
|
name: 'Management',
|
|
description: 'KPIs first, everything visible.',
|
|
icon: 'i-heroicons-chart-bar',
|
|
sections: makeSections([
|
|
'kpi_strip', 'orientation', 'service_actions', 'quick_policies',
|
|
'tabbed_content', 'personal_details', 'documents',
|
|
]),
|
|
defaultTab: 'history',
|
|
isCustom: false,
|
|
},
|
|
{
|
|
id: 'superadmin',
|
|
role: 'superadmin',
|
|
name: 'Superadmin',
|
|
description: 'Everything visible, history focus.',
|
|
icon: 'i-heroicons-cog-8-tooth',
|
|
sections: makeSections([
|
|
'kpi_strip', 'orientation', 'service_actions', 'quick_policies',
|
|
'tabbed_content', 'personal_details', 'documents',
|
|
]),
|
|
defaultTab: 'history',
|
|
isCustom: false,
|
|
},
|
|
]
|
|
}
|
|
|
|
const LAYOUTS_KEY = 'policy-ui-profile-layouts-v1'
|
|
const ACTIVE_KEY = 'policy-ui-active-profile-layout-v1'
|
|
|
|
/* ── Composable ── */
|
|
|
|
export function useProfileLayouts() {
|
|
const layouts = useLocalStorageRef<ProfileLayout[]>(LAYOUTS_KEY, defaultLayouts)
|
|
|
|
const activeLayoutId = useLocalStorageRef<{ id: string }>(ACTIVE_KEY, () => ({ id: 'general_service' }))
|
|
|
|
const activeLayout = computed<ProfileLayout>(() => {
|
|
const found = layouts.value.find(l => l.id === activeLayoutId.value.id)
|
|
return found ?? layouts.value[0] ?? defaultLayouts()[3] // fallback to general_service
|
|
})
|
|
|
|
const sortedSections = computed<ProfileSection[]>(() =>
|
|
[...activeLayout.value.sections]
|
|
.filter(s => s.visible)
|
|
.sort((a, b) => a.order - b.order)
|
|
)
|
|
|
|
function setActiveLayout(id: string) {
|
|
activeLayoutId.value = { id }
|
|
}
|
|
|
|
function addCustomLayout(layout: Omit<ProfileLayout, 'isCustom'>) {
|
|
layouts.value = [
|
|
...layouts.value,
|
|
{ ...layout, isCustom: true },
|
|
]
|
|
}
|
|
|
|
function updateLayout(id: string, partial: Partial<ProfileLayout>) {
|
|
layouts.value = layouts.value.map(l =>
|
|
l.id === id ? { ...l, ...partial } : l
|
|
)
|
|
}
|
|
|
|
function removeCustomLayout(id: string) {
|
|
const target = layouts.value.find(l => l.id === id)
|
|
if (!target || !target.isCustom) return
|
|
layouts.value = layouts.value.filter(l => l.id !== id)
|
|
if (activeLayoutId.value.id === id) {
|
|
activeLayoutId.value = { id: 'general_service' }
|
|
}
|
|
}
|
|
|
|
function resetToDefaults() {
|
|
layouts.value = defaultLayouts()
|
|
activeLayoutId.value = { id: 'general_service' }
|
|
}
|
|
|
|
return {
|
|
layouts,
|
|
activeLayoutId: computed(() => activeLayoutId.value.id),
|
|
activeLayout,
|
|
sortedSections,
|
|
setActiveLayout,
|
|
addCustomLayout,
|
|
updateLayout,
|
|
removeCustomLayout,
|
|
resetToDefaults,
|
|
}
|
|
}
|