feat: report dashboard

This commit is contained in:
Nawcodes 2025-04-27 18:03:42 +07:00
parent ac9fee750b
commit d65f1bda1f
13 changed files with 556 additions and 5 deletions

View File

@ -0,0 +1,145 @@
<?php
namespace App\Filament\Pages;
use App\Models\TrRegistrasi;
use App\Models\TrTransaksi;
use Carbon\Carbon;
use Filament\Actions\Concerns\InteractsWithActions;
use Filament\Actions\Contracts\HasActions;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Filament\Forms\Form;
use Filament\Pages\Page;
use Malzariey\FilamentDaterangepickerFilter\Fields\DateRangePicker;
use Torgodly\Html2Media\Actions\Html2MediaAction;
use Filament\Actions\Action;
class Dashboard extends \Filament\Pages\Dashboard implements HasForms, HasActions
{
use InteractsWithForms;
use InteractsWithActions;
protected static ?string $navigationIcon = 'heroicon-o-document-text';
protected static string $view = 'filament.pages.dashboard';
public ?array $data = [];
public function mount(): void
{
$this->form->fill();
}
public function form(Form $form): Form
{
return $form->schema([
DateRangePicker::make('filtering_date')
->label('Rentang Tanggal')
->placeholder('dd/mm/yyyy - dd/mm/yyyy')
->format('date format')
->disabledDates(['array of Dates'])
])
->statePath('data');
}
public function filter()
{
// filtering date null return.
if ($this->data['filtering_date'] == null) {
return;
}
$this->dispatch('filter', data: $this->data['filtering_date']);
}
public function printTrendPasienAction(): Action
{
$filtering_date = $this->data['filtering_date'];
if ($this->data['filtering_date']) {
$filtering_date = explode(' - ', $this->data['filtering_date']);
$start_date = $filtering_date[0];
$end_date = $filtering_date[1];
// Format pakai Carbon
$start_date = Carbon::createFromFormat('d/m/Y', $start_date)->format('Y-m-d');
$end_date = Carbon::createFromFormat('d/m/Y', $end_date)->format('Y-m-d');
$query = TrRegistrasi::whereBetween('tgl_registrasi', [$start_date, $end_date]);
} else {
$query = TrRegistrasi::query();
}
$data =
$query->selectRaw('DATE(tgl_registrasi) as tanggal, COUNT(*) as total')
->groupBy('tanggal')
->orderBy('tanggal')
->get()
->pluck('total', 'tanggal');
return Html2MediaAction::make('printTrendPasienAction')
->label('Print')
->scale(2)
->print() // Enable print option
->preview()
->filename(function ($record) use ($filtering_date) {
return 'trends-pasien.pdf';
})
->content(function ($record) use ($data, $filtering_date) {
return view('components.pdf.trends-pasien', ['pasien' => $data, 'filtering_date' => $filtering_date]);
})
->savePdf() // Enable save as PDF option
->requiresConfirmation() // Show confirmation modal
->pagebreak('section', ['css', 'legacy'])
->orientation('portrait') // Portrait orientation
->format('a4', 'mm') // A4 format with mm units
->enableLinks() // Enable links in PDF
->margin([25, 50, 0, 50]); //
}
public function printTrendPendapatanAction(): Action
{
$filtering_date = $this->data['filtering_date'];
if ($this->data['filtering_date']) {
$filtering_date = explode(' - ', $this->data['filtering_date']);
$start_date = $filtering_date[0];
$end_date = $filtering_date[1];
$start_date = Carbon::createFromFormat('d/m/Y', $start_date)->format('Y-m-d');
$end_date = Carbon::createFromFormat('d/m/Y', $end_date)->format('Y-m-d');
$query = TrTransaksi::whereBetween('created_at', [$start_date, $end_date]);
} else {
$query = TrTransaksi::query();
}
$data = $query->where('status', 'paid')
->selectRaw('DATE(created_at) as tanggal, SUM(total_harga) as total')
->groupBy('tanggal')
->orderBy('tanggal')
->get()
->pluck('total', 'tanggal');
return Html2MediaAction::make('printTrendPendapatanAction')
->label('Print')
->scale(2)
->print()
->preview()
->filename(function ($record) use ($filtering_date) {
return 'trends-pendapatan.pdf';
})
->content(function ($record) use ($data, $filtering_date) {
return view('components.pdf.trends-pendapatan', ['pendapatans' => $data, 'filtering_date' => $filtering_date]);
})
->savePdf() // Enable save as PDF option
->requiresConfirmation() // Show confirmation modal
->pagebreak('section', ['css', 'legacy'])
->orientation('portrait') // Portrait orientation
->format('a4', 'mm') // A4 format with mm units
->enableLinks() // Enable links in PDF
->margin([25, 50, 0, 50]); //
}
}

