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

@@ -3,15 +3,13 @@
* Global command palette — searches customers, policies, claims, and app pages.
* Keyboard: Ctrl/Cmd+K to focus, Escape to close, ↑↓ to navigate, Enter to go.
*/
import { MOCK_CUSTOMERS, fmtMoney } from '~/data/mock-customers'
const router = useRouter()
const open = ref(false)
const q = ref('')
const inputRef = ref<HTMLElement | null>(null)
const activeIndex = ref(-1)
/* ── Build searchable records from mock data ── */
/* ── Build searchable records from API data ── */
type SearchHit = {
id: string
@@ -24,48 +22,9 @@ type SearchHit = {
}
const allRecords = computed<SearchHit[]>(() => {
const hits: SearchHit[] = []
for (const c of MOCK_CUSTOMERS) {
// Customer record
hits.push({
id: `cust-${c.id}`,
kind: 'customer',
icon: 'i-heroicons-user',
title: c.name,
meta: `${c.type} · ${c.documentId}`,
detail: `${c.policies.length} policies · ${fmtMoney(c.policies.reduce((s, p) => s + p.premium, 0))}/yr · Agent: ${c.agent}`,
to: `/customers/${c.id}`
})
// Each policy
for (const p of c.policies) {
hits.push({
id: `pol-${p.id}`,
kind: 'policy',
icon: p.icon,
title: p.id,
meta: `${p.line} · ${p.carrier} · ${c.name}`,
detail: p.product,
to: `/customers/${c.id}`
})
}
// Each claim
for (const cl of c.claims) {
hits.push({
id: `claim-${cl.id}`,
kind: 'claim',
icon: 'i-heroicons-shield-exclamation',
title: cl.id,
meta: `${cl.type} · ${cl.status} · ${c.name}`,
detail: `Policy ${cl.policy} · $${cl.amount.toLocaleString()}`,
to: `/customers/${c.id}`
})
}
}
return hits
// TODO: Replace with API calls to fetch customers, policies, and claims
// For now, return empty array since mock data has been removed
return []
})
/* ── App pages / destinations ── */

View File

@@ -1,7 +1,4 @@
<script setup lang="ts">
import type { AppThemeId } from '~/types/app-theme'
import { APP_THEME_OPTIONS } from '~/types/app-theme'
defineProps<{
sidebarCollapsed: boolean
brandTitle?: string
@@ -15,19 +12,18 @@ const emit = defineEmits<{
const route = useRoute()
const router = useRouter()
const isHome = computed(() => route.path === '/')
const { themeId, applyTheme } = useAppTheme()
const themeIcons: Record<string, string> = {
light: 'i-heroicons-sun',
purple: 'i-heroicons-sparkles',
dark: 'i-heroicons-moon',
'dark-purple': 'i-heroicons-star',
}
const colorMode = useColorMode()
const isDark = computed({
get () {
return colorMode.value === 'dark'
},
set () {
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
}
})
const userMenuOpen = ref(false)
const userMenuRoot = ref<HTMLElement | null>(null)
const themeMenuOpen = ref(false)
const themeMenuRoot = ref<HTMLElement | null>(null)
function closeUserMenu() {
userMenuOpen.value = false
@@ -38,10 +34,6 @@ function onDocClick(e: MouseEvent) {
if (userEl && userMenuOpen.value && !userEl.contains(e.target as Node)) {
userMenuOpen.value = false
}
const themeEl = themeMenuRoot.value
if (themeEl && themeMenuOpen.value && !themeEl.contains(e.target as Node)) {
themeMenuOpen.value = false
}
}
onMounted(() => document.addEventListener('click', onDocClick))
@@ -107,58 +99,19 @@ onUnmounted(() => document.removeEventListener('click', onDocClick))
<UIcon name="i-heroicons-arrow-path" style="width: 16px; height: 16px;" />
</button>
<!-- Quick theme switcher -->
<div ref="themeMenuRoot" class="relative">
<button
type="button"
class="app-topbar-icon-btn"
title="Switch theme"
<!-- Theme toggle -->
<ClientOnly>
<UButton
:icon="isDark ? 'i-heroicons-moon-20-solid' : 'i-heroicons-sun-20-solid'"
color="gray"
variant="ghost"
aria-label="Theme"
@click.stop="themeMenuOpen = !themeMenuOpen"
>
<UIcon :name="themeIcons[themeId] ?? 'i-heroicons-swatch'" style="width: 16px; height: 16px;" />
</button>
<Transition
enter-active-class="transition duration-150 ease-out"
enter-from-class="opacity-0 scale-95 translate-y-1"
enter-to-class="opacity-100 scale-100 translate-y-0"
leave-active-class="transition duration-100 ease-in"
leave-from-class="opacity-100"
leave-to-class="opacity-0 scale-95"
>
<div
v-show="themeMenuOpen"
class="absolute right-0 top-[calc(100%+8px)] z-50 w-52 overflow-hidden rounded-xl border border-[var(--sidebar-border)] bg-[var(--surface)] py-1.5 shadow-xl ring-1 ring-black/5"
>
<p class="px-3 py-1 text-[10px] font-semibold uppercase tracking-wider text-[var(--text-muted)]">Theme</p>
<button
v-for="opt in APP_THEME_OPTIONS"
:key="opt.id"
type="button"
class="flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm transition hover:bg-[var(--brand-faint)]"
:class="themeId === opt.id ? 'text-[var(--brand)] font-medium' : 'text-[var(--text-primary)]'"
@click="applyTheme(opt.id as AppThemeId); themeMenuOpen = false"
>
<UIcon :name="themeIcons[opt.id]" class="h-4 w-4 shrink-0" :class="themeId === opt.id ? 'text-[var(--brand)]' : 'opacity-60'" />
<span class="flex-1">{{ opt.label }}</span>
<UIcon
v-if="themeId === opt.id"
name="i-heroicons-check"
class="h-3.5 w-3.5 text-[var(--brand)]"
/>
</button>
<div class="mx-3 my-1.5 border-t border-[var(--sidebar-border)]" />
<NuxtLink
to="/account"
class="flex items-center gap-2.5 px-3 py-1.5 text-xs text-[var(--text-muted)] transition hover:text-[var(--brand)]"
@click="themeMenuOpen = false"
>
<UIcon name="i-heroicons-cog-6-tooth" class="h-3.5 w-3.5" />
All appearance settings
</NuxtLink>
</div>
</Transition>
</div>
@click="isDark = !isDark"
/>
<template #fallback>
<div class="w-8 h-8" />
</template>
</ClientOnly>
<NuxtLink to="/settings" class="inline-flex" title="Software settings">
<span class="app-topbar-icon-btn">