refactor: update routing paths in app.vue, enhance AnnualBudget component layout, and streamline dashboard and budget pages for improved user experience
This commit is contained in:
parent
09d8794d72
commit
864a81065c
23 changed files with 3211 additions and 1978 deletions
261
pages/index.vue
261
pages/index.vue
|
|
@ -2,40 +2,32 @@
|
|||
<section class="py-8 space-y-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-semibold">Dashboard</h2>
|
||||
<h2 class="text-2xl font-semibold">Compensation</h2>
|
||||
<div class="flex items-center gap-2 mt-1">
|
||||
<UBadge
|
||||
:color="policiesStore.operatingMode === 'target' ? 'primary' : 'gray'"
|
||||
size="xs"
|
||||
>
|
||||
{{ policiesStore.operatingMode === 'target' ? '🎯 Target Mode' : '⚡ Min Mode' }}
|
||||
</UBadge>
|
||||
<span class="text-xs text-gray-500">
|
||||
<span class="px-2 py-1 border border-black bg-white text-xs font-bold uppercase">
|
||||
{{ policiesStore.operatingMode === 'target' ? 'Target Mode' : 'Min Mode' }}
|
||||
</span>
|
||||
<span class="text-xs font-mono">
|
||||
Runway: {{ Math.round(metrics.runway) }}mo
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<UButton
|
||||
icon="i-heroicons-arrow-down-tray"
|
||||
color="gray"
|
||||
<button
|
||||
@click="onExport"
|
||||
>Export JSON</UButton
|
||||
>
|
||||
<UButton icon="i-heroicons-arrow-up-tray" color="gray" @click="onImport"
|
||||
>Import JSON</UButton
|
||||
>
|
||||
class="px-4 py-2 border-2 border-black bg-white font-bold uppercase text-sm hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] transition-shadow">
|
||||
Export JSON
|
||||
</button>
|
||||
<button
|
||||
@click="onImport"
|
||||
class="px-4 py-2 border-2 border-black bg-white font-bold uppercase text-sm hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] transition-shadow">
|
||||
Import JSON
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Key Metrics Row -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<RunwayMeter
|
||||
:months="metrics.runway"
|
||||
:description="`You have ${$format.number(
|
||||
metrics.runway
|
||||
)} months of runway with current spending.`" />
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<CoverageMeter
|
||||
:funded-paid-hours="Math.round(metrics.totalTargetHours * 0.65)"
|
||||
:target-hours="metrics.totalTargetHours"
|
||||
|
|
@ -46,141 +38,152 @@
|
|||
:savings-target-months="savingsProgress.targetMonths"
|
||||
:monthly-burn="getMonthlyBurn()"
|
||||
:description="`${savingsProgress.progressPct.toFixed(0)}% of savings target reached. ${savingsProgress.gap > 0 ? 'Gap: ' + $format.currency(savingsProgress.gap) : 'Target achieved!'}`" />
|
||||
|
||||
<UCard>
|
||||
<div class="text-center space-y-3">
|
||||
<div class="text-3xl font-bold" :class="concentrationColor">
|
||||
{{ topSourcePct }}%
|
||||
</div>
|
||||
<div class="text-sm text-neutral-600">
|
||||
<GlossaryTooltip
|
||||
term="Concentration"
|
||||
term-id="concentration"
|
||||
definition="Dependence on few revenue sources. UI shows top source percentage." />
|
||||
</div>
|
||||
<ConcentrationChip
|
||||
:status="concentrationStatus"
|
||||
:top-source-pct="topSourcePct"
|
||||
:show-percentage="false"
|
||||
variant="soft" />
|
||||
<p class="text-xs text-neutral-500 mt-2">
|
||||
Most of your money comes from one place. Add another stream to
|
||||
reduce risk.
|
||||
</p>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
<!-- Quick Wins Dashboard Components -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<!-- Dashboard Components with Wizard Styling -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<!-- Needs Coverage Bars -->
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h3 class="text-lg font-medium">Member Coverage</h3>
|
||||
</template>
|
||||
<NeedsCoverageBars />
|
||||
</UCard>
|
||||
|
||||
<!-- Revenue Mix -->
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h3 class="text-lg font-medium">Revenue Mix</h3>
|
||||
</template>
|
||||
<RevenueMixTable />
|
||||
</UCard>
|
||||
<div class="border-2 border-black bg-white shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div class="border-b-2 border-black p-4">
|
||||
<h3 class="text-lg font-bold uppercase">Member Coverage</h3>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<NeedsCoverageBars />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Milestone-Runway Overlay -->
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h3 class="text-lg font-medium">Runway vs Milestones</h3>
|
||||
</template>
|
||||
<MilestoneRunwayOverlay />
|
||||
</UCard>
|
||||
<div class="border-2 border-black bg-white shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div class="border-b-2 border-black p-4">
|
||||
<h3 class="text-lg font-bold uppercase">Runway vs Milestones</h3>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<MilestoneRunwayOverlay />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alerts Section -->
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h3 class="text-lg font-medium">Alerts</h3>
|
||||
</template>
|
||||
<div class="space-y-3">
|
||||
<!-- Alerts Section with Wizard Styling -->
|
||||
<div class="border-2 border-black bg-white shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div class="border-b-2 border-black p-4">
|
||||
<h3 class="text-lg font-bold uppercase">Alerts</h3>
|
||||
</div>
|
||||
<div class="p-4 space-y-4">
|
||||
<!-- Concentration Risk Alert -->
|
||||
<UAlert
|
||||
<div
|
||||
v-if="topSourcePct > 50"
|
||||
color="red"
|
||||
variant="subtle"
|
||||
icon="i-heroicons-exclamation-triangle"
|
||||
title="Revenue Concentration Risk"
|
||||
:description="`${topStreamName} = ${topSourcePct}% of total → consider balancing`"
|
||||
:actions="[
|
||||
{ label: 'Plan Mix', click: () => handleAlertNavigation('/mix', 'concentration') }
|
||||
]" />
|
||||
class="border-2 border-red-600 bg-red-50 p-4">
|
||||
<div class="flex items-start gap-3">
|
||||
<span class="text-red-600 font-bold text-xl">!</span>
|
||||
<div class="flex-1">
|
||||
<h4 class="font-bold uppercase mb-1">Revenue Concentration Risk</h4>
|
||||
<p class="text-sm mb-2">{{ topStreamName }} = {{ topSourcePct }}% of total → consider balancing</p>
|
||||
<button
|
||||
@click="handleAlertNavigation('/dashboard', 'concentration')"
|
||||
class="text-sm underline font-bold">
|
||||
VIEW DETAILS
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cushion Breach Alert -->
|
||||
<UAlert
|
||||
<div
|
||||
v-if="alerts.cushionBreach"
|
||||
color="orange"
|
||||
variant="subtle"
|
||||
icon="i-heroicons-calendar"
|
||||
title="Cash Cushion Breach Forecast"
|
||||
:description="`Projected to breach minimum cushion in week ${cushionForecast.firstBreachWeek || 'unknown'}`"
|
||||
:actions="[
|
||||
{ label: 'View Calendar', click: () => handleAlertNavigation('/cash', 'breach-forecast') },
|
||||
{ label: 'Adjust Budget', click: () => handleAlertNavigation('/budget', 'expenses') }
|
||||
]" />
|
||||
class="border-2 border-orange-600 bg-orange-50 p-4">
|
||||
<div class="flex items-start gap-3">
|
||||
<span class="text-orange-600 font-bold text-xl">!</span>
|
||||
<div class="flex-1">
|
||||
<h4 class="font-bold uppercase mb-1">Cash Cushion Breach Forecast</h4>
|
||||
<p class="text-sm mb-2">Projected to breach minimum cushion in week {{ cushionForecast.firstBreachWeek || 'unknown' }}</p>
|
||||
<div class="flex gap-4">
|
||||
<button
|
||||
@click="handleAlertNavigation('/cash', 'breach-forecast')"
|
||||
class="text-sm underline font-bold">
|
||||
VIEW CALENDAR
|
||||
</button>
|
||||
<button
|
||||
@click="handleAlertNavigation('/budget', 'expenses')"
|
||||
class="text-sm underline font-bold">
|
||||
ADJUST BUDGET
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Savings Below Target Alert -->
|
||||
<UAlert
|
||||
<div
|
||||
v-if="alerts.savingsBelowTarget"
|
||||
color="yellow"
|
||||
variant="subtle"
|
||||
icon="i-heroicons-banknotes"
|
||||
title="Savings Below Target"
|
||||
:description="`${savingsProgress.progressPct.toFixed(0)}% of target reached. Build savings before increasing paid hours.`"
|
||||
:actions="[
|
||||
{ label: 'View Progress', click: () => handleAlertNavigation('/budget', 'savings') },
|
||||
{ label: 'Adjust Policies', click: () => handleAlertNavigation('/coop-builder', 'policies') }
|
||||
]" />
|
||||
class="border-2 border-yellow-600 bg-yellow-50 p-4">
|
||||
<div class="flex items-start gap-3">
|
||||
<span class="text-yellow-600 font-bold text-xl">!</span>
|
||||
<div class="flex-1">
|
||||
<h4 class="font-bold uppercase mb-1">Savings Below Target</h4>
|
||||
<p class="text-sm mb-2">{{ savingsProgress.progressPct.toFixed(0) }}% of target reached. Build savings before increasing paid hours.</p>
|
||||
<div class="flex gap-4">
|
||||
<button
|
||||
@click="handleAlertNavigation('/budget', 'savings')"
|
||||
class="text-sm underline font-bold">
|
||||
VIEW PROGRESS
|
||||
</button>
|
||||
<button
|
||||
@click="handleAlertNavigation('/coop-builder', 'policies')"
|
||||
class="text-sm underline font-bold">
|
||||
ADJUST POLICIES
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Over-Deferred Member Alert -->
|
||||
<UAlert
|
||||
<div
|
||||
v-if="deferredAlert.show"
|
||||
color="purple"
|
||||
variant="subtle"
|
||||
icon="i-heroicons-user-group"
|
||||
title="Member Over-Deferred"
|
||||
:description="deferredAlert.description"
|
||||
:actions="[
|
||||
{ label: 'Review Members', click: () => handleAlertNavigation('/coop-builder', 'members') },
|
||||
]" />
|
||||
class="border-2 border-purple-600 bg-purple-50 p-4">
|
||||
<div class="flex items-start gap-3">
|
||||
<span class="text-purple-600 font-bold text-xl">!</span>
|
||||
<div class="flex-1">
|
||||
<h4 class="font-bold uppercase mb-1">Member Over-Deferred</h4>
|
||||
<p class="text-sm mb-2">{{ deferredAlert.description }}</p>
|
||||
<button
|
||||
@click="handleAlertNavigation('/coop-builder', 'members')"
|
||||
class="text-sm underline font-bold">
|
||||
REVIEW MEMBERS
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Success message when no alerts -->
|
||||
<div v-if="!alerts.cushionBreach && !alerts.savingsBelowTarget && topSourcePct <= 50 && !deferredAlert.show"
|
||||
class="text-center py-8 text-gray-500">
|
||||
<UIcon name="i-heroicons-check-circle" class="w-8 h-8 mx-auto mb-2 text-green-500" />
|
||||
<p class="font-medium">All systems looking good!</p>
|
||||
<p class="text-sm">No critical alerts at this time.</p>
|
||||
class="text-center py-8">
|
||||
<span class="text-4xl font-bold">✓</span>
|
||||
<p class="font-bold uppercase mt-2">All systems looking good!</p>
|
||||
<p class="text-sm mt-1">No critical alerts at this time.</p>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<UButton
|
||||
block
|
||||
variant="ghost"
|
||||
class="justify-start h-auto p-4"
|
||||
@click="navigateTo('/mix')">
|
||||
<div class="text-left">
|
||||
<div class="font-medium">Revenue Mix</div>
|
||||
<div class="text-xs text-neutral-500">Plan revenue streams</div>
|
||||
</div>
|
||||
</UButton>
|
||||
|
||||
<!-- Quick Actions with Wizard Styling -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<button
|
||||
@click="navigateTo('/cash-flow')"
|
||||
class="border-2 border-black bg-white p-4 text-left hover:shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] transition-shadow">
|
||||
<div class="font-bold uppercase mb-1">Cash Flow Analysis</div>
|
||||
<div class="text-sm">Detailed runway & one-time events</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="navigateTo('/budget')"
|
||||
class="border-2 border-black bg-white p-4 text-left hover:shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] transition-shadow">
|
||||
<div class="font-bold uppercase mb-1">Budget Planning</div>
|
||||
<div class="text-sm">Manage expenses & savings</div>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue