289 lines
10 KiB
Vue
289 lines
10 KiB
Vue
<template>
|
|
<section class="py-8 space-y-6">
|
|
<div class="flex items-center justify-between">
|
|
<h2 class="text-2xl font-semibold">Dashboard</h2>
|
|
<div class="flex gap-2">
|
|
<UButton
|
|
icon="i-heroicons-arrow-down-tray"
|
|
color="gray"
|
|
@click="onExport"
|
|
>Export JSON</UButton
|
|
>
|
|
<UButton icon="i-heroicons-arrow-up-tray" color="gray" @click="onImport"
|
|
>Import JSON</UButton
|
|
>
|
|
</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.`"
|
|
/>
|
|
|
|
<CoverageMeter
|
|
:funded-paid-hours="Math.round(metrics.totalTargetHours * 0.65)"
|
|
:target-hours="metrics.totalTargetHours"
|
|
description="Funded hours vs target capacity across all members."
|
|
/>
|
|
|
|
<ReserveMeter
|
|
:current-savings="metrics.finances.currentBalances.savings"
|
|
:savings-target-months="metrics.finances.policies.savingsTargetMonths"
|
|
:monthly-burn="metrics.monthlyBurn"
|
|
description="Build savings to your target before increasing paid hours."
|
|
/>
|
|
|
|
<UCard>
|
|
<div class="text-center space-y-3">
|
|
<div class="text-3xl font-bold text-red-600">65%</div>
|
|
<div class="text-sm text-gray-600">
|
|
<GlossaryTooltip
|
|
term="Concentration"
|
|
term-id="concentration"
|
|
definition="Dependence on few revenue sources. UI shows top source percentage."
|
|
/>
|
|
</div>
|
|
<ConcentrationChip
|
|
status="red"
|
|
:top-source-pct="65"
|
|
:show-percentage="false"
|
|
variant="soft"
|
|
/>
|
|
<p class="text-xs text-gray-500 mt-2">
|
|
Most of your money comes from one place. Add another stream to reduce risk.
|
|
</p>
|
|
</div>
|
|
</UCard>
|
|
</div>
|
|
|
|
<!-- Alerts Section -->
|
|
<UCard>
|
|
<template #header>
|
|
<h3 class="text-lg font-medium">Alerts</h3>
|
|
</template>
|
|
<div class="space-y-3">
|
|
<UAlert
|
|
color="red"
|
|
variant="subtle"
|
|
icon="i-heroicons-exclamation-triangle"
|
|
title="Revenue Concentration Risk"
|
|
description="Most of your money comes from one place. Add another stream to reduce risk."
|
|
:actions="[{ label: 'Plan Mix', click: () => navigateTo('/mix') }]"
|
|
/>
|
|
<UAlert
|
|
color="orange"
|
|
variant="subtle"
|
|
icon="i-heroicons-calendar"
|
|
title="Cash Cushion Breach Forecast"
|
|
description="Week 7 would drop below your minimum cushion."
|
|
:actions="[{ label: 'View Calendar', click: () => navigateTo('/cash') }]"
|
|
/>
|
|
<UAlert
|
|
color="yellow"
|
|
variant="subtle"
|
|
icon="i-heroicons-banknotes"
|
|
title="Savings Below Target"
|
|
description="Build savings to your target before increasing paid hours."
|
|
:actions="[{ label: 'View Progress', click: () => navigateTo('/budget') }]"
|
|
/>
|
|
<UAlert
|
|
color="amber"
|
|
variant="subtle"
|
|
icon="i-heroicons-clock"
|
|
title="Over-Deferred Member"
|
|
description="Alex has reached 85% of quarterly deferred cap."
|
|
/>
|
|
</div>
|
|
</UCard>
|
|
|
|
<!-- Scenario Snapshots -->
|
|
<UCard>
|
|
<template #header>
|
|
<h3 class="text-lg font-medium">Scenario Snapshots</h3>
|
|
</template>
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<div class="p-4 border border-gray-200 rounded-lg">
|
|
<div class="flex items-center justify-between mb-2">
|
|
<h4 class="font-medium text-sm">Operate Current</h4>
|
|
<UBadge color="green" variant="subtle" size="xs">Active</UBadge>
|
|
</div>
|
|
<div class="text-2xl font-bold text-orange-600 mb-1">2.8 months</div>
|
|
<p class="text-xs text-gray-600">Continue existing plan</p>
|
|
</div>
|
|
|
|
<div class="p-4 border border-gray-200 rounded-lg">
|
|
<div class="flex items-center justify-between mb-2">
|
|
<h4 class="font-medium text-sm">Quit Day Jobs</h4>
|
|
<UBadge color="gray" variant="subtle" size="xs">Scenario</UBadge>
|
|
</div>
|
|
<div class="text-2xl font-bold text-red-600 mb-1">1.4 months</div>
|
|
<p class="text-xs text-gray-600">Full-time co-op work</p>
|
|
</div>
|
|
|
|
<div class="p-4 border border-gray-200 rounded-lg">
|
|
<div class="flex items-center justify-between mb-2">
|
|
<h4 class="font-medium text-sm">Start Production</h4>
|
|
<UBadge color="gray" variant="subtle" size="xs">Scenario</UBadge>
|
|
</div>
|
|
<div class="text-2xl font-bold text-yellow-600 mb-1">2.1 months</div>
|
|
<p class="text-xs text-gray-600">Launch development</p>
|
|
</div>
|
|
</div>
|
|
<div class="mt-4">
|
|
<UButton variant="outline" @click="navigateTo('/scenarios')">
|
|
Compare All Scenarios
|
|
</UButton>
|
|
</div>
|
|
</UCard>
|
|
|
|
<!-- Next Value Accounting Session -->
|
|
<UCard>
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-lg font-medium">Next Value Accounting Session</h3>
|
|
<UBadge color="blue" variant="subtle">January 2024</UBadge>
|
|
</div>
|
|
</template>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div>
|
|
<h4 class="font-medium mb-3">Session Preparation</h4>
|
|
<div class="space-y-2">
|
|
<div class="flex items-center gap-3">
|
|
<UIcon name="i-heroicons-check-circle" class="text-green-500" />
|
|
<span class="text-sm">Month closed & reviewed</span>
|
|
</div>
|
|
<div class="flex items-center gap-3">
|
|
<UIcon name="i-heroicons-check-circle" class="text-green-500" />
|
|
<span class="text-sm">Contributions logged</span>
|
|
</div>
|
|
<div class="flex items-center gap-3">
|
|
<UIcon name="i-heroicons-x-circle" class="text-gray-400" />
|
|
<span class="text-sm text-gray-600">Surplus calculated</span>
|
|
</div>
|
|
<div class="flex items-center gap-3">
|
|
<UIcon name="i-heroicons-x-circle" class="text-gray-400" />
|
|
<span class="text-sm text-gray-600">Member needs reviewed</span>
|
|
</div>
|
|
</div>
|
|
<div class="mt-4">
|
|
<UProgress value="50" :max="100" color="blue" />
|
|
<p class="text-xs text-gray-600 mt-1">2 of 4 items complete</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<h4 class="font-medium mb-3">Available for Distribution</h4>
|
|
<div class="space-y-2">
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-gray-600">Surplus</span>
|
|
<span class="font-medium text-green-600">{{ $format.currency(1200) }}</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-gray-600">Deferred owed</span>
|
|
<span class="font-medium text-orange-600">{{ $format.currency(metrics.finances.deferredLiabilities.totalDeferred) }}</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-gray-600">Savings gap</span>
|
|
<span class="font-medium text-blue-600">{{ $format.currency(2000) }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="mt-4">
|
|
<UButton color="primary" @click="navigateTo('/session')">
|
|
Start Session
|
|
</UButton>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</UCard>
|
|
|
|
<!-- 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-gray-500">Plan revenue streams</div>
|
|
</div>
|
|
</UButton>
|
|
|
|
<UButton
|
|
block
|
|
variant="ghost"
|
|
class="justify-start h-auto p-4"
|
|
@click="navigateTo('/cash')"
|
|
>
|
|
<div class="text-left">
|
|
<div class="font-medium">Cash Calendar</div>
|
|
<div class="text-xs text-gray-500">13-week cash flow</div>
|
|
</div>
|
|
</UButton>
|
|
|
|
<UButton
|
|
block
|
|
variant="ghost"
|
|
class="justify-start h-auto p-4"
|
|
@click="navigateTo('/scenarios')"
|
|
>
|
|
<div class="text-left">
|
|
<div class="font-medium">Scenarios</div>
|
|
<div class="text-xs text-gray-500">What-if analysis</div>
|
|
</div>
|
|
</UButton>
|
|
|
|
<UButton
|
|
block
|
|
color="primary"
|
|
class="justify-start h-auto p-4"
|
|
@click="navigateTo('/session')"
|
|
>
|
|
<div class="text-left">
|
|
<div class="font-medium">Next Session</div>
|
|
<div class="text-xs">Value Accounting</div>
|
|
</div>
|
|
</UButton>
|
|
</div>
|
|
</section>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
// Dashboard page
|
|
const { $format } = useNuxtApp()
|
|
const { calculateMetrics } = useFixtures()
|
|
|
|
// Load fixture data and calculate metrics
|
|
const metrics = await calculateMetrics()
|
|
|
|
const onExport = () => {
|
|
const data = exportAll();
|
|
const blob = new Blob([JSON.stringify(data, null, 2)], {
|
|
type: "application/json",
|
|
});
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement("a");
|
|
a.href = url;
|
|
a.download = "urgent-tools.json";
|
|
a.click();
|
|
URL.revokeObjectURL(url);
|
|
};
|
|
|
|
const onImport = async () => {
|
|
const input = document.createElement("input");
|
|
input.type = "file";
|
|
input.accept = "application/json";
|
|
input.onchange = async () => {
|
|
const file = input.files?.[0];
|
|
if (!file) return;
|
|
const text = await file.text();
|
|
importAll(JSON.parse(text));
|
|
};
|
|
input.click();
|
|
};
|
|
|
|
const { exportAll, importAll } = useFixtureIO();
|
|
</script>
|