app/components/dashboard/AdvancedAccordion.vue

186 lines
No EOL
5.2 KiB
Vue

<template>
<div class="hidden" data-ui="advanced_accordion_v1" />
<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"
/>
</div>
<!-- Stress Test Panel -->
<div class="space-y-6">
<h4 class="font-semibold">Stress Test</h4>
<div class="space-y-2">
<div>
<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>
</div>
<div>
<label class="text-xs text-gray-600">Cost Shock (%)</label>
<URange
v-model="stress.costShockPct"
:min="0"
:max="30"
:step="1"
class="mt-1"
/>
<div class="text-xs text-gray-500">{{ stress.costShockPct }}%</div>
</div>
<UCheckbox
v-model="stress.grantLost"
label="Grant lost"
/>
<div class="text-sm text-gray-600 pt-2 border-t">
Projected runway: {{ projectedRunway }}
</div>
</div>
</div>
<!-- Milestones Panel -->
<div class="space-y-6">
<div class="flex items-center justify-between">
<h4 class="font-semibold">Milestones</h4>
<UButton
size="xs"
variant="outline"
@click="showMilestoneModal = true"
>
+ Add
</UButton>
</div>
<div class="space-y-2">
<div
v-for="milestone in milestoneStatus()"
:key="milestone.id"
class="flex items-center justify-between text-sm"
>
<div class="flex items-center gap-2">
<span>{{ milestone.willReach ? '✅' : '⚠️' }}</span>
<span>{{ milestone.label }}</span>
</div>
<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">
No milestones yet
</div>
</div>
</div>
</div>
</template>
</UAccordion>
<!-- Milestone Modal -->
<UModal v-model="showMilestoneModal">
<UCard>
<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">
<UInput v-model="newMilestone.date" type="date" />
</UFormGroup>
</div>
<template #footer>
<div class="flex justify-end gap-2">
<UButton variant="ghost" @click="showMilestoneModal = false">
Cancel
</UButton>
<UButton @click="addNewMilestone" :disabled="!newMilestone.label || !newMilestone.date">
Add Milestone
</UButton>
</div>
</template>
</UCard>
</UModal>
</template>
<script setup lang="ts">
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
}]
// Scenarios
const scenarioOptions = [
{ 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`
})
// Milestones modal
const showMilestoneModal = ref(false)
const newMilestone = reactive({
label: '',
date: ''
})
function addNewMilestone() {
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'
})
}
// Watch scenario changes
watch(scenario, (newValue) => {
setScenario(newValue)
})
// Watch stress changes
watch(stress, (newValue) => {
updateStress(newValue)
}, { deep: true })
</script>