From fc2d9ed56bf42b419ca5aeafd49bd8aea2d19a25 Mon Sep 17 00:00:00 2001 From: Jennie Robinson Faber Date: Sun, 24 Aug 2025 07:38:30 +0100 Subject: [PATCH] refactor: streamline WizardPoliciesStep component layout and improve budget management forms with enhanced initial value handling --- components/WizardPoliciesStep.vue | 112 ++++---- pages/budget.vue | 423 +++++++++++++++--------------- stores/budget.ts | 4 +- 3 files changed, 272 insertions(+), 267 deletions(-) diff --git a/components/WizardPoliciesStep.vue b/components/WizardPoliciesStep.vue index 8102d69..ecaed22 100644 --- a/components/WizardPoliciesStep.vue +++ b/components/WizardPoliciesStep.vue @@ -3,19 +3,13 @@
-

- Set your wage & pay policy -

+

Set your wage & pay policy

Choose how to allocate payroll among members and set the base hourly rate.

- + Export @@ -26,7 +20,11 @@

Pay Allocation Policy

-
- +
Role Bands (monthly € or weight)
-
- {{ member.role || 'No role' }} +
+ {{ member.role || "No role" }}
- +
- +

Base Hourly Wage

@@ -78,7 +80,8 @@ placeholder="0.00" size="xl" class="text-4xl font-black w-full h-20" - @update:model-value="validateAndSaveWage"> + @update:model-value="validateAndSaveWage" + > @@ -98,9 +101,9 @@ const coop = useCoopBuilder(); const store = useCoopBuilderStore(); // Initialize from store -const selectedPolicy = ref(coop.policy.value?.relationship || 'equal-pay') -const roleBands = ref(coop.policy.value?.roleBands || {}) -const wageText = ref(String(store.equalHourlyWage || '')) +const selectedPolicy = ref(coop.policy.value?.relationship || "equal-pay"); +const roleBands = ref(coop.policy.value?.roleBands || {}); +const wageText = ref(String(store.equalHourlyWage || "")); function parseNumberInput(val: unknown): number { if (typeof val === "number") return val; @@ -114,43 +117,54 @@ function parseNumberInput(val: unknown): number { // Pay policy options const policyOptions = [ - { value: 'equal-pay', label: 'Equal pay - Everyone gets the same monthly amount' }, - { value: 'needs-weighted', label: 'Needs-weighted - Allocate based on minimum needs' }, - { value: 'hours-weighted', label: 'Hours-weighted - Allocate based on hours worked' }, - { value: 'role-banded', label: 'Role-banded - Different amounts per role' } -] + { + value: "equal-pay", + label: "Equal pay - Everyone gets the same monthly amount", + }, + { + value: "needs-weighted", + label: "Needs-weighted - Allocate based on minimum needs", + }, + { + value: "hours-weighted", + label: "Hours-weighted - Allocate based on hours worked", + }, + { value: "role-banded", label: "Role-banded - Different amounts per role" }, +]; // Already initialized above with store values const uniqueRoles = computed(() => { - const roles = new Set(coop.members.value.map(m => m.role || '')) - return Array.from(roles).map(role => ({ role })) -}) + const roles = new Set(coop.members.value.map((m) => m.role || "")); + return Array.from(roles).map((role) => ({ role })); +}); function updatePolicy(value: string) { - selectedPolicy.value = value - coop.setPolicy(value as "equal-pay" | "needs-weighted" | "hours-weighted" | "role-banded") - + selectedPolicy.value = value; + coop.setPolicy( + value as "equal-pay" | "needs-weighted" | "hours-weighted" | "role-banded" + ); + // Trigger payroll reallocation after policy change - const allocatedMembers = coop.allocatePayroll() - allocatedMembers.forEach(m => { - coop.upsertMember(m) - }) - + const allocatedMembers = coop.allocatePayroll(); + allocatedMembers.forEach((m) => { + coop.upsertMember(m); + }); + emit("save-status", "saved"); } function updateRoleBands() { - coop.setRoleBands(roleBands.value) - + coop.setRoleBands(roleBands.value); + // Trigger payroll reallocation after role bands change - if (selectedPolicy.value === 'role-banded') { - const allocatedMembers = coop.allocatePayroll() - allocatedMembers.forEach(m => { - coop.upsertMember(m) - }) + if (selectedPolicy.value === "role-banded") { + const allocatedMembers = coop.allocatePayroll(); + allocatedMembers.forEach((m) => { + coop.upsertMember(m); + }); } - + emit("save-status", "saved"); } @@ -163,14 +177,14 @@ function validateAndSaveWage(value: string) { wageText.value = cleanValue; if (!isNaN(numValue) && numValue >= 0) { - coop.setEqualWage(numValue) - + coop.setEqualWage(numValue); + // Trigger payroll reallocation after wage change - const allocatedMembers = coop.allocatePayroll() - allocatedMembers.forEach(m => { - coop.upsertMember(m) - }) - + const allocatedMembers = coop.allocatePayroll(); + allocatedMembers.forEach((m) => { + coop.upsertMember(m); + }); + emit("save-status", "saved"); } } diff --git a/pages/budget.vue b/pages/budget.vue index f9a1cc9..f436f78 100644 --- a/pages/budget.vue +++ b/pages/budget.vue @@ -95,15 +95,12 @@ >
- +
- +
+ + + + - - - @@ -356,79 +398,6 @@ - - - - - - @@ -453,7 +422,7 @@ @@ -466,20 +435,59 @@ /> - - - @@ -513,7 +521,6 @@ const policiesStore = usePoliciesStore(); // State const showAddRevenueModal = ref(false); const showAddExpenseModal = ref(false); -const showHelperModal = ref(false); const activeTab = ref(0); const highlightedItemId = ref(null); @@ -523,6 +530,8 @@ const newRevenue = ref({ subcategory: "", name: "", initialAmount: 0, + annualAmount: 0, + monthlyAmount: 0, }); const newExpense = ref({ @@ -530,27 +539,14 @@ const newExpense = ref({ subcategory: "", name: "", initialAmount: 0, -}); - -// Helper config -const helperConfig = ref({ - selectedItem: null as string | null, annualAmount: 0, monthlyAmount: 0, - startAmount: 0, - percentChange: 0, - baseAmount: 0, }); -// Selected item details for helper modal -const selectedItemDetails = computed(() => { - if (!helperConfig.value.selectedItem) return null; - return allBudgetItems.value.find((item) => item.id === helperConfig.value.selectedItem); -}); -// Helper tabs configuration -const activeHelperTab = ref(0); // UTabs uses index, not key -const helperTabs = [ +// New modal helper tabs +const revenueInitialTab = ref(0); +const revenueHelperTabs = [ { key: "annual", label: "Annual Distribution", @@ -561,12 +557,49 @@ const helperTabs = [ label: "Set All Months", icon: "i-heroicons-squares-2x2", }, + { + key: "empty", + label: "Start Empty", + icon: "i-heroicons-minus", + }, ]; -// Data from store +const expenseInitialTab = ref(0); +const expenseHelperTabs = [ + { + key: "annual", + label: "Annual Distribution", + icon: "i-heroicons-calendar", + }, + { + key: "monthly", + label: "Set All Months", + icon: "i-heroicons-squares-2x2", + }, + { + key: "empty", + label: "Start Empty", + icon: "i-heroicons-minus", + }, +]; + +// Data from store - just use the string arrays directly const revenueCategories = computed(() => budgetStore.revenueCategories); const expenseCategories = computed(() => budgetStore.expenseCategories); +// Revenue subcategories based on selected category +const revenueSubcategories = computed(() => { + if (!newRevenue.value.category) return []; + return budgetStore.revenueSubcategories[newRevenue.value.category] || []; +}); + +// Clear subcategory when category changes +watch(() => newRevenue.value.category, (newCategory, oldCategory) => { + if (newCategory !== oldCategory) { + newRevenue.value.subcategory = ""; + } +}); + // Generate monthly headers const monthlyHeaders = computed(() => { const headers = []; @@ -591,41 +624,11 @@ const groupedRevenue = computed(() => budgetStore.groupedRevenue); const groupedExpenses = computed(() => budgetStore.groupedExpenses); const monthlyTotals = computed(() => budgetStore.monthlyTotals); -// All budget items for helper dropdown -const allBudgetItems = computed(() => { - const items: Array<{ - id: string; - label: string; - type: "revenue" | "expenses"; - data: any; - }> = []; - - budgetStore.budgetWorksheet.revenue.forEach((item: any) => { - items.push({ - id: item.id, - label: `[Revenue] ${item.name}`, - type: "revenue", - data: item, - }); - }); - - budgetStore.budgetWorksheet.expenses.forEach((item: any) => { - items.push({ - id: item.id, - label: `[Expense] ${item.name}`, - type: "expenses", - data: item, - }); - }); - - return items; -}); // Initialize on mount onMounted(async () => { try { - // Always re-initialize to get latest wizard data - budgetStore.isInitialized = false; + // Only initialize if not already done (preserve persisted data) await budgetStore.initializeFromWizardData(); } catch (error) { console.error("Error initializing budget page:", error); @@ -637,20 +640,36 @@ function addRevenueItem() { const id = budgetStore.addBudgetItem( "revenue", newRevenue.value.name, - newRevenue.value.category + newRevenue.value.category, + newRevenue.value.subcategory ); - // Set initial amount for all months if provided - if (newRevenue.value.initialAmount > 0) { + // Apply helper values based on selected tab + const activeTab = revenueHelperTabs[revenueInitialTab.value]; + + if (activeTab?.key === 'annual' && Number(newRevenue.value.annualAmount) > 0) { + // Annual distribution + const monthlyAmount = Math.round(newRevenue.value.annualAmount / 12); monthlyHeaders.value.forEach((month) => { budgetStore.updateMonthlyValue( "revenue", id, month.key, - newRevenue.value.initialAmount.toString() + monthlyAmount.toString() + ); + }); + } else if (activeTab?.key === 'monthly' && Number(newRevenue.value.monthlyAmount) > 0) { + // Set all months + monthlyHeaders.value.forEach((month) => { + budgetStore.updateMonthlyValue( + "revenue", + id, + month.key, + newRevenue.value.monthlyAmount.toString() ); }); } + // If tab === 2 (empty), don't set any values // Reset form and close modal newRevenue.value = { @@ -658,7 +677,10 @@ function addRevenueItem() { subcategory: "", name: "", initialAmount: 0, + annualAmount: 0, + monthlyAmount: 0, }; + revenueInitialTab.value = 0; showAddRevenueModal.value = false; } @@ -670,17 +692,32 @@ function addExpenseItem() { newExpense.value.category ); - // Set initial amount for all months if provided - if (newExpense.value.initialAmount > 0) { + // Apply helper values based on selected tab + const activeExpenseTab = expenseHelperTabs[expenseInitialTab.value]; + + if (activeExpenseTab?.key === 'annual' && Number(newExpense.value.annualAmount) > 0) { + // Annual distribution + const monthlyAmount = Math.round(newExpense.value.annualAmount / 12); monthlyHeaders.value.forEach((month) => { budgetStore.updateMonthlyValue( "expenses", id, month.key, - newExpense.value.initialAmount.toString() + monthlyAmount.toString() + ); + }); + } else if (activeExpenseTab?.key === 'monthly' && Number(newExpense.value.monthlyAmount) > 0) { + // Set all months + monthlyHeaders.value.forEach((month) => { + budgetStore.updateMonthlyValue( + "expenses", + id, + month.key, + newExpense.value.monthlyAmount.toString() ); }); } + // If tab === 2 (empty), don't set any values // Reset form and close modal newExpense.value = { @@ -688,7 +725,10 @@ function addExpenseItem() { subcategory: "", name: "", initialAmount: 0, + annualAmount: 0, + monthlyAmount: 0, }; + expenseInitialTab.value = 0; showAddExpenseModal.value = false; } @@ -726,14 +766,6 @@ function handleEnter(event: KeyboardEvent) { input.blur(); } -// Open helper modal for specific item -function openHelperForItem(item: any) { - helperConfig.value.selectedItem = item.id; - helperConfig.value.annualAmount = 0; - helperConfig.value.monthlyAmount = 0; - activeTab.value = 0; // Reset to first tab - showHelperModal.value = true; -} // Highlight row after changes function highlightRow(itemId: string) { @@ -743,47 +775,6 @@ function highlightRow(itemId: string) { }, 1500); } -// Helper functions -function distributeAnnualAmount() { - if (!helperConfig.value.selectedItem || !helperConfig.value.annualAmount) return; - - const item = allBudgetItems.value.find((i) => i.id === helperConfig.value.selectedItem); - if (!item) return; - - const monthlyAmount = Math.round(helperConfig.value.annualAmount / 12); - monthlyHeaders.value.forEach((month) => { - budgetStore.updateMonthlyValue( - item.type, - item.id, - month.key, - monthlyAmount.toString() - ); - }); - - helperConfig.value.annualAmount = 0; - highlightRow(item.id); - showHelperModal.value = false; -} - -function setAllMonths() { - if (!helperConfig.value.selectedItem || !helperConfig.value.monthlyAmount) return; - - const item = allBudgetItems.value.find((i) => i.id === helperConfig.value.selectedItem); - if (!item) return; - - monthlyHeaders.value.forEach((month) => { - budgetStore.updateMonthlyValue( - item.type, - item.id, - month.key, - helperConfig.value.monthlyAmount.toString() - ); - }); - - helperConfig.value.monthlyAmount = 0; - highlightRow(item.id); - showHelperModal.value = false; -} // Reset worksheet function resetWorksheet() { diff --git a/stores/budget.ts b/stores/budget.ts index 66b2318..f6bf34e 100644 --- a/stores/budget.ts +++ b/stores/budget.ts @@ -661,7 +661,7 @@ export const useBudgetStore = defineStore( } } - function addBudgetItem(category, name, selectedCategory = "") { + function addBudgetItem(category, name, selectedCategory = "", subcategory = "") { const id = `${category}-${Date.now()}`; // Create empty monthly values for next 12 months @@ -681,7 +681,7 @@ export const useBudgetStore = defineStore( mainCategory: selectedCategory || (category === "revenue" ? "Games & Products" : "Other Expenses"), - subcategory: "", // Will be set by user via dropdown + subcategory: subcategory || "", source: "user", monthlyValues, values: {