app/components/dashboard/AdvancedAccordion.vue

199 lines
5.3 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-neutral-600"
>Revenue Delay (months)</label
>
<URange
v-model="stress.revenueDelay"
:min="0"
:max="6"
:step="1"
class="mt-1" />
<div class="text-xs text-neutral-500">
{{ stress.revenueDelay }} months
</div>
</div>
<div>
<label class="text-xs text-neutral-600">Cost Shock (%)</label>
<URange
v-model="stress.costShockPct"
:min="0"
:max="30"
:step="1"
class="mt-1" />
<div class="text-xs text-neutral-500">
{{ stress.costShockPct }}%
</div>
</div>
<UCheckbox v-model="stress.grantLost" label="Grant lost" />
<div class="text-sm text-neutral-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-neutral-600">{{
formatDate(milestone.date)
}}</span>
</div>
<div
v-if="milestones.length === 0"
class="text-sm text-neutral-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">
<UFormField label="Label">
<UInput
v-model="newMilestone.label"
placeholder="e.g. Product launch" />
</UFormField>
<UFormField label="Date">
<UInput v-model="newMilestone.date" type="date" />
</UFormField>
</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>