feat: add initial application structure with configuration, UI components, and state management
This commit is contained in:
parent
fadf94002c
commit
0af6b17792
56 changed files with 6137 additions and 129 deletions
214
pages/wizard.vue
Normal file
214
pages/wizard.vue
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
<template>
|
||||
<section class="py-8 space-y-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-2xl font-semibold">Setup Wizard</h2>
|
||||
<div class="flex items-center gap-3">
|
||||
<UBadge color="primary" variant="subtle"
|
||||
>Step {{ currentStep }} of 5</UBadge
|
||||
>
|
||||
<UButton
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
@click="resetWizard"
|
||||
:disabled="isResetting">
|
||||
Reset Wizard
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<UCard>
|
||||
<div class="space-y-6">
|
||||
<!-- Step 1: Members -->
|
||||
<div v-if="currentStep === 1">
|
||||
<WizardMembersStep @save-status="handleSaveStatus" />
|
||||
</div>
|
||||
|
||||
<!-- Step 2: Wage & Policies -->
|
||||
<div v-if="currentStep === 2">
|
||||
<WizardPoliciesStep @save-status="handleSaveStatus" />
|
||||
</div>
|
||||
|
||||
<!-- Step 3: Costs -->
|
||||
<div v-if="currentStep === 3">
|
||||
<WizardCostsStep @save-status="handleSaveStatus" />
|
||||
</div>
|
||||
|
||||
<!-- Step 4: Revenue -->
|
||||
<div v-if="currentStep === 4">
|
||||
<WizardRevenueStep @save-status="handleSaveStatus" />
|
||||
</div>
|
||||
|
||||
<!-- Step 5: Review -->
|
||||
<div v-if="currentStep === 5">
|
||||
<WizardReviewStep @complete="completeWizard" @reset="resetWizard" />
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<div class="flex justify-between items-center pt-6 border-t">
|
||||
<UButton v-if="currentStep > 1" variant="ghost" @click="previousStep">
|
||||
Previous
|
||||
</UButton>
|
||||
<div v-else></div>
|
||||
|
||||
<!-- Save status indicator -->
|
||||
<div class="flex items-center gap-2">
|
||||
<UIcon
|
||||
v-if="saveStatus === 'saving'"
|
||||
name="i-heroicons-arrow-path"
|
||||
class="w-4 h-4 animate-spin text-gray-500" />
|
||||
<UIcon
|
||||
v-if="saveStatus === 'saved'"
|
||||
name="i-heroicons-check-circle"
|
||||
class="w-4 h-4 text-green-500" />
|
||||
<span v-if="saveStatus === 'saving'" class="text-xs text-gray-500"
|
||||
>Saving...</span
|
||||
>
|
||||
<span v-if="saveStatus === 'saved'" class="text-xs text-green-600"
|
||||
>Saved</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<UButton
|
||||
v-if="currentStep < 5"
|
||||
@click="nextStep"
|
||||
:disabled="!isHydrated || !canProceed">
|
||||
Next
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<!-- Step validation messages -->
|
||||
<div
|
||||
v-if="!canProceed && currentStep < 5"
|
||||
class="text-sm text-red-600 mt-2">
|
||||
{{ validationMessage }}
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// Stores
|
||||
const membersStore = useMembersStore();
|
||||
const policiesStore = usePoliciesStore();
|
||||
const streamsStore = useStreamsStore();
|
||||
const budgetStore = useBudgetStore();
|
||||
|
||||
// Wizard state (persisted)
|
||||
const wizardStore = useWizardStore();
|
||||
const currentStep = computed({
|
||||
get: () => wizardStore.currentStep,
|
||||
set: (val: number) => wizardStore.setStep(val),
|
||||
});
|
||||
const saveStatus = ref("");
|
||||
const isResetting = ref(false);
|
||||
const isHydrated = ref(false);
|
||||
onMounted(() => {
|
||||
isHydrated.value = true;
|
||||
});
|
||||
|
||||
// Debug: log step and validation state
|
||||
watch(
|
||||
() => ({
|
||||
step: currentStep.value,
|
||||
membersValid: membersStore.isValid,
|
||||
policiesValid: policiesStore.isValid,
|
||||
streamsValid: streamsStore.hasValidStreams,
|
||||
members: membersStore.members,
|
||||
memberValidation: membersStore.validationDetails,
|
||||
}),
|
||||
(state) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug("Wizard state:", JSON.parse(JSON.stringify(state)));
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
// Save status handler
|
||||
function handleSaveStatus(status: "saving" | "saved" | "error") {
|
||||
saveStatus.value = status;
|
||||
if (status === "saved") {
|
||||
// Clear status after delay
|
||||
setTimeout(() => {
|
||||
if (saveStatus.value === "saved") {
|
||||
saveStatus.value = "";
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
// Step validation
|
||||
const canProceed = computed(() => {
|
||||
switch (currentStep.value) {
|
||||
case 1:
|
||||
return membersStore.isValid;
|
||||
case 2:
|
||||
return policiesStore.isValid;
|
||||
case 3:
|
||||
return true; // Costs are optional
|
||||
case 4:
|
||||
return streamsStore.hasValidStreams;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
const validationMessage = computed(() => {
|
||||
switch (currentStep.value) {
|
||||
case 1:
|
||||
if (membersStore.members.length === 0)
|
||||
return "Add at least one member to continue";
|
||||
return "Complete all required member fields";
|
||||
case 2:
|
||||
if (policiesStore.equalHourlyWage <= 0)
|
||||
return "Enter an hourly wage greater than 0";
|
||||
return "Complete all required policy fields";
|
||||
case 4:
|
||||
return "Add at least one valid revenue stream";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
});
|
||||
|
||||
function nextStep() {
|
||||
if (currentStep.value < 5 && canProceed.value) {
|
||||
currentStep.value++;
|
||||
}
|
||||
}
|
||||
|
||||
function previousStep() {
|
||||
if (currentStep.value > 1) {
|
||||
currentStep.value--;
|
||||
}
|
||||
}
|
||||
|
||||
function completeWizard() {
|
||||
// Mark setup as complete and redirect
|
||||
navigateTo("/scenarios");
|
||||
}
|
||||
|
||||
async function resetWizard() {
|
||||
isResetting.value = true;
|
||||
|
||||
// Reset all stores
|
||||
membersStore.resetMembers();
|
||||
policiesStore.resetPolicies();
|
||||
streamsStore.resetStreams();
|
||||
budgetStore.resetBudgetOverhead();
|
||||
|
||||
// Reset wizard state
|
||||
wizardStore.reset();
|
||||
saveStatus.value = "";
|
||||
|
||||
// Small delay for UX
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
isResetting.value = false;
|
||||
}
|
||||
|
||||
// SEO
|
||||
useSeoMeta({
|
||||
title: "Setup Wizard - Configure Your Co-op",
|
||||
description:
|
||||
"Set up your co-op members, policies, costs, and revenue streams.",
|
||||
});
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue