refactor: remove deprecated components and streamline member coverage calculations, enhance budget management with improved payroll handling, and update UI elements for better clarity

This commit is contained in:
Jennie Robinson Faber 2025-09-06 09:48:57 +01:00
parent 983aeca2dc
commit 09d8794d72
42 changed files with 2166 additions and 2974 deletions

View file

@ -57,8 +57,27 @@
</div>
</div>
<!-- Empty State Message -->
<div v-if="activeView === 'monthly' && budgetWorksheet.revenue.length === 0 && budgetWorksheet.expenses.length === 0" class="border-4 border-black bg-white shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] p-12 text-center">
<div class="max-w-md mx-auto space-y-6">
<div class="text-6xl">📊</div>
<h3 class="text-xl font-bold text-black">No budget data found</h3>
<p class="text-gray-600">
Your budget is empty. Complete the setup wizard to add your revenue streams, team members, and expenses.
</p>
<div class="flex justify-center">
<NuxtLink
to="/coop-builder"
class="inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-black hover:bg-gray-800 border-2 border-black transition-colors"
>
Complete Setup Wizard
</NuxtLink>
</div>
</div>
</div>
<!-- Monthly View -->
<div v-if="activeView === 'monthly'" class="border-4 border-black bg-white shadow-[8px_8px_0px_0px_rgba(0,0,0,1)]">
<div v-else-if="activeView === 'monthly'" class="border-4 border-black bg-white shadow-[8px_8px_0px_0px_rgba(0,0,0,1)]">
<div class="overflow-x-auto">
<table class="w-full border-collapse text-sm">
<thead>
@ -236,13 +255,38 @@
</div>
</div>
</div>
<!-- Special settings gear for payroll oncosts -->
<div v-if="item.id === 'expense-payroll-oncosts'" class="flex items-center gap-1">
<UButton
@click="showPayrollOncostModal = true"
size="xs"
variant="ghost"
icon="i-heroicons-cog-6-tooth"
:ui="{
base: 'text-gray-500 hover:bg-gray-100 opacity-0 group-hover:opacity-100 transition-all',
}"
/>
<UButton
@click="removeItem('expenses', item.id)"
size="xs"
variant="ghost"
:ui="{
base: 'text-red-600 hover:bg-red-100 opacity-0 group-hover:opacity-100 transition-none',
}"
>
×
</UButton>
</div>
<!-- Regular delete button for non-payroll items -->
<UButton
v-else
@click="removeItem('expenses', item.id)"
size="xs"
variant="ghost"
:ui="{
base:
'text-red-600 hover:bg-red-100 opacity-0 group-hover:opacity-100 transition-none',
base: 'text-red-600 hover:bg-red-100 opacity-0 group-hover:opacity-100 transition-none',
}"
>
×
@ -316,7 +360,7 @@
<!-- Add Revenue Modal -->
<UModal v-model:open="showAddRevenueModal">
<template #header>
<div class="flex items-center justify-between border-b-4 border-black pb-4">
<div class="flex items-center justify-between pb-4">
<div>
<h3 class="text-xl font-bold text-black">Add Revenue Source</h3>
<p class="mt-1 text-sm text-gray-600">
@ -334,7 +378,7 @@
</template>
<template #body>
<div class="space-y-5 py-4">
<div class="space-y-6 py-4">
<UFormGroup label="Category" required>
<USelectMenu
v-model="newRevenue.category"
@ -417,7 +461,7 @@
</template>
<template #footer>
<div class="flex justify-end gap-3 border-t-2 border-gray-200 pt-4">
<div class="flex justify-end gap-3 pt-4">
<UButton @click="showAddRevenueModal = false" variant="outline" size="md">
Cancel
</UButton>
@ -438,7 +482,7 @@
<!-- Add Expense Modal -->
<UModal v-model:open="showAddExpenseModal">
<template #header>
<div class="flex items-center justify-between border-b-4 border-black pb-4">
<div class="flex items-center justify-between pb-4">
<div>
<h3 class="text-xl font-bold text-black">Add Expense Item</h3>
<p class="mt-1 text-sm text-gray-600">Create a new expense for your budget</p>
@ -454,7 +498,7 @@
</template>
<template #body>
<div class="space-y-5 py-4">
<div class="space-y-6 py-4">
<UFormGroup label="Category" required>
<USelectMenu
v-model="newExpense.category"
@ -528,7 +572,7 @@
</template>
<template #footer>
<div class="flex justify-end gap-3 border-t-2 border-gray-200 pt-4">
<div class="flex justify-end gap-3 pt-4">
<UButton @click="showAddExpenseModal = false" variant="outline" size="md">
Cancel
</UButton>
@ -544,20 +588,102 @@
</div>
</template>
</UModal>
<!-- Payroll Oncost Settings Modal -->
<PayrollOncostModal
v-model:open="showPayrollOncostModal"
@save="handlePayrollOncostUpdate"
/>
</div>
</template>
<script setup lang="ts">
// Stores
// Stores and synchronization
const budgetStore = useBudgetStore();
const streamsStore = useStreamsStore();
const membersStore = useMembersStore();
const policiesStore = usePoliciesStore();
const coopBuilderStore = useCoopBuilderStore();
const { initSync, getStreams, getMembers, unifiedStreams, unifiedMembers } = useStoreSync();
// Initialize synchronization and budget data
const initializeBudgetPage = async () => {
console.log('📊 Budget Page: Starting initialization');
// First, sync stores to ensure data is available (now async)
await initSync();
// Additional wait to ensure all reactive updates have propagated
await nextTick();
// Now check if we need to initialize budget data
const hasCoopData = coopBuilderStore.streams.length > 0 || coopBuilderStore.members.length > 0;
const hasBudgetData = budgetStore.budgetWorksheet.revenue.length > 0 || budgetStore.budgetWorksheet.expenses.length > 0;
console.log('📊 Budget Page: After sync - hasCoopData:', hasCoopData, 'hasBudgetData:', hasBudgetData);
console.log('📊 Budget Page: Coop streams:', coopBuilderStore.streams);
console.log('📊 Budget Page: Coop members:', coopBuilderStore.members);
if (hasCoopData && !hasBudgetData) {
console.log('📊 Budget Page: Initializing budget from coop data');
// Force initialization since we have coop data but no budget data
await budgetStore.forceInitializeFromWizardData();
// Refresh the page data after initialization
await nextTick();
} else if (!hasCoopData && !hasBudgetData) {
console.log('📊 Budget Page: No data found in any store');
// Try one more time to get the data after a delay
await new Promise(resolve => setTimeout(resolve, 100));
const retryCoopData = coopBuilderStore.streams.length > 0 || coopBuilderStore.members.length > 0;
if (retryCoopData) {
console.log('📊 Budget Page: Found data on retry, initializing');
await budgetStore.forceInitializeFromWizardData();
}
} else if (hasBudgetData) {
console.log('📊 Budget Page: Budget data already exists, using existing data');
}
};
// Initialize on mount
onMounted(async () => {
// Always force reinitialize when navigating to budget page
// This ensures changes from setup are reflected
await budgetStore.forceInitializeFromWizardData();
// Mark initial load as complete after initialization
await nextTick();
initialLoadComplete = true;
});
// Track if initial load is complete
let initialLoadComplete = false;
// Re-initialize when coop data changes (but not on initial load)
watch([() => coopBuilderStore.streams, () => coopBuilderStore.members], async (newVal, oldVal) => {
// Skip the initial trigger
if (!initialLoadComplete) {
return;
}
// Only reinitialize if we actually have new data
const hasNewStreams = coopBuilderStore.streams.length > 0;
const hasNewMembers = coopBuilderStore.members.length > 0;
if (hasNewStreams || hasNewMembers) {
console.log('📊 Budget Page: Coop data changed, reinitializing');
await nextTick();
await initializeBudgetPage();
}
}, { deep: true });
// Use reactive synchronized data
const syncedStreams = unifiedStreams;
const syncedMembers = unifiedMembers;
// State
const activeView = ref('monthly');
const showAddRevenueModal = ref(false);
const showAddExpenseModal = ref(false);
const showPayrollOncostModal = ref(false);
const activeTab = ref(0);
const highlightedItemId = ref<string | null>(null);
@ -656,21 +782,15 @@ const monthlyHeaders = computed(() => {
return headers;
});
// Grouped data
// Grouped data with safe fallbacks
const budgetWorksheet = computed(() => budgetStore.budgetWorksheet || { revenue: [], expenses: [] });
const groupedRevenue = computed(() => budgetStore.groupedRevenue);
const groupedExpenses = computed(() => budgetStore.groupedExpenses);
const monthlyTotals = computed(() => budgetStore.monthlyTotals);
// Initialize on mount
onMounted(async () => {
try {
// Only initialize if not already done (preserve persisted data)
await budgetStore.initializeFromWizardData();
} catch (error) {
console.error("Error initializing budget page:", error);
}
});
// Removed duplicate onMounted - initialization is now handled above
// Add revenue item
function addRevenueItem() {
@ -824,6 +944,7 @@ function resetWorksheet() {
}
}
// Export budget
function exportBudget() {
const data = {
@ -861,6 +982,15 @@ function getNetIncomeClass(amount: number): string {
return "text-gray-600";
}
// Payroll oncost handling
function handlePayrollOncostUpdate(newPercentage: number) {
// Update the coop store
coopBuilderStore.payrollOncostPct = newPercentage;
// Refresh the budget to reflect the new oncost percentage
budgetStore.refreshPayrollInBudget();
}
// SEO
useSeoMeta({
title: "Budget Worksheet - Plan Your Co-op's Financial Future",