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:
Jennie Robinson Faber 2025-09-08 09:39:30 +01:00
parent 09d8794d72
commit 864a81065c
23 changed files with 3211 additions and 1978 deletions

View file

@ -1,17 +1,18 @@
<template>
<div class="hidden" data-ui="advanced_accordion_v1" />
<UAccordion :items="accordionItems" :multiple="false" class="shadow-sm rounded-xl">
<UAccordion
:items="accordionItems"
:multiple="false"
class="shadow-sm rounded-xl">
<template #advanced>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Scenarios Panel -->
<div class="space-y-6">
<h4 class="font-semibold">Scenarios</h4>
<USelect
v-model="scenario"
:options="scenarioOptions"
placeholder="Select scenario"
/>
placeholder="Select scenario" />
</div>
<!-- Stress Test Panel -->
@ -19,15 +20,18 @@
<h4 class="font-semibold">Stress Test</h4>
<div class="space-y-2">
<div>
<label class="text-xs text-gray-600">Revenue Delay (months)</label>
<label class="text-xs text-gray-600"
>Revenue Delay (months)</label
>
<URange
v-model="stress.revenueDelay"
:min="0"
:max="6"
:step="1"
class="mt-1"
/>
<div class="text-xs text-gray-500">{{ stress.revenueDelay }} months</div>
class="mt-1" />
<div class="text-xs text-gray-500">
{{ stress.revenueDelay }} months
</div>
</div>
<div>
<label class="text-xs text-gray-600">Cost Shock (%)</label>
@ -36,14 +40,12 @@
:min="0"
:max="30"
:step="1"
class="mt-1"
/>
<div class="text-xs text-gray-500">{{ stress.costShockPct }}%</div>
class="mt-1" />
<div class="text-xs text-gray-500">
{{ stress.costShockPct }}%
</div>
</div>
<UCheckbox
v-model="stress.grantLost"
label="Grant lost"
/>
<UCheckbox v-model="stress.grantLost" label="Grant lost" />
<div class="text-sm text-gray-600 pt-2 border-t">
Projected runway: {{ projectedRunway }}
</div>
@ -57,8 +59,7 @@
<UButton
size="xs"
variant="outline"
@click="showMilestoneModal = true"
>
@click="showMilestoneModal = true">
+ Add
</UButton>
</div>
@ -66,20 +67,22 @@
<div
v-for="milestone in milestoneStatus()"
:key="milestone.id"
class="flex items-center justify-between text-sm"
>
class="flex items-center justify-between text-sm">
<div class="flex items-center gap-2">
<span>{{ milestone.willReach ? '✅' : '⚠️' }}</span>
<span>{{ milestone.willReach ? "✅" : "⚠️" }}</span>
<span>{{ milestone.label }}</span>
</div>
<span class="text-xs text-gray-600">{{ formatDate(milestone.date) }}</span>
<span class="text-xs text-gray-600">{{
formatDate(milestone.date)
}}</span>
</div>
<div v-if="milestones.length === 0" class="text-sm text-gray-600 italic">
<div
v-if="milestones.length === 0"
class="text-sm text-gray-600 italic">
No milestones yet
</div>
</div>
</div>
</div>
</template>
</UAccordion>
@ -90,14 +93,16 @@
<template #header>
<h3 class="text-lg font-medium">Add Milestone</h3>
</template>
<div class="space-y-4">
<UFormGroup label="Label">
<UInput v-model="newMilestone.label" placeholder="e.g. Product launch" />
</UFormGroup>
<UFormGroup label="Date">
<UFormField label="Label">
<UInput
v-model="newMilestone.label"
placeholder="e.g. Product launch" />
</UFormField>
<UFormField label="Date">
<UInput v-model="newMilestone.date" type="date" />
</UFormGroup>
</UFormField>
</div>
<template #footer>
@ -105,7 +110,9 @@
<UButton variant="ghost" @click="showMilestoneModal = false">
Cancel
</UButton>
<UButton @click="addNewMilestone" :disabled="!newMilestone.label || !newMilestone.date">
<UButton
@click="addNewMilestone"
:disabled="!newMilestone.label || !newMilestone.date">
Add Milestone
</UButton>
</div>
@ -115,72 +122,78 @@
</template>
<script setup lang="ts">
const {
scenario,
setScenario,
stress,
updateStress,
milestones,
milestoneStatus,
addMilestone,
runwayMonths
} = useCoopBuilder()
const {
scenario,
setScenario,
stress,
updateStress,
milestones,
milestoneStatus,
addMilestone,
runwayMonths,
} = useCoopBuilder();
// Accordion setup
const accordionItems = [{
label: 'Advanced Planning',
icon: 'i-heroicons-wrench-screwdriver',
slot: 'advanced',
defaultOpen: false
}]
const accordionItems = [
{
label: "Advanced Planning",
icon: "i-heroicons-wrench-screwdriver",
slot: "advanced",
defaultOpen: false,
},
];
// Scenarios
const scenarioOptions = [
{ label: 'Current', value: 'current' },
{ label: 'Quit Day Jobs', value: 'quit-jobs' },
{ label: 'Start Production', value: 'start-production' },
{ label: 'Custom', value: 'custom' }
]
{ label: "Current", value: "current" },
{ label: "Quit Day Jobs", value: "quit-jobs" },
{ label: "Start Production", value: "start-production" },
{ label: "Custom", value: "custom" },
];
// Stress test with live preview
const projectedRunway = computed(() => {
const months = runwayMonths(undefined, { useStress: true })
if (!isFinite(months)) return '∞'
if (months < 1) return '<1m'
return `${Math.round(months)}m`
})
const months = runwayMonths(undefined, { useStress: true });
if (!isFinite(months)) return "∞";
if (months < 1) return "<1m";
return `${Math.round(months)}m`;
});
// Milestones modal
const showMilestoneModal = ref(false)
const showMilestoneModal = ref(false);
const newMilestone = reactive({
label: '',
date: ''
})
label: "",
date: "",
});
function addNewMilestone() {
if (!newMilestone.label || !newMilestone.date) return
addMilestone(newMilestone.label, newMilestone.date)
newMilestone.label = ''
newMilestone.date = ''
showMilestoneModal.value = false
if (!newMilestone.label || !newMilestone.date) return;
addMilestone(newMilestone.label, newMilestone.date);
newMilestone.label = "";
newMilestone.date = "";
showMilestoneModal.value = false;
}
function formatDate(dateStr: string): string {
return new Date(dateStr).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
})
return new Date(dateStr).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
}
// Watch scenario changes
watch(scenario, (newValue) => {
setScenario(newValue)
})
setScenario(newValue);
});
// Watch stress changes
watch(stress, (newValue) => {
updateStress(newValue)
}, { deep: true })
</script>
watch(
stress,
(newValue) => {
updateStress(newValue);
},
{ deep: true }
);
</script>