big refactor

This commit is contained in:
2026-04-29 16:25:11 -05:00
parent 6c411ce2b6
commit 8265fb689a
156 changed files with 15845 additions and 50373 deletions

View File

@@ -1,10 +1,115 @@
<script setup lang="ts">
const route = useRoute()
const { saved: branding, sidebarTitle } = useBrokerageBranding()
const { isSuperAdmin } = useSuperAdmin()
useAppTheme()
const { sidebarCollapsed, toggleSidebar } = useAppShellLayout()
const sidebarFeatures = useSidebarFeatures()
const STORAGE_KEY_BRANDING = 'policy-ui.branding'
const STORAGE_KEY_SUPERADMIN = 'policy-ui.superadmin'
const STORAGE_KEY_SIDEBAR_WORKSTATIONS = 'policy-ui.sidebar.workstations'
const STORAGE_KEY_SIDEBAR_AI_TOOLS = 'policy-ui.sidebar.ai-tools'
const STORAGE_KEY_SIDEBAR_LEADS_HUB = 'policy-ui.sidebar.leads-hub'
const STORAGE_KEY_SIDEBAR_COLLAPSED = 'policy-ui.sidebar.collapsed'
interface BrokerageBrandingState {
companyName: string
logoDataUrl: string | null
logoFileName: string
reportPageHeader: string
reportPageFooter: string
}
function loadBranding(): BrokerageBrandingState {
if (import.meta.client) {
const stored = localStorage.getItem(STORAGE_KEY_BRANDING)
if (stored) {
try {
return JSON.parse(stored)
} catch {
return defaultBranding()
}
}
}
return defaultBranding()
}
function defaultBranding(): BrokerageBrandingState {
return {
companyName: '',
logoDataUrl: null,
logoFileName: '',
reportPageHeader: '',
reportPageFooter: ''
}
}
const branding = ref<BrokerageBrandingState>(loadBranding())
const sidebarTitle = computed(() => branding.value.companyName || 'Segur-OS')
const isSuperAdmin = computed(() => {
if (import.meta.client) {
const stored = localStorage.getItem(STORAGE_KEY_SUPERADMIN)
return stored !== '0'
}
return true
})
const sidebarCollapsed = computed({
get: () => {
if (import.meta.client) {
return localStorage.getItem(STORAGE_KEY_SIDEBAR_COLLAPSED) === 'true'
}
return false
},
set: (value: boolean) => {
if (import.meta.client) {
localStorage.setItem(STORAGE_KEY_SIDEBAR_COLLAPSED, String(value))
}
}
})
function toggleSidebar() {
sidebarCollapsed.value = !sidebarCollapsed.value
}
const sidebarFeatures = reactive({
showWorkstations: computed({
get: () => {
if (import.meta.client) {
return localStorage.getItem(STORAGE_KEY_SIDEBAR_WORKSTATIONS) !== 'false'
}
return true
},
set: (value: boolean) => {
if (import.meta.client) {
localStorage.setItem(STORAGE_KEY_SIDEBAR_WORKSTATIONS, String(value))
}
}
}),
showAiTools: computed({
get: () => {
if (import.meta.client) {
return localStorage.getItem(STORAGE_KEY_SIDEBAR_AI_TOOLS) !== 'false'
}
return true
},
set: (value: boolean) => {
if (import.meta.client) {
localStorage.setItem(STORAGE_KEY_SIDEBAR_AI_TOOLS, String(value))
}
}
}),
showLeadsHub: computed({
get: () => {
if (import.meta.client) {
return localStorage.getItem(STORAGE_KEY_SIDEBAR_LEADS_HUB) !== 'false'
}
return true
},
set: (value: boolean) => {
if (import.meta.client) {
localStorage.setItem(STORAGE_KEY_SIDEBAR_LEADS_HUB, String(value))
}
}
})
})
const openGroups = ref({
quotes: false,
@@ -12,10 +117,10 @@ const openGroups = ref({
cartera: false,
customerService: false,
workstation: false,
aiTools: false
aiTools: false,
backOffice: false
})
// Auto-open the group matching the current route (but never close others)
watch(() => route.path, (p) => {
if (p.startsWith('/quotes') && p !== '/quotes/new' && p !== '/quotes/compare') openGroups.value.quotes = true
if (p.startsWith('/onboarding') || (p.startsWith('/sales') && !p.startsWith('/sales/leads')) || p === '/quotes/new' || p === '/quotes/compare' || p.startsWith('/registration')) openGroups.value.sales = true
@@ -23,6 +128,7 @@ watch(() => route.path, (p) => {
if (p.startsWith('/support') || p.startsWith('/claims') || p.startsWith('/collections') || p.startsWith('/renewals') || p.startsWith('/sales/leads')) openGroups.value.customerService = true
if (p.startsWith('/workstation')) openGroups.value.workstation = true
if (p.startsWith('/ai-tools')) openGroups.value.aiTools = true
if (p.startsWith('/back-office')) openGroups.value.backOffice = true
}, { immediate: true })
function toggleGroup(key: string) {
@@ -34,7 +140,6 @@ function isActive(path: string, exact = false) {
return exact ? p === path : p === path || p.startsWith(`${path}/`)
}
/* ── Parent link class (40px row, 20px icon, 10px gap) ── */
function linkClass(path: string, exact = false) {
const active = isActive(path, exact)
return [
@@ -45,7 +150,6 @@ function linkClass(path: string, exact = false) {
]
}
/* ── Child link class (text only, 32px row, 13px, indented) ── */
function subLinkClass(path: string, exact = false) {
const active = isActive(path, exact)
return [
@@ -73,7 +177,8 @@ function groupBtnClass(key: string) {
route.path.startsWith('/renewals') ||
route.path.startsWith('/sales/leads'))) ||
(key === 'workstation' && route.path.startsWith('/workstation')) ||
(key === 'aiTools' && route.path.startsWith('/ai-tools'))
(key === 'aiTools' && route.path.startsWith('/ai-tools')) ||
(key === 'backOffice' && route.path.startsWith('/back-office'))
return [
'app-sidebar-link sidebar-parent-link flex w-full items-center text-left',
hasActive
@@ -90,7 +195,6 @@ async function onTopRefresh() {
}
}
// Keyboard shortcut: Ctrl/Cmd + B to toggle sidebar
function onKeydown(e: KeyboardEvent) {
if ((e.metaKey || e.ctrlKey) && e.key === 'b') {
e.preventDefault()
@@ -150,12 +254,10 @@ onUnmounted(() => {
/>
</button>
<div v-if="openGroups.quotes" class="sidebar-children">
<NuxtLink to="/quotes" active-class="" exact-active-class="" :class="subLinkClass('/quotes', true)">Mission Control</NuxtLink>
<NuxtLink to="/quotes/auto" active-class="" exact-active-class="" :class="subLinkClass('/quotes/auto')">Auto</NuxtLink>
<NuxtLink to="/quotes/health" active-class="" exact-active-class="" :class="subLinkClass('/quotes/health')">Health</NuxtLink>
<NuxtLink to="/quotes/life" active-class="" exact-active-class="" :class="subLinkClass('/quotes/life')">Life</NuxtLink>
<NuxtLink to="/quotes/general-risk" active-class="" exact-active-class="" :class="subLinkClass('/quotes/general-risk')">General Risk</NuxtLink>
<NuxtLink to="/quotes/custom" active-class="" exact-active-class="" :class="subLinkClass('/quotes/custom')">Custom</NuxtLink>
<NuxtLink to="/quotes/new?tab=car" :class="subLinkClass('/quotes/new', true)">Auto</NuxtLink>
<NuxtLink to="/quotes/new?tab=life" :class="subLinkClass('/quotes/new')">Life</NuxtLink>
<NuxtLink to="/quotes/new?tab=fire_structure" :class="subLinkClass('/quotes/new')">Fire Structure</NuxtLink>
<NuxtLink to="/quotes/new?tab=fire_contents" :class="subLinkClass('/quotes/new')">Fire Contents</NuxtLink>
</div>
<button type="button" :class="groupBtnClass('sales')" @click="toggleGroup('sales')">
@@ -169,12 +271,8 @@ onUnmounted(() => {
<div v-if="openGroups.sales" class="sidebar-children">
<NuxtLink to="/onboarding" :class="subLinkClass('/onboarding', true)">Sales Pipeline</NuxtLink>
<NuxtLink to="/sales/quick-lead" :class="subLinkClass('/sales/quick-lead')">Quick Lead</NuxtLink>
<NuxtLink to="/registration/client" :class="subLinkClass('/registration/client', true)">New Customer</NuxtLink>
<NuxtLink to="/quotes/new" :class="subLinkClass('/quotes/new', true)">Get Quotes</NuxtLink>
<NuxtLink to="/quotes/compare" :class="subLinkClass('/quotes/compare', true)">Present Quotes</NuxtLink>
<NuxtLink to="/onboarding/solicitud" :class="subLinkClass('/onboarding/solicitud', true)">Solicitudes</NuxtLink>
<NuxtLink to="/onboarding/emissions" :class="subLinkClass('/onboarding/emissions')">Emissions</NuxtLink>
<NuxtLink to="/onboarding/policy-upload/new" :class="subLinkClass('/onboarding/policy-upload')">Nombramiento</NuxtLink>
<NuxtLink to="/customers/new" :class="subLinkClass('/customers/new', true)">New Customer</NuxtLink>
<NuxtLink to="/quotes/new" :class="subLinkClass('/quotes/new', true)">New Quote</NuxtLink>
</div>
</div>
@@ -217,9 +315,27 @@ onUnmounted(() => {
<UIcon name="i-heroicons-chart-bar-square" class="sidebar-icon shrink-0" />
Reports & Analysis
</NuxtLink>
</div>
</div>
<!-- WORKSTATION section -->
<!-- BACK OFFICE section -->
<p class="app-sidebar-section-label">Back Office</p>
<div class="flex flex-col">
<button type="button" :class="groupBtnClass('backOffice')" @click="toggleGroup('backOffice')">
<UIcon name="i-heroicons-inbox-stack" class="sidebar-icon shrink-0" />
<span class="flex-1 text-left">Back Office</span>
<UIcon
:name="openGroups.backOffice ? 'i-heroicons-chevron-down' : 'i-heroicons-chevron-right'"
class="sidebar-chevron shrink-0"
/>
</button>
<div v-if="openGroups.backOffice" class="sidebar-children">
<NuxtLink to="/back-office/workload" :class="subLinkClass('/back-office/workload', true)">Task List</NuxtLink>
<NuxtLink to="/back-office/workload/kanban" :class="subLinkClass('/back-office/workload/kanban')">Kanban Board</NuxtLink>
</div>
</div>
<!-- WORKSTATION section -->
<template v-if="sidebarFeatures.showWorkstations || sidebarFeatures.showAiTools">
<p class="app-sidebar-section-label">Workstations</p>
@@ -291,7 +407,7 @@ onUnmounted(() => {
class="flex min-h-0 min-w-0 flex-1 flex-col"
:data-app-surface="isSettingsRoute ? 'settings' : undefined"
>
<div class="flex-1 overflow-y-auto" style="padding: 16px 24px 32px;">
<div class="flex-1 overflow-y-auto flex flex-col" style="padding: 16px 24px 32px;">
<NuxtPage :key="route.fullPath" />
</div>
</main>