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:
parent
983aeca2dc
commit
09d8794d72
42 changed files with 2166 additions and 2974 deletions
168
pages/budget.vue
168
pages/budget.vue
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue