Files
suivicpt/resources/views/livewire/dashboard/index.blade.php
T
jeremy bayseandClaude Sonnet 5 52ebb9bc83 Apply Finlio cream/terracotta design system
Re-theme dashboard, suivi, plan, and flux pages plus nav from the
claude.ai/design import: warm cream/terracotta/olive palette, pill
nav/buttons/inputs, Caprasimo display font for headings. Dark mode
variant derived to hold the existing WCAG AA contrast ratios since
the import only specified light.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 21:21:17 +02:00

186 lines
12 KiB
PHP

<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 space-y-6">
<div class="flex flex-wrap items-center justify-between gap-4">
<h1 class="text-3xl font-display text-text">Tableau de bord</h1>
<div class="flex items-center gap-2">
<div>
<label for="dashboard-year" class="sr-only">Année</label>
<select id="dashboard-year" wire:model.live="year" class="block rounded-full bg-surface border-border-strong text-text text-sm px-4 py-2 focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring">
@foreach (range($year - 3, $year + 3) as $y)
<option value="{{ $y }}">{{ $y }}</option>
@endforeach
</select>
</div>
<div>
<label for="dashboard-period" class="sr-only">Période</label>
<select id="dashboard-period" wire:model.live="period" class="block rounded-full bg-surface border-border-strong text-text text-sm px-4 py-2 focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring">
<option value="year">Année complète</option>
@foreach (['Janvier','Février','Mars','Avril','Mai','Juin','Juillet','Août','Septembre','Octobre','Novembre','Décembre'] as $i => $label)
<option value="{{ $i + 1 }}">{{ $label }}</option>
@endforeach
</select>
</div>
</div>
</div>
<div class="bg-surface shadow rounded-3xl px-6 py-5 inline-flex flex-col gap-1">
<p class="text-xs text-text-muted uppercase tracking-wide">Reste à dépenser</p>
<p class="font-display text-2xl text-negative">{{ number_format($sections['depense']['totals']['reste'], 2, ',', ' ') }} </p>
</div>
@php
$colors = [
'revenu' => ['bg' => 'bg-positive-fill', 'row' => 'bg-positive-surface/40'],
'depense' => ['bg' => 'bg-negative-fill', 'row' => 'bg-negative-surface/40'],
'epargne' => ['bg' => 'bg-info-fill', 'row' => 'bg-info-surface/40'],
];
// Ordered to alternate light/dark shades so adjacent donut slices stay distinguishable
// even without relying on the border stroke (WCAG 1.4.11 graphical-object contrast).
$chartPalettes = [
'revenu' => ['#15803d', '#4ade80', '#166534', '#86efac', '#16a34a', '#22c55e'],
'depense' => ['#991b1b', '#fb923c', '#c2410c', '#fdba74', '#dc2626', '#f97316'],
'epargne' => ['#1e40af', '#93c5fd', '#1d4ed8', '#bfdbfe', '#2563eb', '#60a5fa'],
];
@endphp
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div class="bg-surface shadow rounded-3xl overflow-hidden">
<div class="px-5 pt-4 pb-1">
<h2 class="font-semibold text-base text-text">Analyse</h2>
</div>
<div class="divide-y divide-border">
@foreach ($types as $type)
@php $section = $sections[$type->value]; @endphp
<div class="px-5 pb-5">
<div class="{{ $colors[$type->value]['bg'] }} px-4 py-2 rounded-2xl mb-2">
<h3 class="text-white text-sm font-bold">{{ $type->label() }}</h3>
</div>
<div class="overflow-x-auto">
<table class="min-w-full text-sm">
<caption class="sr-only">Suivi budgétaire {{ Str::lower($type->label()) }}</caption>
<thead>
<tr class="text-xs text-text-muted">
<th scope="col" class="text-left px-4 py-1.5">Catégorie</th>
<th scope="col" class="text-right px-2 py-1.5">Tracked</th>
<th scope="col" class="text-right px-2 py-1.5">Budget</th>
<th scope="col" class="text-center px-2 py-1.5">% Compl.</th>
<th scope="col" class="text-right px-2 py-1.5">Reste</th>
<th scope="col" class="text-right px-2 py-1.5">Manque</th>
</tr>
</thead>
<tbody>
@foreach ($section['rows'] as $row)
<tr class="border-t border-border/60 text-text">
<th scope="row" class="px-4 py-1.5 whitespace-nowrap font-normal text-left">{{ $row['category']->name }}</th>
<td class="px-2 py-1.5 text-right">{{ number_format($row['tracked'], 2, ',', ' ') }} </td>
<td class="px-2 py-1.5 text-right">{{ number_format($row['budget'], 2, ',', ' ') }} </td>
<td class="px-2 py-1.5">
<div class="flex items-center gap-1">
<div class="w-10 h-1.5 bg-surface-alt rounded-full overflow-hidden" role="img" aria-label="{{ $row['pct'] }}% du budget utilisé">
<div class="h-full {{ $colors[$type->value]['bg'] }}" style="width: {{ $row['pct'] }}%"></div>
</div>
<span class="text-xs">{{ $row['pct'] }}%</span>
</div>
</td>
<td class="px-2 py-1.5 text-right">{{ number_format($row['reste'], 2, ',', ' ') }} </td>
<td class="px-2 py-1.5 text-right {{ $row['manque'] > 0 ? 'text-danger font-semibold' : '' }}">
@if ($row['manque'] > 0)
<span aria-hidden="true"> </span><span class="sr-only">Dépassement : </span>
@endif
{{ number_format($row['manque'], 2, ',', ' ') }}
</td>
</tr>
@endforeach
<tr class="border-t border-border font-semibold {{ $colors[$type->value]['row'] }} text-text">
<th scope="row" class="px-4 py-1.5 font-semibold text-left">Total</th>
<td class="px-2 py-1.5 text-right">{{ number_format($section['totals']['tracked'], 2, ',', ' ') }} </td>
<td class="px-2 py-1.5 text-right">{{ number_format($section['totals']['budget'], 2, ',', ' ') }} </td>
<td class="px-2 py-1.5"></td>
<td class="px-2 py-1.5 text-right">{{ number_format($section['totals']['reste'], 2, ',', ' ') }} </td>
<td class="px-2 py-1.5 text-right">{{ number_format($section['totals']['manque'], 2, ',', ' ') }} </td>
</tr>
</tbody>
</table>
</div>
</div>
@endforeach
</div>
</div>
<div class="space-y-6">
@foreach ($types as $type)
@php $top = $chartData[$type->value]; @endphp
<div class="bg-surface shadow rounded-3xl p-5">
<h3 class="text-sm font-semibold mb-4 text-text">Catégorie de <span class="{{ $type->value === 'revenu' ? 'text-positive' : ($type->value === 'depense' ? 'text-negative' : 'text-info') }}">{{ Str::lower($type->label()) }}</span> (suivis)</h3>
@if ($top['labels']->isEmpty())
<p class="text-xs text-text-muted py-8 text-center">Aucun montant suivi.</p>
@else
<div
wire:ignore
x-data="donutChart(@js($top['labels']), @js($top['data']), @js($chartPalettes[$type->value]))"
x-init="init()"
wire:key="chart-{{ $type->value }}-{{ $year }}-{{ $period }}"
class="flex items-center gap-4"
>
<div class="w-32 h-32 shrink-0">
<canvas x-ref="canvas" role="img" aria-label="Répartition {{ Str::lower($type->label()) }} par catégorie"></canvas>
</div>
<ul class="text-xs space-y-1 flex-1">
@foreach ($top['labels'] as $i => $label)
<li class="flex items-center justify-between gap-2 text-text">
<span class="flex items-center gap-1.5">
<span class="inline-block w-2.5 h-2.5 rounded-full ring-1 ring-border-strong" style="background-color: {{ $chartPalettes[$type->value][$i % 6] }}"></span>
{{ $label }}
</span>
<span>{{ number_format($top['data'][$i], 2, ',', ' ') }} </span>
</li>
@endforeach
</ul>
</div>
@endif
<div class="mt-2 pt-2 border-t border-border flex justify-between text-xs font-semibold text-text">
<span>Total</span>
<span>{{ number_format($sections[$type->value]['totals']['tracked'], 2, ',', ' ') }} </span>
</div>
</div>
@endforeach
</div>
</div>
</div>
@once
@push('scripts')
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('donutChart', (labels, data, palette) => ({
chart: null,
init() {
// Visible stroke between slices so adjacent segments stay
// distinguishable even when their fill colors are close (WCAG 1.4.11).
const surface = getComputedStyle(document.documentElement).getPropertyValue('--color-surface').trim();
this.chart = new Chart(this.$refs.canvas, {
type: 'doughnut',
data: {
labels: labels,
datasets: [{
data: data,
backgroundColor: labels.map((_, i) => palette[i % palette.length]),
borderColor: `rgb(${surface})`,
borderWidth: 2,
}],
},
options: {
plugins: { legend: { display: false } },
cutout: '70%',
},
});
},
}));
});
</script>
@endpush
@endonce
</div>