chore: update application configuration and UI components for improved styling and functionality
This commit is contained in:
parent
0af6b17792
commit
37ab8d7bab
54 changed files with 23293 additions and 1666 deletions
305
pages/index.vue
305
pages/index.vue
|
|
@ -17,42 +17,42 @@
|
|||
|
||||
<!-- 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)"
|
||||
<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
|
||||
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."
|
||||
/>
|
||||
|
||||
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 class="text-3xl font-bold" :class="concentrationColor">
|
||||
{{ topSourcePct }}%
|
||||
</div>
|
||||
<ConcentrationChip
|
||||
status="red"
|
||||
:top-source-pct="65"
|
||||
<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-gray-500 mt-2">
|
||||
Most of your money comes from one place. Add another stream to reduce risk.
|
||||
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>
|
||||
|
|
@ -70,31 +70,31 @@
|
|||
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') }]"
|
||||
/>
|
||||
: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') }]"
|
||||
/>
|
||||
:description="cashBreachDescription"
|
||||
: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') }]"
|
||||
/>
|
||||
: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."
|
||||
/>
|
||||
description="Alex has reached 85% of quarterly deferred cap." />
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
|
|
@ -104,31 +104,37 @@
|
|||
<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="p-4 border border-neutral-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 class="text-2xl font-bold text-orange-600 mb-1">
|
||||
{{ scenarioMetrics.current.runway }} months
|
||||
</div>
|
||||
<p class="text-xs text-neutral-600">Continue existing plan</p>
|
||||
</div>
|
||||
|
||||
<div class="p-4 border border-gray-200 rounded-lg">
|
||||
|
||||
<div class="p-4 border border-neutral-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 class="text-2xl font-bold text-red-600 mb-1">
|
||||
{{ scenarioMetrics.quitJobs.runway }} months
|
||||
</div>
|
||||
<p class="text-xs text-neutral-600">Full-time co-op work</p>
|
||||
</div>
|
||||
|
||||
<div class="p-4 border border-gray-200 rounded-lg">
|
||||
|
||||
<div class="p-4 border border-neutral-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 class="text-2xl font-bold text-yellow-600 mb-1">
|
||||
{{ scenarioMetrics.startProduction.runway }} months
|
||||
</div>
|
||||
<p class="text-xs text-neutral-600">Launch development</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
|
|
@ -159,34 +165,44 @@
|
|||
<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>
|
||||
<UIcon name="i-heroicons-x-circle" class="text-neutral-400" />
|
||||
<span class="text-sm text-neutral-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>
|
||||
<UIcon name="i-heroicons-x-circle" class="text-neutral-400" />
|
||||
<span class="text-sm text-neutral-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>
|
||||
<p class="text-xs text-neutral-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>
|
||||
<span class="text-neutral-600">Surplus</span>
|
||||
<span class="font-medium text-green-600">{{
|
||||
$format.currency(metrics.finances.surplus || 0)
|
||||
}}</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>
|
||||
<span class="text-neutral-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>
|
||||
<span class="text-neutral-600">Savings gap</span>
|
||||
<span class="font-medium text-blue-600">{{
|
||||
$format.currency(metrics.finances.savingsGap || 0)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
|
|
@ -200,48 +216,44 @@
|
|||
|
||||
<!-- Quick Actions -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<UButton
|
||||
block
|
||||
variant="ghost"
|
||||
<UButton
|
||||
block
|
||||
variant="ghost"
|
||||
class="justify-start h-auto p-4"
|
||||
@click="navigateTo('/mix')"
|
||||
>
|
||||
@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 class="text-xs text-neutral-500">Plan revenue streams</div>
|
||||
</div>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
block
|
||||
variant="ghost"
|
||||
|
||||
<UButton
|
||||
block
|
||||
variant="ghost"
|
||||
class="justify-start h-auto p-4"
|
||||
@click="navigateTo('/cash')"
|
||||
>
|
||||
@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 class="text-xs text-neutral-500">13-week cash flow</div>
|
||||
</div>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
block
|
||||
variant="ghost"
|
||||
|
||||
<UButton
|
||||
block
|
||||
variant="ghost"
|
||||
class="justify-start h-auto p-4"
|
||||
@click="navigateTo('/scenarios')"
|
||||
>
|
||||
@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 class="text-xs text-neutral-500">What-if analysis</div>
|
||||
</div>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
block
|
||||
|
||||
<UButton
|
||||
block
|
||||
color="primary"
|
||||
class="justify-start h-auto p-4"
|
||||
@click="navigateTo('/session')"
|
||||
>
|
||||
@click="navigateTo('/session')">
|
||||
<div class="text-left">
|
||||
<div class="font-medium">Next Session</div>
|
||||
<div class="text-xs">Value Accounting</div>
|
||||
|
|
@ -253,11 +265,126 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
// Dashboard page
|
||||
const { $format } = useNuxtApp()
|
||||
const { calculateMetrics } = useFixtures()
|
||||
const { $format } = useNuxtApp();
|
||||
|
||||
// Load fixture data and calculate metrics
|
||||
const metrics = await calculateMetrics()
|
||||
// Use real store data instead of fixtures
|
||||
const membersStore = useMembersStore();
|
||||
const policiesStore = usePoliciesStore();
|
||||
const streamsStore = useStreamsStore();
|
||||
const budgetStore = useBudgetStore();
|
||||
const cashStore = useCashStore();
|
||||
|
||||
// Calculate metrics from real store data
|
||||
const metrics = computed(() => {
|
||||
const totalTargetHours = membersStore.members.reduce(
|
||||
(sum, member) => sum + (member.capacity?.targetHours || 0),
|
||||
0
|
||||
);
|
||||
|
||||
const totalTargetRevenue = streamsStore.streams.reduce(
|
||||
(sum, stream) => sum + (stream.targetMonthlyAmount || 0),
|
||||
0
|
||||
);
|
||||
|
||||
const totalOverheadCosts = budgetStore.overheadCosts.reduce(
|
||||
(sum, cost) => sum + (cost.amount || 0),
|
||||
0
|
||||
);
|
||||
|
||||
const monthlyPayroll =
|
||||
totalTargetHours *
|
||||
policiesStore.equalHourlyWage *
|
||||
(1 + policiesStore.payrollOncostPct / 100);
|
||||
|
||||
const monthlyBurn = monthlyPayroll + totalOverheadCosts;
|
||||
|
||||
// Use actual cash store values
|
||||
const totalLiquid = cashStore.currentCash + cashStore.currentSavings;
|
||||
|
||||
const runway = monthlyBurn > 0 ? totalLiquid / monthlyBurn : 0;
|
||||
|
||||
return {
|
||||
totalTargetHours,
|
||||
totalTargetRevenue,
|
||||
monthlyPayroll,
|
||||
monthlyBurn,
|
||||
runway,
|
||||
finances: {
|
||||
currentBalances: {
|
||||
cash: cashStore.currentCash,
|
||||
savings: cashStore.currentSavings,
|
||||
totalLiquid,
|
||||
},
|
||||
policies: {
|
||||
equalHourlyWage: policiesStore.equalHourlyWage,
|
||||
payrollOncostPct: policiesStore.payrollOncostPct,
|
||||
savingsTargetMonths: policiesStore.savingsTargetMonths,
|
||||
minCashCushionAmount: policiesStore.minCashCushionAmount,
|
||||
},
|
||||
deferredLiabilities: {
|
||||
totalDeferred: membersStore.members.reduce(
|
||||
(sum, m) =>
|
||||
sum + (m.deferredHours || 0) * policiesStore.equalHourlyWage,
|
||||
0
|
||||
),
|
||||
},
|
||||
surplus: Math.max(0, totalTargetRevenue - monthlyBurn),
|
||||
savingsGap: Math.max(
|
||||
0,
|
||||
policiesStore.savingsTargetMonths * monthlyBurn -
|
||||
cashStore.currentSavings
|
||||
),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Calculate concentration metrics
|
||||
const topSourcePct = computed(() => {
|
||||
if (streamsStore.streams.length === 0) return 0;
|
||||
const amounts = streamsStore.streams.map((s) => s.targetMonthlyAmount || 0);
|
||||
const total = amounts.reduce((sum, amt) => sum + amt, 0);
|
||||
return total > 0 ? Math.round((Math.max(...amounts) / total) * 100) : 0;
|
||||
});
|
||||
|
||||
const concentrationStatus = computed(() => {
|
||||
if (topSourcePct.value > 50) return "red";
|
||||
if (topSourcePct.value > 35) return "yellow";
|
||||
return "green";
|
||||
});
|
||||
|
||||
const concentrationColor = computed(() => {
|
||||
if (topSourcePct.value > 50) return "text-red-600";
|
||||
if (topSourcePct.value > 35) return "text-yellow-600";
|
||||
return "text-green-600";
|
||||
});
|
||||
|
||||
// Calculate scenario metrics
|
||||
const scenarioMetrics = computed(() => {
|
||||
const baseRunway = metrics.value.runway;
|
||||
return {
|
||||
current: {
|
||||
runway: Math.round(baseRunway * 100) / 100 || 0,
|
||||
},
|
||||
quitJobs: {
|
||||
runway: Math.round(baseRunway * 0.7 * 100) / 100 || 0, // Shorter runway due to higher costs
|
||||
},
|
||||
startProduction: {
|
||||
runway: Math.round(baseRunway * 0.8 * 100) / 100 || 0, // Moderate impact
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Cash breach description
|
||||
const cashBreachDescription = computed(() => {
|
||||
// Check cash store for first breach week from projections
|
||||
const breachWeek = cashStore.weeklyProjections.find(
|
||||
(week) => week.breachesCushion
|
||||
);
|
||||
if (breachWeek) {
|
||||
return `Week ${breachWeek.number} would drop below your minimum cushion.`;
|
||||
}
|
||||
return "No cushion breach currently projected.";
|
||||
});
|
||||
|
||||
const onExport = () => {
|
||||
const data = exportAll();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue