refactor budget worksheet layout and functionality, streamline revenue and expense management, and improve modal interactions for better user experience

This commit is contained in:
Jennie Robinson Faber 2025-08-17 21:30:10 +01:00
parent f67b138d95
commit 848386e3dd
2 changed files with 1222 additions and 442 deletions

File diff suppressed because it is too large Load diff

View file

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