refactor: streamline WizardPoliciesStep component layout and improve budget management forms with enhanced initial value handling

This commit is contained in:
Jennie Robinson Faber 2025-08-24 07:38:30 +01:00
parent 4cea1f71fe
commit fc2d9ed56b
3 changed files with 272 additions and 267 deletions

View file

@ -3,19 +3,13 @@
<!-- Section Header with Export Controls --> <!-- Section Header with Export Controls -->
<div class="flex items-center justify-between mb-8"> <div class="flex items-center justify-between mb-8">
<div> <div>
<h3 class="text-2xl font-black text-black mb-2"> <h3 class="text-2xl font-black text-black mb-2">Set your wage & pay policy</h3>
Set your wage & pay policy
</h3>
<p class="text-neutral-600"> <p class="text-neutral-600">
Choose how to allocate payroll among members and set the base hourly rate. Choose how to allocate payroll among members and set the base hourly rate.
</p> </p>
</div> </div>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<UButton <UButton variant="outline" color="neutral" size="sm" @click="exportPolicies">
variant="outline"
color="gray"
size="sm"
@click="exportPolicies">
<UIcon name="i-heroicons-arrow-down-tray" class="mr-1" /> <UIcon name="i-heroicons-arrow-down-tray" class="mr-1" />
Export Export
</UButton> </UButton>
@ -26,7 +20,11 @@
<div class="p-6 border-3 border-black rounded-xl bg-white shadow-md"> <div class="p-6 border-3 border-black rounded-xl bg-white shadow-md">
<h4 class="font-bold mb-4">Pay Allocation Policy</h4> <h4 class="font-bold mb-4">Pay Allocation Policy</h4>
<div class="space-y-3"> <div class="space-y-3">
<label v-for="option in policyOptions" :key="option.value" class="flex items-start gap-3 cursor-pointer hover:bg-gray-50 p-2 rounded-lg transition-colors"> <label
v-for="option in policyOptions"
:key="option.value"
class="flex items-start gap-3 cursor-pointer hover:bg-gray-50 p-2 rounded-lg transition-colors"
>
<input <input
type="radio" type="radio"
:value="option.value" :value="option.value"
@ -37,13 +35,17 @@
<span class="text-sm flex-1">{{ option.label }}</span> <span class="text-sm flex-1">{{ option.label }}</span>
</label> </label>
</div> </div>
<!-- Role bands editor if role-banded is selected --> <!-- Role bands editor if role-banded is selected -->
<div v-if="selectedPolicy === 'role-banded'" class="mt-4 p-4 bg-gray-50 rounded-lg"> <div v-if="selectedPolicy === 'role-banded'" class="mt-4 p-4 bg-gray-50 rounded-lg">
<h5 class="text-sm font-medium mb-3">Role Bands (monthly or weight)</h5> <h5 class="text-sm font-medium mb-3">Role Bands (monthly or weight)</h5>
<div class="space-y-2"> <div class="space-y-2">
<div v-for="member in uniqueRoles" :key="member.role" class="flex items-center gap-2"> <div
<span class="text-sm w-32">{{ member.role || 'No role' }}</span> v-for="member in uniqueRoles"
:key="member.role"
class="flex items-center gap-2"
>
<span class="text-sm w-32">{{ member.role || "No role" }}</span>
<UInput <UInput
v-model="roleBands[member.role || '']" v-model="roleBands[member.role || '']"
type="text" type="text"
@ -55,7 +57,7 @@
</div> </div>
</div> </div>
</div> </div>
<UAlert <UAlert
class="mt-4" class="mt-4"
color="primary" color="primary"
@ -67,7 +69,7 @@
</template> </template>
</UAlert> </UAlert>
</div> </div>
<!-- Hourly Wage Input --> <!-- Hourly Wage Input -->
<div class="p-6 border-3 border-black rounded-xl bg-white shadow-md"> <div class="p-6 border-3 border-black rounded-xl bg-white shadow-md">
<h4 class="font-bold mb-4">Base Hourly Wage</h4> <h4 class="font-bold mb-4">Base Hourly Wage</h4>
@ -78,7 +80,8 @@
placeholder="0.00" placeholder="0.00"
size="xl" size="xl"
class="text-4xl font-black w-full h-20" class="text-4xl font-black w-full h-20"
@update:model-value="validateAndSaveWage"> @update:model-value="validateAndSaveWage"
>
<template #leading> <template #leading>
<span class="text-neutral-500 text-3xl"></span> <span class="text-neutral-500 text-3xl"></span>
</template> </template>
@ -98,9 +101,9 @@ const coop = useCoopBuilder();
const store = useCoopBuilderStore(); const store = useCoopBuilderStore();
// Initialize from store // Initialize from store
const selectedPolicy = ref(coop.policy.value?.relationship || 'equal-pay') const selectedPolicy = ref(coop.policy.value?.relationship || "equal-pay");
const roleBands = ref(coop.policy.value?.roleBands || {}) const roleBands = ref(coop.policy.value?.roleBands || {});
const wageText = ref(String(store.equalHourlyWage || '')) const wageText = ref(String(store.equalHourlyWage || ""));
function parseNumberInput(val: unknown): number { function parseNumberInput(val: unknown): number {
if (typeof val === "number") return val; if (typeof val === "number") return val;
@ -114,43 +117,54 @@ function parseNumberInput(val: unknown): number {
// Pay policy options // Pay policy options
const policyOptions = [ 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: "equal-pay",
{ value: 'hours-weighted', label: 'Hours-weighted - Allocate based on hours worked' }, label: "Equal pay - Everyone gets the same monthly amount",
{ value: 'role-banded', label: 'Role-banded - Different amounts per role' } },
] {
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 // Already initialized above with store values
const uniqueRoles = computed(() => { const uniqueRoles = computed(() => {
const roles = new Set(coop.members.value.map(m => m.role || '')) const roles = new Set(coop.members.value.map((m) => m.role || ""));
return Array.from(roles).map(role => ({ role })) return Array.from(roles).map((role) => ({ role }));
}) });
function updatePolicy(value: string) { function updatePolicy(value: string) {
selectedPolicy.value = value selectedPolicy.value = value;
coop.setPolicy(value as "equal-pay" | "needs-weighted" | "hours-weighted" | "role-banded") coop.setPolicy(
value as "equal-pay" | "needs-weighted" | "hours-weighted" | "role-banded"
);
// Trigger payroll reallocation after policy change // Trigger payroll reallocation after policy change
const allocatedMembers = coop.allocatePayroll() const allocatedMembers = coop.allocatePayroll();
allocatedMembers.forEach(m => { allocatedMembers.forEach((m) => {
coop.upsertMember(m) coop.upsertMember(m);
}) });
emit("save-status", "saved"); emit("save-status", "saved");
} }
function updateRoleBands() { function updateRoleBands() {
coop.setRoleBands(roleBands.value) coop.setRoleBands(roleBands.value);
// Trigger payroll reallocation after role bands change // Trigger payroll reallocation after role bands change
if (selectedPolicy.value === 'role-banded') { if (selectedPolicy.value === "role-banded") {
const allocatedMembers = coop.allocatePayroll() const allocatedMembers = coop.allocatePayroll();
allocatedMembers.forEach(m => { allocatedMembers.forEach((m) => {
coop.upsertMember(m) coop.upsertMember(m);
}) });
} }
emit("save-status", "saved"); emit("save-status", "saved");
} }
@ -163,14 +177,14 @@ function validateAndSaveWage(value: string) {
wageText.value = cleanValue; wageText.value = cleanValue;
if (!isNaN(numValue) && numValue >= 0) { if (!isNaN(numValue) && numValue >= 0) {
coop.setEqualWage(numValue) coop.setEqualWage(numValue);
// Trigger payroll reallocation after wage change // Trigger payroll reallocation after wage change
const allocatedMembers = coop.allocatePayroll() const allocatedMembers = coop.allocatePayroll();
allocatedMembers.forEach(m => { allocatedMembers.forEach((m) => {
coop.upsertMember(m) coop.upsertMember(m);
}) });
emit("save-status", "saved"); emit("save-status", "saved");
} }
} }

View file

@ -95,15 +95,12 @@
> >
<div class="flex items-center justify-between group"> <div class="flex items-center justify-between group">
<div class="flex-1"> <div class="flex-1">
<button <div class="text-left w-full">
@click="openHelperForItem(item)"
class="text-left hover:underline focus:outline-none focus:underline w-full"
>
<div class="font-medium">{{ item.name }}</div> <div class="font-medium">{{ item.name }}</div>
<div class="text-xs text-gray-600"> <div class="text-xs text-gray-600">
{{ item.subcategory }} {{ item.subcategory }}
</div> </div>
</button> </div>
</div> </div>
<UButton <UButton
@click="removeItem('revenue', item.id)" @click="removeItem('revenue', item.id)"
@ -204,15 +201,12 @@
> >
<div class="flex items-center justify-between group"> <div class="flex items-center justify-between group">
<div class="flex-1"> <div class="flex-1">
<button <div class="text-left w-full">
@click="openHelperForItem(item)"
class="text-left hover:underline focus:outline-none focus:underline w-full"
>
<div class="font-medium">{{ item.name }}</div> <div class="font-medium">{{ item.name }}</div>
<div class="text-xs text-gray-600"> <div class="text-xs text-gray-600">
{{ item.subcategory }} {{ item.subcategory }}
</div> </div>
</button> </div>
</div> </div>
<UButton <UButton
@click="removeItem('expenses', item.id)" @click="removeItem('expenses', item.id)"
@ -308,11 +302,20 @@
<UFormGroup label="Category" required> <UFormGroup label="Category" required>
<USelectMenu <USelectMenu
v-model="newRevenue.category" v-model="newRevenue.category"
:options="revenueCategories" :items="revenueCategories"
placeholder="Select a category" placeholder="Select a category"
/> />
</UFormGroup> </UFormGroup>
<UFormGroup label="Subcategory" :required="false">
<USelectMenu
v-model="newRevenue.subcategory"
:items="revenueSubcategories"
placeholder="Select a subcategory"
:disabled="!newRevenue.category"
/>
</UFormGroup>
<UFormGroup label="Revenue Name" required> <UFormGroup label="Revenue Name" required>
<UInput <UInput
v-model="newRevenue.name" v-model="newRevenue.name"
@ -321,20 +324,59 @@
/> />
</UFormGroup> </UFormGroup>
<UFormGroup <div class="border-t-2 border-gray-200 pt-5">
label="Initial Monthly Amount" <h4 class="text-sm font-semibold text-gray-700 mb-3">Initial Values</h4>
description="Starting amount for each month"
> <UTabs v-model="revenueInitialTab" :items="revenueHelperTabs" class="w-full">
<UInput <template #content="{ item }">
v-model.number="newRevenue.initialAmount" <!-- Annual Distribution -->
type="number" <div v-if="item.key === 'annual'" class="pt-4 space-y-4">
placeholder="0.00" <UFormGroup label="Annual Total Amount">
> <UInput
<template #leading> v-model.number="newRevenue.annualAmount"
<span class="text-gray-500">$</span> type="number"
placeholder="Enter annual amount (e.g., 12000)"
size="lg"
>
<template #leading>
<span class="text-gray-500">$</span>
</template>
</UInput>
</UFormGroup>
<p class="text-sm text-gray-600">
This will divide ${{ newRevenue.annualAmount || 0 }} equally across all 12 months
(${{ newRevenue.annualAmount ? Math.round(newRevenue.annualAmount / 12) : 0 }} per month)
</p>
</div>
<!-- Monthly Amount -->
<div v-else-if="item.key === 'monthly'" class="pt-4 space-y-4">
<UFormGroup label="Monthly Amount">
<UInput
v-model.number="newRevenue.monthlyAmount"
type="number"
placeholder="Enter monthly amount (e.g., 1000)"
size="lg"
>
<template #leading>
<span class="text-gray-500">$</span>
</template>
</UInput>
</UFormGroup>
<p class="text-sm text-gray-600">
This will set ${{ newRevenue.monthlyAmount || 0 }} for all months
</p>
</div>
<!-- Start Empty -->
<div v-else class="pt-4">
<p class="text-sm text-gray-600">
The revenue item will be created with no initial values. You can fill them in later.
</p>
</div>
</template> </template>
</UInput> </UTabs>
</UFormGroup> </div>
</div> </div>
</template> </template>
@ -356,79 +398,6 @@
</template> </template>
</UModal> </UModal>
<!-- Helper Modal -->
<UModal
v-model:open="showHelperModal"
:ui="{ wrapper: 'sm:max-w-lg', footer: 'justify-end' }"
title="Quick Entry Tools"
:description="selectedItemDetails?.label || 'Budget item'"
>
<template #body>
<div class="isolate">
<UTabs v-model="activeHelperTab" :items="helperTabs" class="w-full">
<template #content="{ item }">
<!-- Annual Distribution Content -->
<div v-if="item.key === 'annual'" class="pt-4 space-y-4">
<UFormGroup label="Annual Total Amount">
<UInput
v-model.number="helperConfig.annualAmount"
type="number"
placeholder="Enter annual amount (e.g., 12000)"
size="lg"
>
<template #leading>
<span class="text-gray-500">$</span>
</template>
</UInput>
</UFormGroup>
<p class="text-sm text-gray-600">
This will divide the amount equally across all 12 months
</p>
</div>
<!-- Monthly Amount Content -->
<div v-else-if="item.key === 'monthly'" class="pt-4 space-y-4">
<UFormGroup label="Monthly Amount">
<UInput
v-model.number="helperConfig.monthlyAmount"
type="number"
placeholder="Enter monthly amount (e.g., 1000)"
size="lg"
>
<template #leading>
<span class="text-gray-500">$</span>
</template>
</UInput>
</UFormGroup>
<p class="text-sm text-gray-600">
This will set the same value for all months
</p>
</div>
</template>
</UTabs>
</div>
</template>
<template #footer="{ close }">
<UButton @click="close" variant="outline" color="neutral"> Cancel </UButton>
<UButton
v-if="activeHelperTab === 0"
@click="distributeAnnualAmount"
:disabled="!helperConfig.annualAmount || helperConfig.annualAmount <= 0"
color="primary"
>
Distribute Amount
</UButton>
<UButton
v-else
@click="setAllMonths"
:disabled="!helperConfig.monthlyAmount || helperConfig.monthlyAmount <= 0"
color="primary"
>
Apply to All Months
</UButton>
</template>
</UModal>
<!-- Add Expense Modal --> <!-- Add Expense Modal -->
<UModal v-model:open="showAddExpenseModal"> <UModal v-model:open="showAddExpenseModal">
@ -453,7 +422,7 @@
<UFormGroup label="Category" required> <UFormGroup label="Category" required>
<USelectMenu <USelectMenu
v-model="newExpense.category" v-model="newExpense.category"
:options="expenseCategories" :items="expenseCategories"
placeholder="Select a category" placeholder="Select a category"
/> />
</UFormGroup> </UFormGroup>
@ -466,20 +435,59 @@
/> />
</UFormGroup> </UFormGroup>
<UFormGroup <div class="border-t-2 border-gray-200 pt-5">
label="Initial Monthly Amount" <h4 class="text-sm font-semibold text-gray-700 mb-3">Initial Values</h4>
description="Starting amount for each month"
> <UTabs v-model="expenseInitialTab" :items="expenseHelperTabs" class="w-full">
<UInput <template #content="{ item }">
v-model.number="newExpense.initialAmount" <!-- Annual Distribution -->
type="number" <div v-if="item.key === 'annual'" class="pt-4 space-y-4">
placeholder="0.00" <UFormGroup label="Annual Total Amount">
> <UInput
<template #leading> v-model.number="newExpense.annualAmount"
<span class="text-gray-500">$</span> type="number"
placeholder="Enter annual amount (e.g., 12000)"
size="lg"
>
<template #leading>
<span class="text-gray-500">$</span>
</template>
</UInput>
</UFormGroup>
<p class="text-sm text-gray-600">
This will divide ${{ newExpense.annualAmount || 0 }} equally across all 12 months
(${{ newExpense.annualAmount ? Math.round(newExpense.annualAmount / 12) : 0 }} per month)
</p>
</div>
<!-- Monthly Amount -->
<div v-else-if="item.key === 'monthly'" class="pt-4 space-y-4">
<UFormGroup label="Monthly Amount">
<UInput
v-model.number="newExpense.monthlyAmount"
type="number"
placeholder="Enter monthly amount (e.g., 1000)"
size="lg"
>
<template #leading>
<span class="text-gray-500">$</span>
</template>
</UInput>
</UFormGroup>
<p class="text-sm text-gray-600">
This will set ${{ newExpense.monthlyAmount || 0 }} for all months
</p>
</div>
<!-- Start Empty -->
<div v-else class="pt-4">
<p class="text-sm text-gray-600">
The expense item will be created with no initial values. You can fill them in later.
</p>
</div>
</template> </template>
</UInput> </UTabs>
</UFormGroup> </div>
</div> </div>
</template> </template>
@ -513,7 +521,6 @@ const policiesStore = usePoliciesStore();
// State // State
const showAddRevenueModal = ref(false); const showAddRevenueModal = ref(false);
const showAddExpenseModal = ref(false); const showAddExpenseModal = ref(false);
const showHelperModal = ref(false);
const activeTab = ref(0); const activeTab = ref(0);
const highlightedItemId = ref<string | null>(null); const highlightedItemId = ref<string | null>(null);
@ -523,6 +530,8 @@ const newRevenue = ref({
subcategory: "", subcategory: "",
name: "", name: "",
initialAmount: 0, initialAmount: 0,
annualAmount: 0,
monthlyAmount: 0,
}); });
const newExpense = ref({ const newExpense = ref({
@ -530,27 +539,14 @@ const newExpense = ref({
subcategory: "", subcategory: "",
name: "", name: "",
initialAmount: 0, initialAmount: 0,
});
// Helper config
const helperConfig = ref({
selectedItem: null as string | null,
annualAmount: 0, annualAmount: 0,
monthlyAmount: 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 // New modal helper tabs
const activeHelperTab = ref(0); // UTabs uses index, not key const revenueInitialTab = ref(0);
const helperTabs = [ const revenueHelperTabs = [
{ {
key: "annual", key: "annual",
label: "Annual Distribution", label: "Annual Distribution",
@ -561,12 +557,49 @@ const helperTabs = [
label: "Set All Months", label: "Set All Months",
icon: "i-heroicons-squares-2x2", 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 revenueCategories = computed(() => budgetStore.revenueCategories);
const expenseCategories = computed(() => budgetStore.expenseCategories); 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 // Generate monthly headers
const monthlyHeaders = computed(() => { const monthlyHeaders = computed(() => {
const headers = []; const headers = [];
@ -591,41 +624,11 @@ const groupedRevenue = computed(() => budgetStore.groupedRevenue);
const groupedExpenses = computed(() => budgetStore.groupedExpenses); const groupedExpenses = computed(() => budgetStore.groupedExpenses);
const monthlyTotals = computed(() => budgetStore.monthlyTotals); 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 // Initialize on mount
onMounted(async () => { onMounted(async () => {
try { try {
// Always re-initialize to get latest wizard data // Only initialize if not already done (preserve persisted data)
budgetStore.isInitialized = false;
await budgetStore.initializeFromWizardData(); await budgetStore.initializeFromWizardData();
} catch (error) { } catch (error) {
console.error("Error initializing budget page:", error); console.error("Error initializing budget page:", error);
@ -637,20 +640,36 @@ function addRevenueItem() {
const id = budgetStore.addBudgetItem( const id = budgetStore.addBudgetItem(
"revenue", "revenue",
newRevenue.value.name, newRevenue.value.name,
newRevenue.value.category newRevenue.value.category,
newRevenue.value.subcategory
); );
// Set initial amount for all months if provided // Apply helper values based on selected tab
if (newRevenue.value.initialAmount > 0) { 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) => { monthlyHeaders.value.forEach((month) => {
budgetStore.updateMonthlyValue( budgetStore.updateMonthlyValue(
"revenue", "revenue",
id, id,
month.key, 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 // Reset form and close modal
newRevenue.value = { newRevenue.value = {
@ -658,7 +677,10 @@ function addRevenueItem() {
subcategory: "", subcategory: "",
name: "", name: "",
initialAmount: 0, initialAmount: 0,
annualAmount: 0,
monthlyAmount: 0,
}; };
revenueInitialTab.value = 0;
showAddRevenueModal.value = false; showAddRevenueModal.value = false;
} }
@ -670,17 +692,32 @@ function addExpenseItem() {
newExpense.value.category newExpense.value.category
); );
// Set initial amount for all months if provided // Apply helper values based on selected tab
if (newExpense.value.initialAmount > 0) { 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) => { monthlyHeaders.value.forEach((month) => {
budgetStore.updateMonthlyValue( budgetStore.updateMonthlyValue(
"expenses", "expenses",
id, id,
month.key, 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 // Reset form and close modal
newExpense.value = { newExpense.value = {
@ -688,7 +725,10 @@ function addExpenseItem() {
subcategory: "", subcategory: "",
name: "", name: "",
initialAmount: 0, initialAmount: 0,
annualAmount: 0,
monthlyAmount: 0,
}; };
expenseInitialTab.value = 0;
showAddExpenseModal.value = false; showAddExpenseModal.value = false;
} }
@ -726,14 +766,6 @@ function handleEnter(event: KeyboardEvent) {
input.blur(); 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 // Highlight row after changes
function highlightRow(itemId: string) { function highlightRow(itemId: string) {
@ -743,47 +775,6 @@ function highlightRow(itemId: string) {
}, 1500); }, 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 // Reset worksheet
function resetWorksheet() { function resetWorksheet() {

View file

@ -661,7 +661,7 @@ export const useBudgetStore = defineStore(
} }
} }
function addBudgetItem(category, name, selectedCategory = "") { function addBudgetItem(category, name, selectedCategory = "", subcategory = "") {
const id = `${category}-${Date.now()}`; const id = `${category}-${Date.now()}`;
// Create empty monthly values for next 12 months // Create empty monthly values for next 12 months
@ -681,7 +681,7 @@ export const useBudgetStore = defineStore(
mainCategory: mainCategory:
selectedCategory || selectedCategory ||
(category === "revenue" ? "Games & Products" : "Other Expenses"), (category === "revenue" ? "Games & Products" : "Other Expenses"),
subcategory: "", // Will be set by user via dropdown subcategory: subcategory || "",
source: "user", source: "user",
monthlyValues, monthlyValues,
values: { values: {