101 lines
3.2 KiB
Vue
101 lines
3.2 KiB
Vue
<template>
|
|
<section class="py-8 space-y-6">
|
|
<div class="flex items-center justify-between">
|
|
<h2 class="text-2xl font-semibold">Cash Calendar</h2>
|
|
<UBadge v-if="firstBreachWeek" color="red" variant="subtle"
|
|
>Week {{ firstBreachWeek }} cushion breach</UBadge
|
|
>
|
|
<UBadge v-else color="green" variant="subtle"
|
|
>No cushion breach projected</UBadge
|
|
>
|
|
</div>
|
|
|
|
<UCard>
|
|
<template #header>
|
|
<h3 class="text-lg font-medium">13-Week Cash Flow</h3>
|
|
</template>
|
|
<div class="space-y-4">
|
|
<div class="text-sm text-neutral-600">
|
|
Week-by-week cash inflows and outflows with minimum cushion tracking.
|
|
</div>
|
|
|
|
<div
|
|
class="grid grid-cols-7 gap-2 text-xs font-medium text-neutral-500">
|
|
<div>Week</div>
|
|
<div>Inflow</div>
|
|
<div>Outflow</div>
|
|
<div>Net</div>
|
|
<div>Balance</div>
|
|
<div>Cushion</div>
|
|
<div>Status</div>
|
|
</div>
|
|
|
|
<div
|
|
v-for="week in weeks"
|
|
:key="week.number"
|
|
class="grid grid-cols-7 gap-2 text-sm py-2 border-b border-neutral-100"
|
|
:class="{ 'bg-red-50': week.breachesCushion }">
|
|
<div class="font-medium">{{ week.number }}</div>
|
|
<div class="text-green-600">+€{{ week.inflow.toLocaleString() }}</div>
|
|
<div class="text-red-600">-€{{ week.outflow.toLocaleString() }}</div>
|
|
<div :class="week.net >= 0 ? 'text-green-600' : 'text-red-600'">
|
|
{{ week.net >= 0 ? "+" : "" }}€{{ week.net.toLocaleString() }}
|
|
</div>
|
|
<div class="font-medium">€{{ week.balance.toLocaleString() }}</div>
|
|
<div
|
|
:class="
|
|
week.breachesCushion
|
|
? 'text-red-600 font-medium'
|
|
: 'text-neutral-600'
|
|
">
|
|
€{{ week.cushion.toLocaleString() }}
|
|
</div>
|
|
<div>
|
|
<UBadge v-if="week.breachesCushion" color="red" size="xs">
|
|
Breach
|
|
</UBadge>
|
|
<UBadge v-else color="green" size="xs"> OK </UBadge>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-4 p-3 bg-orange-50 rounded-lg">
|
|
<div class="flex items-center gap-2">
|
|
<UIcon
|
|
name="i-heroicons-exclamation-triangle"
|
|
class="text-orange-500" />
|
|
<span class="text-sm font-medium text-orange-800">
|
|
This week would drop below your minimum cushion.
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</UCard>
|
|
</section>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
const cashStore = useCashStore();
|
|
const { weeklyProjections } = storeToRefs(cashStore);
|
|
|
|
const weeks = computed(() => {
|
|
// If no projections, show empty state
|
|
if (weeklyProjections.value.length === 0) {
|
|
return Array.from({ length: 13 }, (_, index) => ({
|
|
number: index + 1,
|
|
inflow: 0,
|
|
outflow: 0,
|
|
net: 0,
|
|
balance: 0,
|
|
cushion: 0,
|
|
breachesCushion: false,
|
|
}));
|
|
}
|
|
return weeklyProjections.value;
|
|
});
|
|
|
|
// Find first week that breaches cushion
|
|
const firstBreachWeek = computed(() => {
|
|
const breachWeek = weeks.value.find((week) => week.breachesCushion);
|
|
return breachWeek ? breachWeek.number : null;
|
|
});
|
|
</script>
|