View File

@ -21,6 +21,13 @@ class StatsOverview extends BaseWidget
'Jumlah keseluruhan ' . TrRegistrasi::count() . ' pasien'
. '<br/><a class="underline" href="' . TrRegistrasiResource::getUrl('index') . '"> Lihat Semua Pasien</a>'
)),
// total pasien bulan ini, deskripsi jumlah keseluruhan pasien dengan link ke halaman pasien
Stat::make('Total Pasien Bulan Ini', TrRegistrasi::whereMonth('tgl_registrasi', now()->month)->count())
->description(new HtmlString(
// ini bulan lalu
'Jumlah keseluruhan ' . TrRegistrasi::whereMonth('tgl_registrasi', now()->subMonth()->month)->count() . ' pasien bulan lalu'
. '<br/><a class="underline" href="' . TrRegistrasiResource::getUrl('index') . '"> Lihat Semua Pasien</a>'
)),
// total tagihan, deskripsi jumlah keseluruhan tagihan dengan link ke halaman tagihan
Stat::make('Total Pendapatan Hari Ini', 'Rp ' . number_format(TrTransaksi::whereDate('created_at', now()->toDateString())->where('status', 'paid')->sum('total_harga'), 0, ',', '.'))
@ -29,8 +36,13 @@ class StatsOverview extends BaseWidget
. '<br/><a class="underline" href="' . TrTransaksiResource::getUrl('index') . '"> Lihat Semua Tagihan</a>'
)),
// total tagihan bulan ini, deskripsi jumlah keseluruhan tagihan dengan link ke halaman tagihan
Stat::make('Total Pendapatan Bulan Ini', 'Rp ' . number_format(TrTransaksi::whereMonth('created_at', now()->month)->where('status', 'paid')->sum('total_harga'), 0, ',', '.'))
->description(new HtmlString(
// ini bulan lalu
'Jumlah keseluruhan <strong>Rp.' . number_format(TrTransaksi::whereMonth('created_at', now()->subMonth()->month)->where('status', 'paid')->sum('total_harga'), 0, ',', '.') . '</strong> tagihan bulan lalu'
. '<br/><a class="underline" href="' . TrTransaksiResource::getUrl('index') . '"> Lihat Semua Tagihan</a>'
)),
];
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace App\Filament\Widgets;
use App\Models\TrRegistrasi;
use Carbon\Carbon;
use Filament\Widgets\ChartWidget;
use Livewire\Attributes\On;
class StatsPasienTrends extends ChartWidget
{
protected static ?string $heading = 'Trends Registrasi Pasien';
public $filtering_date;
#[On('filter')]
public function filter($data)
{
$this->filtering_date = $data;
}
protected function getData(): array
{
// filtering date return string: 27/04/2025 - 27/05/2025
if ($this->filtering_date) {
$filtering_date = explode(' - ', $this->filtering_date);
$start_date = $filtering_date[0];
$end_date = $filtering_date[1];
// format using carbon
$start_date = Carbon::createFromFormat('d/m/Y', $start_date)->format('Y-m-d');
$end_date = Carbon::createFromFormat('d/m/Y', $end_date)->format('Y-m-d');
$data = TrRegistrasi::whereBetween('tgl_registrasi', [$start_date, $end_date])->orderBy('tgl_registrasi', 'asc')->get();
} else {
$data = TrRegistrasi::orderBy('tgl_registrasi', 'asc')->get();
}
$data = $data->groupBy('tgl_registrasi')->map(function ($item) {
return $item->count();
});
return [
'datasets' => [
[
'label' => 'Jumlah Pasien',
'data' => $data->values(),
],
],
'labels' => $data->keys(),
];
}
protected function getType(): string
{
return 'line';
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace App\Filament\Widgets;
use App\Models\TrRegistrasi;
use App\Models\TrTransaksi;
use Carbon\Carbon;
use Filament\Widgets\ChartWidget;
use Livewire\Attributes\On;
class StatsPendapatanTrends extends ChartWidget
{
protected static ?string $heading = 'Trends Pendapatan';
public $filtering_date;
#[On('filter')]
public function filter($data)
{
$this->filtering_date = $data;
}
protected function getData(): array
{
if ($this->filtering_date) {
$filtering_date = explode(' - ', $this->filtering_date);
$start_date = $filtering_date[0];
$end_date = $filtering_date[1];
// Format pakai Carbon
$start_date = Carbon::createFromFormat('d/m/Y', $start_date)->format('Y-m-d');
$end_date = Carbon::createFromFormat('d/m/Y', $end_date)->format('Y-m-d');
$query = TrTransaksi::whereBetween('created_at', [$start_date, $end_date]);
} else {
$query = TrTransaksi::query();
}
$data = $query->where('status', 'paid')
->selectRaw('DATE(created_at) as tanggal, SUM(total_harga) as total')
->groupBy('tanggal')
->orderBy('tanggal')
->get()
->pluck('total', 'tanggal');
return [
'datasets' => [
[
'label' => 'Pendapatan',
'data' => $data->values(),
'borderColor' => '#4f46e5',
'backgroundColor' => '#c7d2fe',
],
],
'labels' => $data->keys(),
];
}
protected function getType(): string
{
return 'line';
}
}

View File

@ -34,11 +34,11 @@ class AdminPanelProvider extends PanelProvider
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
->pages([
Pages\Dashboard::class,
// <Pages></Pages>\Dashboard::class,
])
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
->widgets([
StatsOverview::class,
// StatsOverview::class,
// Widgets\AccountWidget::class,
// Widgets\FilamentInfoWidget::class,
])

View File

@ -10,6 +10,7 @@
"filament/filament": "^3.3",
"laravel/framework": "^11.31",
"laravel/tinker": "^2.9",
"malzariey/filament-daterangepicker-filter": "^3.3",
"torgodly/html2media": "^1.1"
},
"require-dev": {

68
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "53b21f71a3004eddc3866a7af901cea9",
"content-hash": "16b3a4696b16e249b46cd0864f4270cd",
"packages": [
{
"name": "anourvalar/eloquent-serialize",
@ -3143,6 +3143,72 @@
],
"time": "2025-04-12T22:26:52+00:00"
},
{
"name": "malzariey/filament-daterangepicker-filter",
"version": "3.3.0",
"source": {
"type": "git",
"url": "https://github.com/malzariey/filament-daterangepicker-filter.git",
"reference": "c4ba99e8d96ad047dd128ffa20edf0c4e835b118"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/malzariey/filament-daterangepicker-filter/zipball/c4ba99e8d96ad047dd128ffa20edf0c4e835b118",
"reference": "c4ba99e8d96ad047dd128ffa20edf0c4e835b118",
"shasum": ""
},
"require": {
"filament/filament": "^3.0",
"illuminate/contracts": "^8.0|^9.0|^10.0|^11.0|^11.0|^12.0",
"spatie/laravel-package-tools": "^1.16.4"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"FilamentDaterangepickerFilter": "Malzariey\\FilamentDaterangepickerFilter\\Facades\\FilamentDaterangepickerFilter"
},
"providers": [
"Malzariey\\FilamentDaterangepickerFilter\\FilamentDaterangepickerFilterServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Malzariey\\FilamentDaterangepickerFilter\\": "src",
"Malzariey\\FilamentDaterangepickerFilter\\Database\\Factories\\": "database/factories"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Majid Al-Zariey",
"email": "malzariey@gmail.com",
"role": "Developer"
}
],
"description": "This package uses daterangepciker library to filter date by a range or predefined date ranges (Today , Yesterday ...etc)",
"homepage": "https://github.com/malzariey/filament-daterangepicker-filter",
"keywords": [
"Malzariey",
"filament-daterangepicker-filter",
"laravel"
],
"support": {
"issues": "https://github.com/malzariey/filament-daterangepicker-filter/issues",
"source": "https://github.com/malzariey/filament-daterangepicker-filter/tree/3.3.0"
},
"funding": [
{
"url": "https://github.com/Malzariey",
"type": "github"
}
],
"time": "2025-04-06T07:29:40+00:00"
},
{
"name": "masterminds/html5",
"version": "2.9.0",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,8 @@
<table>
{{-- hello --}}
<tr>
<td>
<h1>Hello</h1>
</td>
</tr>
</table>

View File

@ -0,0 +1,55 @@
<style>
.body {
font-family: DejaVu Sans, sans-serif;
font-size: 12px;
}
.header {
text-align: center;
margin-bottom: 20px;
}
.table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}
.table th, .table td {
border: 1px solid #000;
padding: 6px;
text-align: center;
}
.table th {
background-color: #f3f4f6;
}
.summary {
margin-top: 20px;
font-size: 14px;
}
.summary p {
margin: 5px 0;
}
</style>
<div class="body">
<div class="header">
<h2>Laporan Registrasi Pasien</h2>
<p>Periode: {{ $filtering_date ? $filtering_date : 'Semua Data' }}</p>
</div>
<table class="table">
<thead>
<tr>
<th>Tanggal</th>
<th>Jumlah Pasien</th>
</tr>
</thead>
<tbody>
@foreach ($pasien as $tanggal => $jumlah)
<tr>
<td>{{ $tanggal }}</td>
<td>{{ $jumlah }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>

View File

@ -0,0 +1,69 @@
<style>
.body {
font-family: DejaVu Sans, sans-serif;
font-size: 12px;
}
.header {
text-align: center;
margin-bottom: 20px;
}
.table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}
.table th, .table td {
border: 1px solid #000;
padding: 6px;
text-align: center;
}
.table th {
background-color: #f3f4f6;
}
.summary {
margin-top: 20px;
font-size: 14px;
}
.summary p {
margin: 5px 0;
}
</style>
<div class="body">
<div class="header">
<h2>Laporan Pendapatan</h2>
<p>Periode: {{ $filtering_date ? $filtering_date : 'Semua Data' }}</p>
</div>
<table class="table">
<thead>
<tr>
<th>Tanggal</th>
<th>Pendapatan (Rp)</th>
</tr>
</thead>
<tbody>
@php
$total_pendapatan = 0;
@endphp
@forelse ($pendapatans as $tanggal => $pendapatan)
<tr>
<td>{{ \Carbon\Carbon::parse($tanggal)->format('d M Y') }}</td>
<td>Rp. {{ number_format($pendapatan, 0, ',', '.') }}</td>
</tr>
{{-- total pendapatan --}}
@php
$total_pendapatan += $pendapatan;
@endphp
@empty
<tr>
<td colspan="2">Tidak ada data</td>
</tr>
@endforelse
<tr>
<td colspan="1">Total Pendapatan</td>
<td>Rp. {{ number_format($total_pendapatan, 0, ',', '.') }}</td>
</tr>
</tbody>
</table>
</div>

View File

@ -0,0 +1,33 @@
<x-filament-panels::page>
@livewire(\App\Filament\Widgets\StatsOverview::class)
<div class="flex items-center justify-end gap-2">
<div>
{{$this->form}}
</div>
<x-filament::button wire:click="filter" class="mt-6">
Filter
</x-filament::button>
</div>
{{-- filament section width: 50:50--}}
<div class="flex flex-row gap-4">
<x-filament::section class="w-1/2">
<div class="mb-5">
{{$this->printTrendPasienAction()}}
</div>
@livewire(\App\Filament\Widgets\StatsPasienTrends::class, ['filtering_date' => $this->data['filtering_date']])
</x-filament::section>
<x-filament::section class="w-1/2">
<div class="mb-5">
{{$this->printTrendPendapatanAction()}}
</div>
@livewire(\App\Filament\Widgets\StatsPendapatanTrends::class, ['filtering_date' => $this->data['filtering_date']])
</x-filament::section>
</div>
<x-filament-actions::modals />
</x-filament-panels::page>