From 848386e3ddde7517605ff6d8aed81a111a0f3e16 Mon Sep 17 00:00:00 2001 From: Jennie Robinson Faber Date: Sun, 17 Aug 2025 21:30:10 +0100 Subject: [PATCH] refactor budget worksheet layout and functionality, streamline revenue and expense management, and improve modal interactions for better user experience --- pages/budget.vue | 1061 +++++++++++++++++++++++++++++++++++----------- stores/budget.ts | 603 +++++++++++++++++--------- 2 files changed, 1222 insertions(+), 442 deletions(-) diff --git a/pages/budget.vue b/pages/budget.vue index 29e3856..ea621be 100644 --- a/pages/budget.vue +++ b/pages/budget.vue @@ -1,320 +1,893 @@ + + diff --git a/stores/budget.ts b/stores/budget.ts index eb5f511..0b39232 100644 --- a/stores/budget.ts +++ b/stores/budget.ts @@ -8,40 +8,71 @@ export const useBudgetStore = defineStore( // Canonical categories from WizardRevenueStep - matches the wizard exactly const revenueCategories = ref([ - 'Games & Products', - 'Services & Contracts', - 'Grants & Funding', - 'Community Support', - 'Partnerships', - 'Investment Income', - 'In-Kind Contributions' + "Games & Products", + "Services & Contracts", + "Grants & Funding", + "Community Support", + "Partnerships", + "Investment Income", + "In-Kind Contributions", ]); // Revenue subcategories by main category (for reference and grouping) const revenueSubcategories = ref({ - 'Games & Products': ['Direct sales', 'Platform revenue share', 'DLC/expansions', 'Merchandise'], - 'Services & Contracts': ['Contract development', 'Consulting', 'Workshops/teaching', 'Technical services'], - 'Grants & Funding': ['Government funding', 'Arts council grants', 'Foundation support', 'Research grants'], - 'Community Support': ['Patreon/subscriptions', 'Crowdfunding', 'Donations', 'Mutual aid received'], - 'Partnerships': ['Corporate partnerships', 'Academic partnerships', 'Sponsorships'], - 'Investment Income': ['Impact investment', 'Venture capital', 'Loans'], - 'In-Kind Contributions': ['Office space', 'Equipment/hardware', 'Software licenses', 'Professional services', 'Marketing/PR services', 'Legal services'] + "Games & Products": [ + "Direct sales", + "Platform revenue share", + "DLC/expansions", + "Merchandise", + ], + "Services & Contracts": [ + "Contract development", + "Consulting", + "Workshops/teaching", + "Technical services", + ], + "Grants & Funding": [ + "Government funding", + "Arts council grants", + "Foundation support", + "Research grants", + ], + "Community Support": [ + "Patreon/subscriptions", + "Crowdfunding", + "Donations", + "Mutual aid received", + ], + Partnerships: [ + "Corporate partnerships", + "Academic partnerships", + "Sponsorships", + ], + "Investment Income": ["Impact investment", "Venture capital", "Loans"], + "In-Kind Contributions": [ + "Office space", + "Equipment/hardware", + "Software licenses", + "Professional services", + "Marketing/PR services", + "Legal services", + ], }); const expenseCategories = ref([ - 'Salaries & Benefits', - 'Development Costs', - 'Equipment & Technology', - 'Marketing & Outreach', - 'Office & Operations', - 'Legal & Professional', - 'Other Expenses' + "Salaries & Benefits", + "Development Costs", + "Equipment & Technology", + "Marketing & Outreach", + "Office & Operations", + "Legal & Professional", + "Other Expenses", ]); // NEW: Budget worksheet structure (starts empty, populated from wizard data) const budgetWorksheet = ref({ revenue: [], - expenses: [] + expenses: [], }); // Track if worksheet has been initialized from wizard data @@ -54,39 +85,49 @@ export const useBudgetStore = defineStore( // Computed grouped data for category headers const groupedRevenue = computed(() => { - const groups = {}; - revenueCategories.value.forEach(category => { - groups[category] = budgetWorksheet.value.revenue.filter(item => item.mainCategory === category); + const groups: Record = {}; + revenueCategories.value.forEach((category) => { + groups[category] = budgetWorksheet.value.revenue.filter( + (item) => item.mainCategory === category + ); }); return groups; }); const groupedExpenses = computed(() => { - const groups = {}; - expenseCategories.value.forEach(category => { - groups[category] = budgetWorksheet.value.expenses.filter(item => item.mainCategory === category); + const groups: Record = {}; + expenseCategories.value.forEach((category) => { + groups[category] = budgetWorksheet.value.expenses.filter( + (item) => item.mainCategory === category + ); }); return groups; }); // Computed totals for budget worksheet (legacy - keep for backward compatibility) const budgetTotals = computed(() => { - const years = ['year1', 'year2', 'year3']; - const scenarios = ['best', 'worst', 'mostLikely']; + const years = ["year1", "year2", "year3"]; + const scenarios = ["best", "worst", "mostLikely"]; const totals = {}; - years.forEach(year => { + years.forEach((year) => { totals[year] = {}; - scenarios.forEach(scenario => { + scenarios.forEach((scenario) => { // Calculate revenue total - const revenueTotal = budgetWorksheet.value.revenue.reduce((sum, item) => { - return sum + (item.values?.[year]?.[scenario] || 0); - }, 0); + const revenueTotal = budgetWorksheet.value.revenue.reduce( + (sum, item) => { + return sum + (item.values?.[year]?.[scenario] || 0); + }, + 0 + ); // Calculate expenses total - const expensesTotal = budgetWorksheet.value.expenses.reduce((sum, item) => { - return sum + (item.values?.[year]?.[scenario] || 0); - }, 0); + const expensesTotal = budgetWorksheet.value.expenses.reduce( + (sum, item) => { + return sum + (item.values?.[year]?.[scenario] || 0); + }, + 0 + ); // Calculate net income const netIncome = revenueTotal - expensesTotal; @@ -94,7 +135,7 @@ export const useBudgetStore = defineStore( totals[year][scenario] = { revenue: revenueTotal, expenses: expensesTotal, - net: netIncome + net: netIncome, }; }); }); @@ -104,23 +145,34 @@ export const useBudgetStore = defineStore( // Monthly totals computation const monthlyTotals = computed(() => { - const totals = {}; - + const totals: Record< + string, + { revenue: number; expenses: number; net: number } + > = {}; + // Generate month keys for next 12 months const today = new Date(); for (let i = 0; i < 12; i++) { const date = new Date(today.getFullYear(), today.getMonth() + i, 1); - const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; - + const monthKey = `${date.getFullYear()}-${String( + date.getMonth() + 1 + ).padStart(2, "0")}`; + // Calculate revenue total for this month - const revenueTotal = budgetWorksheet.value.revenue.reduce((sum, item) => { - return sum + (item.monthlyValues?.[monthKey] || 0); - }, 0); + const revenueTotal = budgetWorksheet.value.revenue.reduce( + (sum, item) => { + return sum + (item.monthlyValues?.[monthKey] || 0); + }, + 0 + ); // Calculate expenses total for this month - const expensesTotal = budgetWorksheet.value.expenses.reduce((sum, item) => { - return sum + (item.monthlyValues?.[monthKey] || 0); - }, 0); + const expensesTotal = budgetWorksheet.value.expenses.reduce( + (sum, item) => { + return sum + (item.monthlyValues?.[monthKey] || 0); + }, + 0 + ); // Calculate net income const netIncome = revenueTotal - expensesTotal; @@ -128,17 +180,17 @@ export const useBudgetStore = defineStore( totals[monthKey] = { revenue: revenueTotal, expenses: expensesTotal, - net: netIncome + net: netIncome, }; } - + return totals; }); // LEGACY: Keep for backward compatibility const currentDate = new Date(); const currentYear = currentDate.getFullYear(); - const currentMonth = String(currentDate.getMonth() + 1).padStart(2, '0'); + const currentMonth = String(currentDate.getMonth() + 1).padStart(2, "0"); const currentPeriod = ref(`${currentYear}-${currentMonth}`); const currentBudget = computed(() => { @@ -222,24 +274,24 @@ export const useBudgetStore = defineStore( // Initialize worksheet from wizard data async function initializeFromWizardData() { if (isInitialized.value && budgetWorksheet.value.revenue.length > 0) { - console.log('Already initialized with data, skipping...'); + console.log("Already initialized with data, skipping..."); return; } - console.log('Initializing budget from wizard data...'); + console.log("Initializing budget from wizard data..."); // Import stores dynamically to avoid circular deps - const { useStreamsStore } = await import('./streams'); - const { useMembersStore } = await import('./members'); - const { usePoliciesStore } = await import('./policies'); - + const { useStreamsStore } = await import("./streams"); + const { useMembersStore } = await import("./members"); + const { usePoliciesStore } = await import("./policies"); + const streamsStore = useStreamsStore(); const membersStore = useMembersStore(); const policiesStore = usePoliciesStore(); - console.log('Streams:', streamsStore.streams.length, 'streams'); - console.log('Members capacity:', membersStore.capacityTotals); - console.log('Policies wage:', policiesStore.equalHourlyWage); + console.log("Streams:", streamsStore.streams.length, "streams"); + console.log("Members capacity:", membersStore.capacityTotals); + console.log("Policies wage:", policiesStore.equalHourlyWage); // Clear existing data budgetWorksheet.value.revenue = []; @@ -247,50 +299,92 @@ export const useBudgetStore = defineStore( // Add revenue streams from wizard if (streamsStore.streams.length === 0) { - console.log('No wizard streams found, adding sample data'); + console.log("No wizard streams found, adding sample data"); // Initialize with minimal demo if no wizard data exists await streamsStore.initializeWithFixtures(); } - - streamsStore.streams.forEach(stream => { + + streamsStore.streams.forEach((stream) => { const monthlyAmount = stream.targetMonthlyAmount || 0; - console.log('Adding stream:', stream.name, 'category:', stream.category, 'subcategory:', stream.subcategory, 'amount:', monthlyAmount); - console.log('Full stream object:', stream); - + console.log( + "Adding stream:", + stream.name, + "category:", + stream.category, + "subcategory:", + stream.subcategory, + "amount:", + monthlyAmount + ); + console.log("Full stream object:", stream); + // Simple category mapping - just map the key categories we know exist - let mappedCategory = 'Games & Products'; // Default - const categoryLower = (stream.category || '').toLowerCase(); - if (categoryLower === 'games' || categoryLower === 'product') mappedCategory = 'Games & Products'; - else if (categoryLower === 'services' || categoryLower === 'service') mappedCategory = 'Services & Contracts'; - else if (categoryLower === 'grants' || categoryLower === 'grant') mappedCategory = 'Grants & Funding'; - else if (categoryLower === 'community') mappedCategory = 'Community Support'; - else if (categoryLower === 'partnerships' || categoryLower === 'partnership') mappedCategory = 'Partnerships'; - else if (categoryLower === 'investment') mappedCategory = 'Investment Income'; - - console.log('Mapped category from', stream.category, 'to', mappedCategory); - + let mappedCategory = "Games & Products"; // Default + const categoryLower = (stream.category || "").toLowerCase(); + if (categoryLower === "games" || categoryLower === "product") + mappedCategory = "Games & Products"; + else if (categoryLower === "services" || categoryLower === "service") + mappedCategory = "Services & Contracts"; + else if (categoryLower === "grants" || categoryLower === "grant") + mappedCategory = "Grants & Funding"; + else if (categoryLower === "community") + mappedCategory = "Community Support"; + else if ( + categoryLower === "partnerships" || + categoryLower === "partnership" + ) + mappedCategory = "Partnerships"; + else if (categoryLower === "investment") + mappedCategory = "Investment Income"; + + console.log( + "Mapped category from", + stream.category, + "to", + mappedCategory + ); + // Create monthly values - split the annual target evenly across 12 months const monthlyValues = {}; const today = new Date(); for (let i = 0; i < 12; i++) { const date = new Date(today.getFullYear(), today.getMonth() + i, 1); - const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; + const monthKey = `${date.getFullYear()}-${String( + date.getMonth() + 1 + ).padStart(2, "0")}`; monthlyValues[monthKey] = monthlyAmount; } - console.log('Created monthly values for', stream.name, ':', monthlyValues); - + console.log( + "Created monthly values for", + stream.name, + ":", + monthlyValues + ); + budgetWorksheet.value.revenue.push({ id: `revenue-${stream.id}`, name: stream.name, mainCategory: mappedCategory, - subcategory: stream.subcategory || 'Direct sales', // Use actual subcategory from stream - source: 'wizard', + subcategory: stream.subcategory || "Direct sales", // Use actual subcategory from stream + source: "wizard", monthlyValues, values: { - year1: { best: monthlyAmount * 12, worst: monthlyAmount * 6, mostLikely: monthlyAmount * 10 }, - year2: { best: monthlyAmount * 15, worst: monthlyAmount * 8, mostLikely: monthlyAmount * 12 }, - year3: { best: monthlyAmount * 18, worst: monthlyAmount * 10, mostLikely: monthlyAmount * 15 } - } + year1: { + best: monthlyAmount * 12, + worst: monthlyAmount * 6, + mostLikely: monthlyAmount * 10, + }, + year2: { + best: monthlyAmount * 15, + worst: monthlyAmount * 8, + mostLikely: monthlyAmount * 12, + }, + year3: { + best: monthlyAmount * 18, + worst: monthlyAmount * 10, + mostLikely: monthlyAmount * 15, + }, + }, }); }); @@ -300,69 +394,101 @@ export const useBudgetStore = defineStore( const oncostPct = policiesStore.payrollOncostPct || 0; if (totalHours > 0 && hourlyWage > 0) { const monthlyPayroll = totalHours * hourlyWage * (1 + oncostPct / 100); - + // Create monthly values for payroll const monthlyValues = {}; const today = new Date(); for (let i = 0; i < 12; i++) { const date = new Date(today.getFullYear(), today.getMonth() + i, 1); - const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; + const monthKey = `${date.getFullYear()}-${String( + date.getMonth() + 1 + ).padStart(2, "0")}`; monthlyValues[monthKey] = monthlyPayroll; } - + budgetWorksheet.value.expenses.push({ - id: 'expense-payroll', - name: 'Payroll', - mainCategory: 'Salaries & Benefits', - subcategory: 'Base wages and benefits', - source: 'wizard', + id: "expense-payroll", + name: "Payroll", + mainCategory: "Salaries & Benefits", + subcategory: "Base wages and benefits", + source: "wizard", monthlyValues, values: { - year1: { best: monthlyPayroll * 12, worst: monthlyPayroll * 8, mostLikely: monthlyPayroll * 12 }, - year2: { best: monthlyPayroll * 14, worst: monthlyPayroll * 10, mostLikely: monthlyPayroll * 13 }, - year3: { best: monthlyPayroll * 16, worst: monthlyPayroll * 12, mostLikely: monthlyPayroll * 15 } - } + year1: { + best: monthlyPayroll * 12, + worst: monthlyPayroll * 8, + mostLikely: monthlyPayroll * 12, + }, + year2: { + best: monthlyPayroll * 14, + worst: monthlyPayroll * 10, + mostLikely: monthlyPayroll * 13, + }, + year3: { + best: monthlyPayroll * 16, + worst: monthlyPayroll * 12, + mostLikely: monthlyPayroll * 15, + }, + }, }); } // Add overhead costs from wizard - overheadCosts.value.forEach(cost => { + overheadCosts.value.forEach((cost) => { if (cost.amount > 0) { const annualAmount = cost.amount * 12; // Map overhead cost categories to expense categories - let expenseCategory = 'Other Expenses'; // Default - if (cost.category === 'Operations') expenseCategory = 'Office & Operations'; - else if (cost.category === 'Technology') expenseCategory = 'Equipment & Technology'; - else if (cost.category === 'Legal') expenseCategory = 'Legal & Professional'; - else if (cost.category === 'Marketing') expenseCategory = 'Marketing & Outreach'; - + let expenseCategory = "Other Expenses"; // Default + if (cost.category === "Operations") + expenseCategory = "Office & Operations"; + else if (cost.category === "Technology") + expenseCategory = "Equipment & Technology"; + else if (cost.category === "Legal") + expenseCategory = "Legal & Professional"; + else if (cost.category === "Marketing") + expenseCategory = "Marketing & Outreach"; + // Create monthly values for overhead costs const monthlyValues = {}; const today = new Date(); for (let i = 0; i < 12; i++) { const date = new Date(today.getFullYear(), today.getMonth() + i, 1); - const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; + const monthKey = `${date.getFullYear()}-${String( + date.getMonth() + 1 + ).padStart(2, "0")}`; monthlyValues[monthKey] = cost.amount; } - + budgetWorksheet.value.expenses.push({ id: `expense-${cost.id}`, name: cost.name, mainCategory: expenseCategory, subcategory: cost.name, // Use the cost name as subcategory - source: 'wizard', + source: "wizard", monthlyValues, values: { - year1: { best: annualAmount, worst: annualAmount * 0.8, mostLikely: annualAmount }, - year2: { best: annualAmount * 1.1, worst: annualAmount * 0.9, mostLikely: annualAmount * 1.05 }, - year3: { best: annualAmount * 1.2, worst: annualAmount, mostLikely: annualAmount * 1.1 } - } + year1: { + best: annualAmount, + worst: annualAmount * 0.8, + mostLikely: annualAmount, + }, + year2: { + best: annualAmount * 1.1, + worst: annualAmount * 0.9, + mostLikely: annualAmount * 1.05, + }, + year3: { + best: annualAmount * 1.2, + worst: annualAmount, + mostLikely: annualAmount * 1.1, + }, + }, }); } }); // Add production costs from wizard - productionCosts.value.forEach(cost => { + productionCosts.value.forEach((cost) => { if (cost.amount > 0) { const annualAmount = cost.amount * 12; // Create monthly values for production costs @@ -370,144 +496,204 @@ export const useBudgetStore = defineStore( const today = new Date(); for (let i = 0; i < 12; i++) { const date = new Date(today.getFullYear(), today.getMonth() + i, 1); - const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; + const monthKey = `${date.getFullYear()}-${String( + date.getMonth() + 1 + ).padStart(2, "0")}`; monthlyValues[monthKey] = cost.amount; } - + budgetWorksheet.value.expenses.push({ id: `expense-${cost.id}`, name: cost.name, - mainCategory: 'Development Costs', + mainCategory: "Development Costs", subcategory: cost.name, // Use the cost name as subcategory - source: 'wizard', + source: "wizard", monthlyValues, values: { - year1: { best: annualAmount, worst: annualAmount * 0.7, mostLikely: annualAmount * 0.9 }, - year2: { best: annualAmount * 1.2, worst: annualAmount * 0.8, mostLikely: annualAmount }, - year3: { best: annualAmount * 1.3, worst: annualAmount * 0.9, mostLikely: annualAmount * 1.1 } - } + year1: { + best: annualAmount, + worst: annualAmount * 0.7, + mostLikely: annualAmount * 0.9, + }, + year2: { + best: annualAmount * 1.2, + worst: annualAmount * 0.8, + mostLikely: annualAmount, + }, + year3: { + best: annualAmount * 1.3, + worst: annualAmount * 0.9, + mostLikely: annualAmount * 1.1, + }, + }, }); } }); // If still no data after initialization, add a sample row if (budgetWorksheet.value.revenue.length === 0) { - console.log('Adding sample revenue line'); + console.log("Adding sample revenue line"); // Create monthly values for sample revenue const monthlyValues = {}; const today = new Date(); for (let i = 0; i < 12; i++) { const date = new Date(today.getFullYear(), today.getMonth() + i, 1); - const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; + const monthKey = `${date.getFullYear()}-${String( + date.getMonth() + 1 + ).padStart(2, "0")}`; monthlyValues[monthKey] = 667; // ~8000/12 } - + budgetWorksheet.value.revenue.push({ - id: 'revenue-sample', - name: 'Sample Revenue', - mainCategory: 'Games & Products', - subcategory: 'Direct sales', - source: 'user', + id: "revenue-sample", + name: "Sample Revenue", + mainCategory: "Games & Products", + subcategory: "Direct sales", + source: "user", monthlyValues, values: { year1: { best: 10000, worst: 5000, mostLikely: 8000 }, year2: { best: 12000, worst: 6000, mostLikely: 10000 }, - year3: { best: 15000, worst: 8000, mostLikely: 12000 } - } + year3: { best: 15000, worst: 8000, mostLikely: 12000 }, + }, }); } if (budgetWorksheet.value.expenses.length === 0) { - console.log('Adding sample expense line'); + console.log("Adding sample expense line"); // Create monthly values for sample expense const monthlyValues = {}; const today = new Date(); for (let i = 0; i < 12; i++) { const date = new Date(today.getFullYear(), today.getMonth() + i, 1); - const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; + const monthKey = `${date.getFullYear()}-${String( + date.getMonth() + 1 + ).padStart(2, "0")}`; monthlyValues[monthKey] = 67; // ~800/12 } - + budgetWorksheet.value.expenses.push({ - id: 'expense-sample', - name: 'Sample Expense', - mainCategory: 'Other Expenses', - subcategory: 'Miscellaneous', - source: 'user', + id: "expense-sample", + name: "Sample Expense", + mainCategory: "Other Expenses", + subcategory: "Miscellaneous", + source: "user", monthlyValues, values: { year1: { best: 1000, worst: 500, mostLikely: 800 }, year2: { best: 1200, worst: 600, mostLikely: 1000 }, - year3: { best: 1500, worst: 800, mostLikely: 1200 } - } + year3: { best: 1500, worst: 800, mostLikely: 1200 }, + }, }); } // Debug: Log all revenue items and their categories - console.log('Final revenue items:'); - budgetWorksheet.value.revenue.forEach(item => { - console.log(`- ${item.name}: ${item.mainCategory} > ${item.subcategory} (${item.values.year1.mostLikely})`); + console.log("Final revenue items:"); + budgetWorksheet.value.revenue.forEach((item) => { + console.log( + `- ${item.name}: ${item.mainCategory} > ${item.subcategory} (${item.values.year1.mostLikely})` + ); }); - - console.log('Final expense items:'); - budgetWorksheet.value.expenses.forEach(item => { - console.log(`- ${item.name}: ${item.mainCategory} > ${item.subcategory} (${item.values.year1.mostLikely})`); + + console.log("Final expense items:"); + budgetWorksheet.value.expenses.forEach((item) => { + console.log( + `- ${item.name}: ${item.mainCategory} > ${item.subcategory} (${item.values.year1.mostLikely})` + ); }); // Ensure all items have monthlyValues and new structure - migrate existing items - [...budgetWorksheet.value.revenue, ...budgetWorksheet.value.expenses].forEach(item => { + [ + ...budgetWorksheet.value.revenue, + ...budgetWorksheet.value.expenses, + ].forEach((item) => { // Migrate to new structure if needed if (item.category && !item.mainCategory) { - console.log('Migrating item structure for:', item.name); + console.log("Migrating item structure for:", item.name); item.mainCategory = item.category; // Old category becomes mainCategory - + // Set appropriate subcategory based on the main category and item name - if (item.category === 'Games & Products') { - const gameSubcategories = ['Direct sales', 'Platform revenue share', 'DLC/expansions', 'Merchandise']; - item.subcategory = gameSubcategories.includes(item.name) ? item.name : 'Direct sales'; - } else if (item.category === 'Services & Contracts') { - const serviceSubcategories = ['Contract development', 'Consulting', 'Workshops/teaching', 'Technical services']; - item.subcategory = serviceSubcategories.includes(item.name) ? item.name : 'Contract development'; - } else if (item.category === 'Investment Income') { - const investmentSubcategories = ['Impact investment', 'Venture capital', 'Loans']; - item.subcategory = investmentSubcategories.includes(item.name) ? item.name : 'Impact investment'; - } else if (item.category === 'Salaries & Benefits') { - item.subcategory = 'Base wages and benefits'; - } else if (item.category === 'Office & Operations') { + if (item.category === "Games & Products") { + const gameSubcategories = [ + "Direct sales", + "Platform revenue share", + "DLC/expansions", + "Merchandise", + ]; + item.subcategory = gameSubcategories.includes(item.name) + ? item.name + : "Direct sales"; + } else if (item.category === "Services & Contracts") { + const serviceSubcategories = [ + "Contract development", + "Consulting", + "Workshops/teaching", + "Technical services", + ]; + item.subcategory = serviceSubcategories.includes(item.name) + ? item.name + : "Contract development"; + } else if (item.category === "Investment Income") { + const investmentSubcategories = [ + "Impact investment", + "Venture capital", + "Loans", + ]; + item.subcategory = investmentSubcategories.includes(item.name) + ? item.name + : "Impact investment"; + } else if (item.category === "Salaries & Benefits") { + item.subcategory = "Base wages and benefits"; + } else if (item.category === "Office & Operations") { // Map specific office tools to appropriate subcategories - if (item.name.toLowerCase().includes('rent') || item.name.toLowerCase().includes('office')) { - item.subcategory = 'Rent'; - } else if (item.name.toLowerCase().includes('util')) { - item.subcategory = 'Utilities'; - } else if (item.name.toLowerCase().includes('insurance')) { - item.subcategory = 'Insurance'; + if ( + item.name.toLowerCase().includes("rent") || + item.name.toLowerCase().includes("office") + ) { + item.subcategory = "Rent"; + } else if (item.name.toLowerCase().includes("util")) { + item.subcategory = "Utilities"; + } else if (item.name.toLowerCase().includes("insurance")) { + item.subcategory = "Insurance"; } else { - item.subcategory = 'Office supplies'; + item.subcategory = "Office supplies"; } } else { // For other categories, use appropriate default - item.subcategory = 'Miscellaneous'; + item.subcategory = "Miscellaneous"; } - + delete item.category; // Remove old property } - + if (!item.monthlyValues) { - console.log('Migrating item to monthly values:', item.name); + console.log("Migrating item to monthly values:", item.name); item.monthlyValues = {}; const today = new Date(); for (let i = 0; i < 12; i++) { const date = new Date(today.getFullYear(), today.getMonth() + i, 1); - const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; + const monthKey = `${date.getFullYear()}-${String( + date.getMonth() + 1 + ).padStart(2, "0")}`; // Try to use most likely value divided by 12, or default to 0 const yearlyValue = item.values?.year1?.mostLikely || 0; item.monthlyValues[monthKey] = Math.round(yearlyValue / 12); } - console.log('Added monthly values to', item.name, ':', item.monthlyValues); + console.log( + "Added monthly values to", + item.name, + ":", + item.monthlyValues + ); } }); - console.log('Initialization complete. Revenue items:', budgetWorksheet.value.revenue.length, 'Expense items:', budgetWorksheet.value.expenses.length); + console.log( + "Initialization complete. Revenue items:", + budgetWorksheet.value.revenue.length, + "Expense items:", + budgetWorksheet.value.expenses.length + ); isInitialized.value = true; } @@ -515,7 +701,7 @@ export const useBudgetStore = defineStore( // NEW: Budget worksheet functions function updateBudgetValue(category, itemId, year, scenario, value) { const items = budgetWorksheet.value[category]; - const item = items.find(i => i.id === itemId); + const item = items.find((i) => i.id === itemId); if (item) { item.values[year][scenario] = Number(value) || 0; } @@ -523,7 +709,7 @@ export const useBudgetStore = defineStore( function updateMonthlyValue(category, itemId, monthKey, value) { const items = budgetWorksheet.value[category]; - const item = items.find(i => i.id === itemId); + const item = items.find((i) => i.id === itemId); if (item) { if (!item.monthlyValues) { item.monthlyValues = {}; @@ -532,30 +718,34 @@ export const useBudgetStore = defineStore( } } - function addBudgetItem(category, name, selectedCategory = '') { + function addBudgetItem(category, name, selectedCategory = "") { const id = `${category}-${Date.now()}`; - + // Create empty monthly values for next 12 months const monthlyValues = {}; const today = new Date(); for (let i = 0; i < 12; i++) { const date = new Date(today.getFullYear(), today.getMonth() + i, 1); - const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; + const monthKey = `${date.getFullYear()}-${String( + date.getMonth() + 1 + ).padStart(2, "0")}`; monthlyValues[monthKey] = 0; } - + const newItem = { id, name, - mainCategory: selectedCategory || (category === 'revenue' ? 'Games & Products' : 'Other Expenses'), - subcategory: '', // Will be set by user via dropdown - source: 'user', + mainCategory: + selectedCategory || + (category === "revenue" ? "Games & Products" : "Other Expenses"), + subcategory: "", // Will be set by user via dropdown + source: "user", monthlyValues, values: { year1: { best: 0, worst: 0, mostLikely: 0 }, year2: { best: 0, worst: 0, mostLikely: 0 }, - year3: { best: 0, worst: 0, mostLikely: 0 } - } + year3: { best: 0, worst: 0, mostLikely: 0 }, + }, }; budgetWorksheet.value[category].push(newItem); return id; @@ -563,7 +753,7 @@ export const useBudgetStore = defineStore( function removeBudgetItem(category, itemId) { const items = budgetWorksheet.value[category]; - const index = items.findIndex(i => i.id === itemId); + const index = items.findIndex((i) => i.id === itemId); if (index > -1) { items.splice(index, 1); } @@ -571,7 +761,7 @@ export const useBudgetStore = defineStore( function renameBudgetItem(category, itemId, newName) { const items = budgetWorksheet.value[category]; - const item = items.find(i => i.id === itemId); + const item = items.find((i) => i.id === itemId); if (item) { item.name = newName; } @@ -579,16 +769,22 @@ export const useBudgetStore = defineStore( function updateBudgetCategory(category, itemId, newCategory) { const items = budgetWorksheet.value[category]; - const item = items.find(i => i.id === itemId); + const item = items.find((i) => i.id === itemId); if (item) { item.category = newCategory; } } function addCustomCategory(type, categoryName) { - if (type === 'revenue' && !revenueCategories.value.includes(categoryName)) { + if ( + type === "revenue" && + !revenueCategories.value.includes(categoryName) + ) { revenueCategories.value.push(categoryName); - } else if (type === 'expenses' && !expenseCategories.value.includes(categoryName)) { + } else if ( + type === "expenses" && + !expenseCategories.value.includes(categoryName) + ) { expenseCategories.value.push(categoryName); } } @@ -601,9 +797,12 @@ export const useBudgetStore = defineStore( function resetBudgetWorksheet() { // Reset all values to 0 but keep the structure - [...budgetWorksheet.value.revenue, ...budgetWorksheet.value.expenses].forEach(item => { - Object.keys(item.values).forEach(year => { - Object.keys(item.values[year]).forEach(scenario => { + [ + ...budgetWorksheet.value.revenue, + ...budgetWorksheet.value.expenses, + ].forEach((item) => { + Object.keys(item.values).forEach((year) => { + Object.keys(item.values[year]).forEach((scenario) => { item.values[year][scenario] = 0; }); }); @@ -652,7 +851,15 @@ export const useBudgetStore = defineStore( { persist: { key: "urgent-tools-budget", - paths: ["budgetWorksheet", "revenueCategories", "expenseCategories", "isInitialized", "overheadCosts", "productionCosts", "currentPeriod"], + paths: [ + "budgetWorksheet", + "revenueCategories", + "expenseCategories", + "isInitialized", + "overheadCosts", + "productionCosts", + "currentPeriod", + ], }, } );