Skip to content

Commit

Permalink
feat: projected cash flow dashboard widget (#16)
Browse files Browse the repository at this point in the history
* explicit declare widgets

* carbon namespace fix

* recurring cash flow widget added

* test added

* url change

* test name fix

* dead property cleaned

* test fix

* currency symbol added
  • Loading branch information
chinmaypurav authored Jan 10, 2025
1 parent 77808b8 commit 02222ed
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 1 deletion.
2 changes: 1 addition & 1 deletion app/Enums/Frequency.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

namespace App\Enums;

use Carbon\Carbon;
use Filament\Support\Contracts\HasLabel;
use Illuminate\Support\Carbon;

enum Frequency: string implements HasLabel
{
Expand Down
9 changes: 9 additions & 0 deletions app/Filament/Pages/Dashboard.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Filament\Pages;

use App\Filament\Widgets;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Section;
use Filament\Forms\Form;
Expand All @@ -24,4 +25,12 @@ public function filtersForm(Form $form): Form
->columns(2),
]);
}

public function getWidgets(): array
{
return [
Widgets\IncomeChart::class,
Widgets\ExpenseChart::class,
];
}
}
22 changes: 22 additions & 0 deletions app/Filament/Pages/RecurringCashFlowOverviewDashboard.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace App\Filament\Pages;

use App\Filament\Widgets;
use Filament\Pages\Dashboard as BaseDashboard;

class RecurringCashFlowOverviewDashboard extends BaseDashboard
{
protected static ?string $navigationGroup = 'Recurring Transactions';

protected static string $routePath = 'recurring-overview';

protected static ?string $title = 'Recurring CashFlow Overview';

public function getWidgets(): array
{
return [
Widgets\RecurringCashFlowOverviewWidget::class,
];
}
}
38 changes: 38 additions & 0 deletions app/Filament/Widgets/RecurringCashFlowOverviewWidget.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace App\Filament\Widgets;

use App\Services\RecurringCashFlowService;
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;

class RecurringCashFlowOverviewWidget extends BaseWidget
{
protected function getStats(): array
{
$startDate = today();
$endDate = today()->addYear();

$totalIncomes = RecurringCashFlowService::processRecurringIncomes(
auth()->user(),
$startDate,
$endDate
);

$totalExpenses = RecurringCashFlowService::processRecurringExpenses(
auth()->user(),
$startDate,
$endDate
);

$disposableIncome = $totalIncomes - $totalExpenses;

$symbol = config('penny.currency_symbol');

return [
Stat::make('Total Estimated Incomes', $symbol.' '.number_format($totalIncomes)),
Stat::make('Total Estimated Expenses', $symbol.' '.number_format($totalExpenses)),
Stat::make('Estimated Disposable Income', $symbol.' '.number_format($disposableIncome)),
];
}
}
47 changes: 47 additions & 0 deletions app/Services/RecurringCashFlowService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace App\Services;

use App\Models\RecurringExpense;
use App\Models\RecurringIncome;
use App\Models\User;
use Carbon\Carbon;

class RecurringCashFlowService
{
public static function processRecurringIncomes(User $user, Carbon $startDate, Carbon $endDate): float
{
return RecurringIncome::query()
->where('user_id', $user->id)
->whereDate('next_transaction_at', '>=', $startDate)
->whereDate('next_transaction_at', '<=', $endDate)
->get()
->reduce(function (?float $carry, RecurringIncome $recurringIncome) use ($endDate) {
$count = $recurringIncome->frequency->getRemainingIterations(
$recurringIncome->next_transaction_at,
$endDate,
$recurringIncome->remaining_recurrences
);

return $carry + $count * $recurringIncome->amount;
});
}

public static function processRecurringExpenses(User $user, Carbon $startDate, Carbon $endDate): float
{
return RecurringExpense::query()
->where('user_id', $user->id)
->whereDate('next_transaction_at', '>=', $startDate)
->whereDate('next_transaction_at', '<=', $endDate)
->get()
->reduce(function (?float $carry, RecurringExpense $recurringExpense) use ($endDate) {
$count = $recurringExpense->frequency->getRemainingIterations(
$recurringExpense->next_transaction_at,
$endDate,
$recurringExpense->remaining_recurrences
);

return $carry + $count * $recurringExpense->amount;
});
}
}
2 changes: 2 additions & 0 deletions config/penny.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@

return [
'currency' => env('CURRENCY', 'INR'),

'currency_symbol' => env('CURRENCY_SYMBOL', ''),
];
18 changes: 18 additions & 0 deletions tests/Feature/RecurringOverviewTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

use App\Filament\Widgets\RecurringCashFlowOverviewWidget;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

beforeEach(function () {
$this->user = User::factory()->create();
$this->actingAs($this->user);
});

it('sees recurring cash flow overview widget', function () {
$this->get('/app/recurring-overview')
->assertOk()
->assertSeeLivewire(RecurringCashFlowOverviewWidget::class);
});

0 comments on commit 02222ed

Please sign in to comment.