123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367 |
- <?php
-
- namespace App\Filament\Company\Pages\Reports;
-
- use App\Contracts\ExportableReport;
- use App\DTO\ReportDTO;
- use App\Filament\Company\Pages\Concerns\HasDeferredFiltersForm;
- use App\Filament\Forms\Components\DateRangeSelect;
- use App\Models\Company;
- use App\Services\DateRangeService;
- use App\Support\Column;
- use Filament\Actions\Action;
- use Filament\Actions\ActionGroup;
- use Filament\Forms\Components\Checkbox;
- use Filament\Forms\Components\Component;
- use Filament\Forms\Components\DatePicker;
- use Filament\Forms\Form;
- use Filament\Forms\Set;
- use Filament\Pages\Page;
- use Filament\Support\Enums\ActionSize;
- use Filament\Support\Enums\IconPosition;
- use Filament\Support\Enums\IconSize;
- use Filament\Support\Facades\FilamentIcon;
- use Illuminate\Support\Arr;
- use Illuminate\Support\Carbon;
- use Livewire\Attributes\Computed;
- use Livewire\Attributes\Session;
- use Symfony\Component\HttpFoundation\StreamedResponse;
-
- abstract class BaseReportPage extends Page
- {
- use HasDeferredFiltersForm;
-
- /**
- * @var array<string, mixed> | null
- */
- public ?array $filters = null;
-
- /**
- * @var array<string, mixed> | null
- */
- public ?array $deferredFilters = null;
-
- public string $fiscalYearStartDate;
-
- public string $fiscalYearEndDate;
-
- public Company $company;
-
- public bool $reportLoaded = false;
-
- #[Session]
- public array $toggledTableColumns = [];
-
- abstract protected function buildReport(array $columns): ReportDTO;
-
- abstract public function exportCSV(): StreamedResponse;
-
- abstract public function exportPDF(): StreamedResponse;
-
- abstract protected function getTransformer(ReportDTO $reportDTO): ExportableReport;
-
- /**
- * @return array<Column>
- */
- abstract public function getTable(): array;
-
- public function mount(): void
- {
- $this->initializeProperties();
-
- $this->loadDefaultDateRange();
-
- $this->initializeDefaultFilters();
-
- $this->initializeFilters();
-
- $this->loadDefaultTableColumnToggleState();
- }
-
- protected function initializeDefaultFilters(): void
- {
- //
- }
-
- public function initializeFilters(): void
- {
- if (! count($this->filters ?? [])) {
- $this->filters = null;
- }
-
- $this->getFiltersForm()->fill($this->filters);
- }
-
- protected function convertDatesToDateTimeString(array $filters): array
- {
- if (isset($filters['startDate'])) {
- $filters['startDate'] = Carbon::parse($filters['startDate'])->startOfDay()->toDateTimeString();
- }
-
- if (isset($filters['endDate'])) {
- $filters['endDate'] = Carbon::parse($filters['endDate'])->endOfDay()->toDateTimeString();
- }
-
- return $filters;
- }
-
- protected function getForms(): array
- {
- return [
- 'toggleTableColumnForm',
- 'filtersForm' => $this->getFiltersForm(),
- ];
- }
-
- public function filtersForm(Form $form): Form
- {
- return $form;
- }
-
- public function getFiltersForm(): Form
- {
- return $this->filtersForm($this->makeForm()
- ->statePath('deferredFilters'));
- }
-
- public function updatedFilters(): void
- {
- $this->deferredFilters = $this->filters;
-
- $this->handleFilterUpdates();
- }
-
- protected function isValidDate($date): bool
- {
- return strtotime($date) !== false;
- }
-
- protected function handleFilterUpdates(): void
- {
- //
- }
-
- public function applyFilters(): void
- {
- $this->filters = $this->deferredFilters;
-
- $this->handleFilterUpdates();
-
- $this->loadReportData();
- }
-
- public function getFiltersApplyAction(): Action
- {
- return Action::make('applyFilters')
- ->label('Update Report')
- ->action('applyFilters')
- ->keyBindings(['mod+s'])
- ->button();
- }
-
- public function getFilterState(string $name): mixed
- {
- return Arr::get($this->filters, $name);
- }
-
- public function setFilterState(string $name, mixed $value): void
- {
- Arr::set($this->filters, $name, $value);
- }
-
- public function getDeferredFilterState(string $name): mixed
- {
- return Arr::get($this->deferredFilters, $name);
- }
-
- public function setDeferredFilterState(string $name, mixed $value): void
- {
- Arr::set($this->deferredFilters, $name, $value);
- }
-
- protected function initializeProperties(): void
- {
- $this->company = auth()->user()->currentCompany;
- $this->fiscalYearStartDate = $this->company->locale->fiscalYearStartDate();
- $this->fiscalYearEndDate = $this->company->locale->fiscalYearEndDate();
- }
-
- protected function loadDefaultDateRange(): void
- {
- $startDate = $this->getFilterState('startDate');
- $endDate = $this->getFilterState('endDate');
-
- if ($this->isValidDate($startDate) && $this->isValidDate($endDate)) {
- $matchingDateRange = app(DateRangeService::class)->getMatchingDateRangeOption(Carbon::parse($startDate), Carbon::parse($endDate));
- $this->setFilterState('dateRange', $matchingDateRange);
- } else {
- $this->setFilterState('dateRange', $this->getDefaultDateRange());
- $this->setDateRange(Carbon::parse($this->fiscalYearStartDate), Carbon::parse($this->fiscalYearEndDate));
- }
- }
-
- public function loadReportData(): void
- {
- unset($this->report);
-
- $this->reportLoaded = true;
- }
-
- protected function loadDefaultTableColumnToggleState(): void
- {
- $tableColumns = $this->getTable();
-
- foreach ($tableColumns as $column) {
- $columnName = $column->getName();
-
- if (empty($this->toggledTableColumns)) {
- if ($column->isToggleable()) {
- $this->toggledTableColumns[$columnName] = ! $column->isToggledHiddenByDefault();
- } else {
- $this->toggledTableColumns[$columnName] = true;
- }
- }
-
- // Handle cases where the toggle state needs to be reset
- if (! $column->isToggleable()) {
- $this->toggledTableColumns[$columnName] = true;
- } elseif ($column->isToggleable() && $column->isToggledHiddenByDefault() && isset($this->toggledTableColumns[$columnName]) && $this->toggledTableColumns[$columnName]) {
- $this->toggledTableColumns[$columnName] = false;
- }
- }
- }
-
- public function getDefaultDateRange(): string
- {
- return 'FY-' . now()->year;
- }
-
- protected function getToggledColumns(): array
- {
- return array_values(
- array_filter(
- $this->getTable(),
- fn (Column $column) => $this->toggledTableColumns[$column->getName()] ?? false,
- )
- );
- }
-
- #[Computed(persist: true)]
- public function report(): ?ExportableReport
- {
- if ($this->reportLoaded === false) {
- return null;
- }
-
- $columns = $this->getToggledColumns();
- $reportDTO = $this->buildReport($columns);
-
- return $this->getTransformer($reportDTO);
- }
-
- public function setDateRange(Carbon $start, Carbon $end): void
- {
- $this->setFilterState('startDate', $start->startOfDay()->toDateTimeString());
- $this->setFilterState('endDate', $end->isFuture() ? now()->endOfDay()->toDateTimeString() : $end->endOfDay()->toDateTimeString());
- }
-
- public function getFormattedStartDate(): string
- {
- return Carbon::parse($this->getFilterState('startDate'))->startOfDay()->toDateTimeString();
- }
-
- public function getFormattedEndDate(): string
- {
- return Carbon::parse($this->getFilterState('endDate'))->endOfDay()->toDateTimeString();
- }
-
- public function toggleColumnsAction(): Action
- {
- return Action::make('toggleColumns')
- ->label(__('filament-tables::table.actions.toggle_columns.label'))
- ->iconButton()
- ->size(ActionSize::Large)
- ->icon(FilamentIcon::resolve('tables::actions.toggle-columns') ?? 'heroicon-m-view-columns')
- ->color('gray');
- }
-
- public function toggleTableColumnForm(Form $form): Form
- {
- return $form
- ->schema($this->getTableColumnToggleFormSchema())
- ->statePath('toggledTableColumns');
- }
-
- protected function hasToggleableColumns(): bool
- {
- return ! empty($this->getTableColumnToggleFormSchema());
- }
-
- /**
- * @return array<Checkbox>
- */
- protected function getTableColumnToggleFormSchema(): array
- {
- $schema = [];
-
- foreach ($this->getTable() as $column) {
- if ($column->isToggleable()) {
- $schema[] = Checkbox::make($column->getName())
- ->label($column->getLabel());
- }
- }
-
- return $schema;
- }
-
- protected function getHeaderActions(): array
- {
- return [
- ActionGroup::make([
- Action::make('exportCSV')
- ->label('CSV')
- ->action(fn () => $this->exportCSV()),
- Action::make('exportPDF')
- ->label('PDF')
- ->action(fn () => $this->exportPDF()),
- ])
- ->label('Export')
- ->button()
- ->outlined()
- ->dropdownWidth('max-w-[7rem]')
- ->dropdownPlacement('bottom-end')
- ->icon('heroicon-c-chevron-down')
- ->iconSize(IconSize::Small)
- ->iconPosition(IconPosition::After),
- ];
- }
-
- protected function getDateRangeFormComponent(): Component
- {
- return DateRangeSelect::make('dateRange')
- ->label('Date Range')
- ->selectablePlaceholder(false)
- ->startDateField('startDate')
- ->endDateField('endDate');
- }
-
- protected function getStartDateFormComponent(): Component
- {
- return DatePicker::make('startDate')
- ->label('Start Date')
- ->live()
- ->afterStateUpdated(static function ($state, Set $set) {
- $set('dateRange', 'Custom');
- });
- }
-
- protected function getEndDateFormComponent(): Component
- {
- return DatePicker::make('endDate')
- ->label('End Date')
- ->live()
- ->afterStateUpdated(static function (Set $set) {
- $set('dateRange', 'Custom');
- });
- }
- }
|