| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 | <?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\Company\Pages\Concerns\HasTableColumnToggleForm;
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\DatePicker;
use Filament\Forms\Set;
use Filament\Pages\Page;
use Filament\Support\Enums\IconPosition;
use Filament\Support\Enums\IconSize;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use Livewire\Attributes\Computed;
use Symfony\Component\HttpFoundation\StreamedResponse;
abstract class BaseReportPage extends Page
{
    use HasDeferredFiltersForm;
    use HasTableColumnToggleForm;
    public string $fiscalYearStartDate;
    public string $fiscalYearEndDate;
    public Company $company;
    public bool $reportLoaded = false;
    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();
    }
    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
    {
        $flatFields = $this->getFiltersForm()->getFlatFields();
        $dateRangeField = Arr::first($flatFields, static fn ($field) => $field instanceof DateRangeSelect);
        if (! $dateRangeField) {
            return;
        }
        $startDateField = $dateRangeField->getStartDateField();
        $endDateField = $dateRangeField->getEndDateField();
        $startDate = $startDateField ? $this->getFilterState($startDateField) : null;
        $endDate = $endDateField ? $this->getFilterState($endDateField) : null;
        $startDateCarbon = $this->isValidDate($startDate) ? Carbon::parse($startDate) : null;
        $endDateCarbon = $this->isValidDate($endDate) ? Carbon::parse($endDate) : null;
        if ($startDateCarbon && $endDateCarbon) {
            $this->setMatchingDateRange($startDateCarbon, $endDateCarbon);
            return;
        }
        if ($endDateCarbon && ! $startDateField) {
            $this->setAsOfDateRange($endDateCarbon);
            return;
        }
        if ($endDateField && ! $startDateField) {
            $this->setFilterState('dateRange', $this->getDefaultDateRange());
            $defaultEndDate = Carbon::parse($this->fiscalYearEndDate);
            $this->setFilterState($endDateField, $defaultEndDate->isFuture() ? now()->endOfDay()->toDateTimeString() : $defaultEndDate->endOfDay()->toDateTimeString());
            return;
        }
        if ($startDateField && $endDateField) {
            $this->setFilterState('dateRange', $this->getDefaultDateRange());
            $defaultStartDate = Carbon::parse($this->fiscalYearStartDate);
            $defaultEndDate = Carbon::parse($this->fiscalYearEndDate);
            $this->setDateRange($defaultStartDate, $defaultEndDate);
        }
    }
    protected function setMatchingDateRange($startDate, $endDate): void
    {
        $matchingDateRange = app(DateRangeService::class)->getMatchingDateRangeOption($startDate, $endDate);
        $this->setFilterState('dateRange', $matchingDateRange);
    }
    protected function setAsOfDateRange($endDate): void
    {
        $fiscalYearStart = Carbon::parse($this->fiscalYearStartDate);
        $asOfStartDate = $endDate->copy()->setMonth($fiscalYearStart->month)->setDay($fiscalYearStart->day);
        $this->setMatchingDateRange($asOfStartDate, $endDate);
    }
    public function loadReportData(): void
    {
        unset($this->report);
        $this->reportLoaded = true;
    }
    public function getDefaultDateRange(): string
    {
        return 'FY-' . now()->year;
    }
    #[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 getFormattedAsOfDate(): string
    {
        return Carbon::parse($this->getFilterState('asOfDate'))->endOfDay()->toDateTimeString();
    }
    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(): DateRangeSelect
    {
        return DateRangeSelect::make('dateRange')
            ->label('Date Range')
            ->selectablePlaceholder(false)
            ->startDateField('startDate')
            ->endDateField('endDate');
    }
    protected function getStartDateFormComponent(): DatePicker
    {
        return DatePicker::make('startDate')
            ->label('Start Date')
            ->live()
            ->afterStateUpdated(static function ($state, Set $set) {
                $set('dateRange', 'Custom');
            });
    }
    protected function getEndDateFormComponent(): DatePicker
    {
        return DatePicker::make('endDate')
            ->label('End Date')
            ->live()
            ->afterStateUpdated(static function (Set $set) {
                $set('dateRange', 'Custom');
            });
    }
    protected function getAsOfDateFormComponent(): DatePicker
    {
        return DatePicker::make('asOfDate')
            ->label('As of Date')
            ->live()
            ->afterStateUpdated(static function (Set $set) {
                $set('dateRange', 'Custom');
            })
            ->extraFieldWrapperAttributes([
                'class' => 'report-hidden-label',
            ]);
    }
}
 |