refactor: update app.vue and various components to improve routing paths, enhance UI consistency, and streamline layout for better user experience

This commit is contained in:
Jennie Robinson Faber 2025-09-11 11:51:48 +01:00
parent b6e8d3b7ec
commit 78af43770c
29 changed files with 1699 additions and 1990 deletions

View file

@ -0,0 +1,130 @@
<template>
<div class="space-y-8">
<div class="">
<h1 class="font-bold text-2xl mb-4">Project Budget Estimate</h1>
<p class="text-neutral-600 dark:text-neutral-400 mx-auto mb-4">
This tool provides a rough estimate of what it would cost to build your
project using the pay policy you've set in the setup.
</p>
<div class="space-y-4">
<!-- Sustainable payroll toggle hidden - defaulting to theoretical maximum -->
<div class="hidden">
<span class="text-sm font-medium">Sustainable Payroll</span>
<USwitch v-model="useTheoreticalPayroll" size="md" />
<span class="text-sm font-medium">Theoretical Maximum</span>
</div>
</div>
</div>
<div v-if="membersWithPay.length === 0" class="text-center py-8">
<p class="text-neutral-600 dark:text-neutral-400 mb-4">
No team members set up yet.
</p>
<NuxtLink
to="/tools/coop-builder"
class="px-4 py-2 border-2 border-black dark:border-white bg-white dark:bg-black text-black dark:text-white font-bold hover:bg-neutral-100 dark:hover:bg-neutral-900">
Set up your team in Setup Wizard
</NuxtLink>
</div>
<ProjectBudgetEstimate
v-else
:members="membersWithPay"
:oncost-rate="coopStore.payrollOncostPct / 100"
:payroll-mode="useTheoreticalPayroll ? 'theoretical' : 'sustainable'" />
</div>
</template>
<script setup lang="ts">
import { allocatePayroll as allocatePayrollImpl } from "~/types/members";
const coopStore = useCoopBuilderStore();
const budgetStore = useBudgetStore();
// Toggle between sustainable and theoretical payroll modes - defaulting to theoretical maximum
const useTheoreticalPayroll = ref(true);
// Calculate member pay using different logic based on payroll mode
const membersWithPay = computed(() => {
// Use the member's desired hours (targetHours if available, otherwise hoursPerMonth)
const getHoursForMember = (member: any) => {
return member.capacity?.targetHours || member.hoursPerMonth || 0;
};
let allocatedMembers;
if (useTheoreticalPayroll.value) {
// Theoretical mode: Calculate true theoretical maximum without revenue constraints
const allMembers = coopStore.members.map((m: any) => ({
...m,
displayName: m.name,
monthlyPayPlanned: m.monthlyPayPlanned || 0,
minMonthlyNeeds: m.minMonthlyNeeds || 0,
hoursPerMonth: m.hoursPerMonth || 0,
}));
const payPolicy = {
relationship: coopStore.policy.relationship || ("equal-pay" as const),
};
// Calculate theoretical maximum budget: total hours × hourly wage
const totalHours = allMembers.reduce(
(sum, m) => sum + (m.hoursPerMonth || 0),
0
);
const hourlyWage = coopStore.equalHourlyWage || 0;
const theoreticalMaxBudget = totalHours * hourlyWage;
allocatedMembers = allocatePayrollImpl(
allMembers,
payPolicy,
theoreticalMaxBudget
);
} else {
// Sustainable mode: Use revenue-constrained allocation (current behavior)
const { allocatePayroll } = useCoopBuilder();
const sustainableMembers = allocatePayroll();
const today = new Date();
const currentMonthKey = `${today.getFullYear()}-${String(
today.getMonth() + 1
).padStart(2, "0")}`;
const payrollExpense = budgetStore.budgetWorksheet.expenses.find(
(item) =>
item.id === "expense-payroll-base" || item.id === "expense-payroll"
);
const actualPayrollBudget =
payrollExpense?.monthlyValues?.[currentMonthKey] || 0;
const theoreticalTotal = sustainableMembers.reduce(
(sum, m) => sum + (m.monthlyPayPlanned || 0),
0
);
const scaleFactor =
theoreticalTotal > 0 ? actualPayrollBudget / theoreticalTotal : 0;
allocatedMembers = sustainableMembers.map((member) => ({
...member,
monthlyPayPlanned: (member.monthlyPayPlanned || 0) * scaleFactor,
}));
}
return allocatedMembers
.map((member: any) => {
const hours = getHoursForMember(member);
return {
name: member.displayName || "Unnamed",
hoursPerMonth: hours,
monthlyPay: member.monthlyPayPlanned || 0,
};
})
.filter((m: any) => m.hoursPerMonth > 0); // Only include members with hours
});
// Set page meta
definePageMeta({
title: "Project Budget Estimate",
});
</script>