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
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue