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 @@
>
-
-
+
+
+
+
-
-
-
- $
+
+
Initial Values
+
+
+
+
+
+
+
+
+ $
+
+
+
+
+ This will divide ${{ newRevenue.annualAmount || 0 }} equally across all 12 months
+ (${{ newRevenue.annualAmount ? Math.round(newRevenue.annualAmount / 12) : 0 }} per month)
+
+
+
+
+
+
+
+
+ $
+
+
+
+
+ This will set ${{ newRevenue.monthlyAmount || 0 }} for all months
+
+
+
+
+
+
+ The revenue item will be created with no initial values. You can fill them in later.
+
+
-
-
+
+
@@ -356,79 +398,6 @@
-
-
-
-
-
-
-
-
-
-
-
- $
-
-
-
-
- This will divide the amount equally across all 12 months
-
-
-
-
-
-
-
-
- $
-
-
-
-
- This will set the same value for all months
-
-
-
-
-
-
-
-
- Cancel
-
- Distribute Amount
-
-
- Apply to All Months
-
-
-
@@ -453,7 +422,7 @@
@@ -466,20 +435,59 @@
/>
-
-
-
- $
+
+
Initial Values
+
+
+
+
+
+
+
+
+ $
+
+
+
+
+ This will divide ${{ newExpense.annualAmount || 0 }} equally across all 12 months
+ (${{ newExpense.annualAmount ? Math.round(newExpense.annualAmount / 12) : 0 }} per month)
+
+
+
+
+
+
+
+
+ $
+
+
+
+
+ This will set ${{ newExpense.monthlyAmount || 0 }} for all months
+
+
+
+
+
+
+ The expense item will be created with no initial values. You can fill them in later.
+
+
-
-
+
+
@@ -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: {