Files
suivicpt/app/Livewire/Dashboard/Index.php
T
jeremy bayseandClaude Sonnet 5 d8dc57a5df Initial commit: SuiviCPT personal finance tracker
Laravel 11 + Livewire 3/Volt + Alpine.js + Tailwind app for household
budget planning, transaction tracking, and financial dashboards.

- Multi-tenant households with member invites
- Plan: monthly/yearly budget per category with overrides
- Suivi: transaction log with running balance (desktop + mobile quick-entry)
- Dashboard: budget vs tracked breakdown + Chart.js donuts
- Flux: Sankey diagram of income allocation
- WCAG 2.1 AA color tokens, dark mode, accessible tables/forms
- 50 Pest tests

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-25 22:39:42 +02:00

111 lines
3.4 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Livewire\Dashboard;
use App\Enums\CategoryType;
use App\Models\Category;
use Livewire\Attributes\Layout;
use Livewire\Component;
#[Layout('layouts.app')]
class Index extends Component
{
public int $year;
public string $period = 'year'; // 'year' or '1'..'12'
public function mount(): void
{
$this->year = (int) now()->year;
$this->period = (string) now()->month;
}
private function budgetFor(Category $category): float
{
$lines = $category->budgetLines()->where('year', $this->year)->get();
if ($this->period === 'year') {
$default = (float) ($lines->firstWhere('month', null)->amount ?? 0);
$total = 0.0;
for ($m = 1; $m <= 12; $m++) {
$override = $lines->firstWhere('month', $m);
$total += $override ? (float) $override->amount : $default;
}
return $total;
}
$month = (int) $this->period;
$override = $lines->firstWhere('month', $month);
return $override ? (float) $override->amount : (float) ($lines->firstWhere('month', null)->amount ?? 0);
}
private function trackedFor(Category $category): float
{
$query = $category->transactions()->whereYear('date_effective', $this->year);
if ($this->period !== 'year') {
$query->whereMonth('date_effective', (int) $this->period);
}
return (float) $query->sum('amount');
}
public function render()
{
$household = auth()->user()->currentHousehold;
$sections = collect(CategoryType::cases())->mapWithKeys(function (CategoryType $type) use ($household) {
$rows = $household->categories()
->where('type', $type)
->orderBy('position')
->get()
->map(function (Category $category) {
$budget = $this->budgetFor($category);
$tracked = $this->trackedFor($category);
return [
'category' => $category,
'tracked' => $tracked,
'budget' => $budget,
'pct' => $budget > 0 ? min(100, round($tracked / $budget * 100)) : 0,
'reste' => max(0, $budget - $tracked),
'manque' => max(0, $tracked - $budget),
];
});
return [$type->value => [
'rows' => $rows,
'totals' => [
'tracked' => $rows->sum('tracked'),
'budget' => $rows->sum('budget'),
'reste' => $rows->sum('reste'),
'manque' => $rows->sum('manque'),
],
]];
});
$chartData = $sections->mapWithKeys(function ($section, $key) {
$top = $section['rows']
->filter(fn ($row) => $row['tracked'] > 0)
->sortByDesc('tracked')
->take(6)
->values();
return [$key => [
'labels' => $top->pluck('category.name')->values(),
'data' => $top->pluck('tracked')->values(),
]];
});
return view('livewire.dashboard.index', [
'types' => CategoryType::cases(),
'sections' => $sections,
'chartData' => $chartData,
]);
}
}