chore: update application configuration and UI components for improved styling and functionality

This commit is contained in:
Jennie Robinson Faber 2025-08-16 08:13:35 +01:00
parent 0af6b17792
commit 37ab8d7bab
54 changed files with 23293 additions and 1666 deletions

View file

@ -16,43 +16,59 @@
</h3>
</template>
<div
class="flex items-center justify-between py-4 border-b border-gray-200">
class="flex items-center justify-between py-4 border-b border-neutral-200">
<div class="flex items-center gap-8">
<div class="text-center">
<div class="text-2xl font-bold text-blue-600">12,000</div>
<div class="text-xs text-gray-600">Gross Revenue</div>
<div class="text-2xl font-bold text-blue-600">
{{ budgetMetrics.grossRevenue.toLocaleString() }}
</div>
<div class="text-xs text-neutral-600">Gross Revenue</div>
</div>
<UIcon name="i-heroicons-arrow-right" class="text-gray-400" />
<UIcon name="i-heroicons-arrow-right" class="text-neutral-400" />
<div class="text-center">
<div class="text-2xl font-bold text-red-600">-450</div>
<div class="text-xs text-gray-600">Fees</div>
<div class="text-2xl font-bold text-red-600">
-{{ budgetMetrics.totalFees.toLocaleString() }}
</div>
<div class="text-xs text-neutral-600">Fees</div>
</div>
<UIcon name="i-heroicons-arrow-right" class="text-gray-400" />
<UIcon name="i-heroicons-arrow-right" class="text-neutral-400" />
<div class="text-center">
<div class="text-2xl font-bold text-green-600">11,550</div>
<div class="text-xs text-gray-600">Net Revenue</div>
<div class="text-2xl font-bold text-green-600">
{{ budgetMetrics.netRevenue.toLocaleString() }}
</div>
<div class="text-xs text-neutral-600">Net Revenue</div>
</div>
<UIcon name="i-heroicons-arrow-right" class="text-gray-400" />
<UIcon name="i-heroicons-arrow-right" class="text-neutral-400" />
<div class="text-center">
<div class="text-2xl font-bold text-blue-600">300</div>
<div class="text-xs text-gray-600">To Savings</div>
<div class="text-2xl font-bold text-blue-600">
{{ Math.round(budgetMetrics.savingsAmount).toLocaleString() }}
</div>
<div class="text-xs text-neutral-600">To Savings</div>
</div>
<UIcon name="i-heroicons-arrow-right" class="text-gray-400" />
<UIcon name="i-heroicons-arrow-right" class="text-neutral-400" />
<div class="text-center">
<div class="text-2xl font-bold text-purple-600">6,400</div>
<div class="text-xs text-gray-600">Payroll</div>
<div class="text-2xl font-bold text-purple-600">
{{ Math.round(budgetMetrics.totalPayroll).toLocaleString() }}
</div>
<div class="text-xs text-neutral-600">Payroll</div>
</div>
<UIcon name="i-heroicons-arrow-right" class="text-gray-400" />
<UIcon name="i-heroicons-arrow-right" class="text-neutral-400" />
<div class="text-center">
<div class="text-2xl font-bold text-orange-600">2,300</div>
<div class="text-xs text-gray-600">Overhead</div>
<div class="text-2xl font-bold text-orange-600">
{{ budgetMetrics.totalOverhead.toLocaleString() }}
</div>
<div class="text-xs text-neutral-600">Overhead</div>
</div>
</div>
</div>
<div class="pt-4">
<div class="flex items-center justify-between">
<span class="text-lg font-medium">Available for Operations</span>
<span class="text-2xl font-bold text-green-600">2,550</span>
<span class="text-2xl font-bold text-green-600"
>{{
Math.round(budgetMetrics.availableForOps).toLocaleString()
}}</span
>
</div>
</div>
</UCard>
@ -111,17 +127,35 @@
<h4 class="font-medium text-sm mb-2">Payroll</h4>
<div class="space-y-2">
<div class="flex justify-between text-sm">
<span class="text-gray-600">Wages (320h @ 20)</span>
<span class="font-medium">6,400</span>
<span class="text-neutral-600"
>Wages ({{ budgetMetrics.totalHours }}h @ {{
budgetMetrics.hourlyWage
}})</span
>
<span class="font-medium"
>{{
Math.round(budgetMetrics.grossWages).toLocaleString()
}}</span
>
</div>
<div class="flex justify-between text-sm">
<span class="text-gray-600">On-costs (25%)</span>
<span class="font-medium">1,600</span>
<span class="text-neutral-600"
>On-costs ({{ budgetMetrics.oncostPct }}%)</span
>
<span class="font-medium"
>{{
Math.round(budgetMetrics.oncosts).toLocaleString()
}}</span
>
</div>
<div
class="flex justify-between text-sm font-medium border-t pt-2">
<span>Total Payroll</span>
<span>8,000</span>
<span
>{{
Math.round(budgetMetrics.totalPayroll).toLocaleString()
}}</span
>
</div>
</div>
</div>
@ -129,22 +163,24 @@
<div>
<h4 class="font-medium text-sm mb-2">Overhead</h4>
<div class="space-y-2">
<div class="flex justify-between text-sm">
<span class="text-gray-600">Coworking space</span>
<span class="font-medium">800</span>
<div
v-if="budgetStore.overheadCosts.length === 0"
class="text-sm text-neutral-500 italic">
No overhead costs added yet
</div>
<div class="flex justify-between text-sm">
<span class="text-gray-600">Tools & software</span>
<span class="font-medium">400</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-gray-600">Insurance</span>
<span class="font-medium">200</span>
<div
v-for="cost in budgetStore.overheadCosts"
:key="cost.id"
class="flex justify-between text-sm">
<span class="text-neutral-600">{{ cost.name }}</span>
<span class="font-medium"
>{{ (cost.amount || 0).toLocaleString() }}</span
>
</div>
<div
class="flex justify-between text-sm font-medium border-t pt-2">
<span>Total Overhead</span>
<span>1,400</span>
<span>{{ budgetMetrics.totalOverhead.toLocaleString() }}</span>
</div>
</div>
</div>
@ -153,7 +189,7 @@
<h4 class="font-medium text-sm mb-2">Production</h4>
<div class="space-y-2">
<div class="flex justify-between text-sm">
<span class="text-gray-600">Dev kits</span>
<span class="text-neutral-600">Dev kits</span>
<span class="font-medium">500</span>
</div>
<div
@ -173,34 +209,59 @@
<div class="space-y-4">
<div class="space-y-3">
<div class="flex justify-between text-sm">
<span class="text-gray-600">Net Revenue</span>
<span class="font-medium text-green-600">11,550</span>
<span class="text-neutral-600">Net Revenue</span>
<span class="font-medium text-green-600"
>{{ budgetMetrics.netRevenue.toLocaleString() }}</span
>
</div>
<div class="flex justify-between text-sm">
<span class="text-gray-600">Total Costs</span>
<span class="font-medium text-red-600">-9,900</span>
<span class="text-neutral-600">Total Costs</span>
<span class="font-medium text-red-600"
>-{{
Math.round(budgetMetrics.totalCosts).toLocaleString()
}}</span
>
</div>
<div class="flex justify-between text-lg font-bold border-t pt-3">
<span>Net</span>
<span class="text-green-600">+1,650</span>
<span
:class="
budgetMetrics.monthlyNet >= 0
? 'text-green-600'
: 'text-red-600'
"
>{{ budgetMetrics.monthlyNet >= 0 ? "+" : "" }}{{
Math.round(budgetMetrics.monthlyNet).toLocaleString()
}}</span
>
</div>
</div>
<div class="bg-gray-50 rounded-lg p-4">
<div class="bg-neutral-50 rounded-lg p-4">
<h4 class="font-medium text-sm mb-3">Allocation</h4>
<div class="space-y-2">
<div class="flex justify-between text-sm">
<span class="text-gray-600">To Savings</span>
<span class="font-medium">1,200</span>
<span class="text-neutral-600">To Savings</span>
<span class="font-medium"
>{{
Math.round(budgetMetrics.savingsAmount).toLocaleString()
}}</span
>
</div>
<div class="flex justify-between text-sm">
<span class="text-gray-600">Available</span>
<span class="font-medium">450</span>
<span class="text-neutral-600">Available</span>
<span class="font-medium"
>{{
Math.round(
budgetMetrics.availableAfterSavings
).toLocaleString()
}}</span
>
</div>
</div>
</div>
<div class="text-xs text-gray-600 space-y-1">
<div class="text-xs text-neutral-600 space-y-1">
<p>
<RestrictionChip restriction="Restricted" size="xs" /> funds can
only be used for approved purposes.
@ -217,6 +278,13 @@
</template>
<script setup lang="ts">
// Use real store data
const membersStore = useMembersStore();
const policiesStore = usePoliciesStore();
const streamsStore = useStreamsStore();
const budgetStore = useBudgetStore();
const cashStore = useCashStore();
const selectedMonth = ref("2024-01");
const months = ref([
{ label: "January 2024", value: "2024-01" },
@ -224,35 +292,71 @@ const months = ref([
{ label: "March 2024", value: "2024-03" },
]);
const revenueStreams = ref([
{
id: 1,
name: "Client Services",
target: 7800,
committed: 6500,
actual: 7200,
variance: 700,
restrictions: "General",
},
{
id: 2,
name: "Platform Sales",
target: 3000,
committed: 2000,
actual: 2400,
variance: 400,
restrictions: "General",
},
{
id: 3,
name: "Grant Funding",
target: 1200,
committed: 0,
actual: 1400,
variance: 1400,
restrictions: "Restricted",
},
]);
// Calculate budget values from real data
const budgetMetrics = computed(() => {
const totalHours = membersStore.capacityTotals.targetHours || 0;
const hourlyWage = policiesStore.equalHourlyWage || 0;
const oncostPct = policiesStore.payrollOncostPct || 0;
const grossWages = totalHours * hourlyWage;
const oncosts = grossWages * (oncostPct / 100);
const totalPayroll = grossWages + oncosts;
const totalOverhead = budgetStore.overheadCosts.reduce(
(sum, cost) => sum + (cost.amount || 0),
0
);
const grossRevenue = streamsStore.totalMonthlyAmount || 0;
// Calculate fees from streams with platform fees
const totalFees = streamsStore.streams.reduce((sum, stream) => {
const revenue = stream.targetMonthlyAmount || 0;
const platformFee = (stream.platformFeePct || 0) / 100;
const revShareFee = (stream.revenueSharePct || 0) / 100;
return sum + revenue * platformFee + revenue * revShareFee;
}, 0);
const netRevenue = grossRevenue - totalFees;
const totalCosts = totalPayroll + totalOverhead;
const monthlyNet = netRevenue - totalCosts;
const savingsAmount = Math.max(0, monthlyNet * 0.3); // Save 30% of positive net if possible
const availableAfterSavings = Math.max(0, monthlyNet - savingsAmount);
const availableForOps = Math.max(
0,
netRevenue - totalPayroll - totalOverhead - savingsAmount
);
return {
grossRevenue,
totalFees,
netRevenue,
totalCosts,
monthlyNet,
savingsAmount,
availableAfterSavings,
totalPayroll,
grossWages,
oncosts,
totalOverhead,
availableForOps,
totalHours,
hourlyWage,
oncostPct,
};
});
// Convert streams to budget table format
const revenueStreams = computed(() =>
streamsStore.streams.map((stream) => ({
id: stream.id,
name: stream.name,
target: stream.targetMonthlyAmount || 0,
committed: Math.round((stream.targetMonthlyAmount || 0) * 0.8), // 80% committed assumption
actual: Math.round((stream.targetMonthlyAmount || 0) * 0.9), // 90% actual assumption
variance: Math.round((stream.targetMonthlyAmount || 0) * 0.1), // 10% positive variance
restrictions: stream.restrictions || "General",
}))
);
const revenueColumns = [
{ id: "name", key: "name", label: "Stream" },