refactor: remove CashFlowChart and UnifiedCashFlowDashboard components, update routing paths in app.vue, and enhance budget page with cumulative balance calculations and payroll explanation modal for improved user experience

This commit is contained in:
Jennie Robinson Faber 2025-09-10 07:42:56 +01:00
parent 864a81065c
commit f1889b3a70
17 changed files with 922 additions and 1004 deletions

View file

@ -28,6 +28,9 @@
</div>
</div>
<div class="flex items-center gap-2">
<UButton @click="showCalculationModal = true" variant="ghost" size="sm">
How are these calculated?
</UButton>
<UButton @click="exportBudget" variant="ghost" size="sm">
Export
</UButton>
@ -222,7 +225,7 @@
<div class="font-medium flex items-start gap-2">
<UTooltip
v-if="isPayrollItem(item.id)"
text="Calculated from compensation settings"
text="Calculated based on available revenue after overhead costs. This represents realistic, sustainable payroll."
:content="{ side: 'top', align: 'start' }">
<span class="cursor-help">{{ item.name }}</span>
</UTooltip>
@ -315,6 +318,23 @@
{{ formatCurrency(monthlyTotals[month.key]?.net || 0) }}
</td>
</tr>
<!-- Cumulative Balance Row -->
<tr class="border-t-1 border-gray-400 font-bold text-lg bg-blue-50">
<td
class="border-r-1 border-black px-4 py-3 sticky left-0 bg-blue-50 z-10">
CUMULATIVE BALANCE
</td>
<td
v-for="month in monthlyHeaders"
:key="month.key"
class="border-r border-gray-400 px-2 py-3 text-right last:border-r-0"
:class="
getCumulativeBalanceClass(cumulativeBalances[month.key] || 0)
">
{{ formatCurrency(cumulativeBalances[month.key] || 0) }}
</td>
</tr>
</tbody>
</table>
</div>
@ -622,6 +642,77 @@
<PayrollOncostModal
v-model:open="showPayrollOncostModal"
@save="handlePayrollOncostUpdate" />
<!-- Calculation Explanation Modal -->
<UModal v-model:open="showCalculationModal" title="How Budget Calculations Work">
<template #content>
<div class="space-y-6 max-w-2xl p-6">
<!-- Revenue Section -->
<div>
<h4 class="font-semibold text-green-600 mb-2">📈 Revenue Calculation</h4>
<p class="text-sm text-gray-600 mb-2">Revenue comes from your setup wizard streams and any manual additions:</p>
<ul class="text-sm text-gray-600 space-y-1 ml-4">
<li> Monthly amounts you entered for each revenue stream</li>
<li> Varies by month based on your specific projections</li>
</ul>
</div>
<!-- Payroll Section -->
<div>
<h4 class="font-semibold text-blue-600 mb-2">👥 Smart Payroll Calculation</h4>
<p class="text-sm text-gray-600 mb-2">Payroll uses a <strong>cumulative balance approach</strong> to ensure sustainability:</p>
<div class="bg-blue-50 border border-blue-200 rounded p-3 text-sm">
<p class="font-medium mb-2">Step-by-step process:</p>
<ol class="space-y-1 ml-4">
<li>1. Calculate available funds: Revenue - Other Expenses</li>
<li>2. Check if this maintains minimum cash threshold (${{ $format.currency(coopBuilderStore.minCashThreshold || 0) }})</li>
<li>3. Allocate using your chosen policy ({{ getPolicyName() }})</li>
<li>4. Account for payroll taxes ({{ coopBuilderStore.payrollOncostPct || 0 }}%)</li>
<li>5. Ensure cumulative balance doesn't fall below threshold</li>
</ol>
</div>
<p class="text-sm text-gray-600 mt-2">
This means payroll varies by month - higher in good cash flow months, lower when cash is tight.
</p>
</div>
<!-- Cumulative Balance Section -->
<div>
<h4 class="font-semibold text-purple-600 mb-2">💰 Cumulative Balance</h4>
<p class="text-sm text-gray-600 mb-2">Shows your running cash position over time:</p>
<ul class="text-sm text-gray-600 space-y-1 ml-4">
<li> Starts at $0 (current cash position)</li>
<li> Adds each month's net income (Revenue - All Expenses)</li>
<li> Helps you see when cash might run low</li>
<li> Payroll is reduced to prevent going below minimum threshold</li>
</ul>
</div>
<!-- Policy Explanation -->
<div>
<h4 class="font-semibold text-orange-600 mb-2"> Pay Policy: {{ getPolicyName() }}</h4>
<div class="text-sm text-gray-600">
<p v-if="coopBuilderStore.policy?.relationship === 'equal-pay'">
Everyone gets equal hourly wage (${{ coopBuilderStore.equalHourlyWage || 0 }}/hour) based on their monthly hours.
</p>
<p v-else-if="coopBuilderStore.policy?.relationship === 'needs-weighted'">
Pay is allocated proportionally based on each member's minimum monthly needs, ensuring fair coverage.
</p>
<p v-else-if="coopBuilderStore.policy?.relationship === 'hours-weighted'">
Pay is allocated proportionally based on hours worked, with higher hours getting more pay.
</p>
</div>
</div>
<div class="bg-gray-50 border border-gray-200 rounded p-3">
<p class="text-sm text-gray-700">
<strong>Key insight:</strong> This system prioritizes sustainability over theoretical maximums.
You might not always get full theoretical wages, but you'll never run out of cash.
</p>
</div>
</div>
</template>
</UModal>
</div>
</template>
@ -749,6 +840,7 @@ const activeView = ref("monthly");
const showAddRevenueModal = ref(false);
const showAddExpenseModal = ref(false);
const showPayrollOncostModal = ref(false);
const showCalculationModal = ref(false);
const activeTab = ref(0);
const highlightedItemId = ref<string | null>(null);
@ -863,6 +955,7 @@ const budgetWorksheet = computed(
const groupedRevenue = computed(() => budgetStore.groupedRevenue);
const groupedExpenses = computed(() => budgetStore.groupedExpenses);
const monthlyTotals = computed(() => budgetStore.monthlyTotals);
const cumulativeBalances = computed(() => budgetStore.cumulativeBalances);
// Initialize on mount
// Removed duplicate onMounted - initialization is now handled above
@ -1141,6 +1234,23 @@ function getNetIncomeClass(amount: number): string {
return "text-gray-600";
}
function getCumulativeBalanceClass(amount: number): string {
if (amount > 50000) return "text-green-700 font-bold"; // Healthy cash position
if (amount > 10000) return "text-green-600 font-bold"; // Good cash position
if (amount > 0) return "text-blue-600 font-bold"; // Positive but low
if (amount > -10000) return "text-orange-600 font-bold"; // Concerning
return "text-red-700 font-bold"; // Critical cash position
}
function getPolicyName(): string {
const policyType = coopBuilderStore.policy?.relationship || 'equal-pay';
if (policyType === 'equal-pay') return 'Equal Pay';
if (policyType === 'hours-weighted') return 'Hours Based';
if (policyType === 'needs-weighted') return 'Needs Weighted';
return 'Equal Pay';
}
// Payroll oncost handling
function handlePayrollOncostUpdate(newPercentage: number) {
// Update the coop store