Files
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

110 lines
3.1 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Livewire\Plan;
use App\Enums\CategoryType;
use App\Models\BudgetLine;
use App\Models\Category;
use Livewire\Attributes\Layout;
use Livewire\Component;
#[Layout('layouts.app')]
class Index extends Component
{
public int $year;
public function mount(): void
{
$this->year = (int) now()->year;
}
public function setYear(int $year): void
{
$this->year = $year;
}
public function updateAmount(int $categoryId, ?int $month, string $amount): void
{
$category = Category::findOrFail($categoryId);
$household = auth()->user()->currentHousehold;
if ($category->household_id !== $household->id) {
abort(403);
}
$amount = is_numeric($amount) ? (float) $amount : 0.0;
BudgetLine::updateOrCreate(
[
'household_id' => $household->id,
'category_id' => $categoryId,
'year' => $this->year,
'month' => $month,
],
['amount' => $amount],
);
}
/**
* @return array<int|string, float> keyed 'default',1..12
*/
private function resolveRow(Category $category): array
{
$lines = $category->budgetLines()->where('year', $this->year)->get();
$default = (float) ($lines->firstWhere('month', null)->amount ?? 0);
$row = ['default' => $default];
for ($m = 1; $m <= 12; $m++) {
$override = $lines->firstWhere('month', $m);
$row[$m] = $override ? (float) $override->amount : $default;
}
return $row;
}
public function render()
{
$household = auth()->user()->currentHousehold;
$sections = collect(CategoryType::cases())->mapWithKeys(function (CategoryType $type) use ($household) {
$categories = $household->categories()
->where('type', $type)
->orderBy('position')
->get()
->map(fn (Category $category) => [
'category' => $category,
'row' => $this->resolveRow($category),
]);
$totals = ['default' => 0.0];
for ($m = 1; $m <= 12; $m++) {
$totals[$m] = 0.0;
}
foreach ($categories as $entry) {
foreach ($entry['row'] as $key => $value) {
$totals[$key] += $value;
}
}
return [$type->value => ['categories' => $categories, 'totals' => $totals]];
});
$remaining = [];
for ($m = 1; $m <= 12; $m++) {
$remaining[$m] = $sections['revenu']['totals'][$m]
- $sections['depense']['totals'][$m]
- $sections['epargne']['totals'][$m];
}
$sections['revenu'] = [...$sections['revenu'], 'remaining' => $remaining];
return view('livewire.plan.index', [
'types' => CategoryType::cases(),
'sections' => $sections,
'months' => ['Janv', 'Févr', 'Mars', 'Avr', 'Mai', 'Juin', 'Juil', 'Août', 'Sept', 'Oct', 'Nov', 'Déc'],
]);
}
}