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>
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Livewire\Sankey;
|
||||
|
||||
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 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;
|
||||
|
||||
$revenueCategories = $household->categories()->where('type', CategoryType::Revenu)->orderBy('position')->get();
|
||||
$totalRevenue = $revenueCategories->sum(fn (Category $c) => $this->trackedFor($c));
|
||||
|
||||
$outflowCategories = $household->categories()
|
||||
->whereIn('type', [CategoryType::Depense, CategoryType::Epargne])
|
||||
->orderBy('type')
|
||||
->orderBy('position')
|
||||
->get()
|
||||
->map(fn (Category $category) => [
|
||||
'category' => $category,
|
||||
'tracked' => $this->trackedFor($category),
|
||||
])
|
||||
->filter(fn ($row) => $row['tracked'] > 0)
|
||||
->values();
|
||||
|
||||
$totalOutflow = $outflowCategories->sum('tracked');
|
||||
$unallocated = max(0, $totalRevenue - $totalOutflow);
|
||||
|
||||
$flows = $outflowCategories->map(fn ($row) => [
|
||||
'from' => 'Revenus',
|
||||
'to' => $row['category']->name,
|
||||
'flow' => (float) $row['tracked'],
|
||||
'type' => $row['category']->type->value,
|
||||
]);
|
||||
|
||||
if ($unallocated > 0) {
|
||||
$flows->push([
|
||||
'from' => 'Revenus',
|
||||
'to' => 'Non affecté',
|
||||
'flow' => $unallocated,
|
||||
'type' => 'unallocated',
|
||||
]);
|
||||
}
|
||||
|
||||
return view('livewire.sankey.index', [
|
||||
'flows' => $flows->values(),
|
||||
'totalRevenue' => $totalRevenue,
|
||||
'totalOutflow' => $totalOutflow,
|
||||
'unallocated' => $unallocated,
|
||||
'hasData' => $totalRevenue > 0,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user