components([ Grid::make() ->schema([ Section::make(__('visits.sections.visit_info')) ->schema([ Select::make('patient') ->label(__('visits.fields.patient')) ->options(fn () => Patient::all()->mapWithKeys( fn ($p) => [$p->id => $p->full_name . ($p->icno ? ' - ' . $p->icno : '')] )->toArray()) ->searchable() ->nullable() ->columnSpan(2), DatePicker::make('visit_date') ->label(__('visits.fields.visit_date')) ->jalali() ->default(now()) ->required(), TimePicker::make('visit_time') ->label(__('visits.fields.visit_time')) ->nullable() ->native(false) ->seconds(false) ->minutesStep(15), Select::make('doctor') ->label(__('visits.fields.doctor')) ->options(fn () => Doctor::all()->mapWithKeys( fn ($d) => [$d->id => $d->full_name] )->toArray()) ->searchable() ->nullable(), Select::make('treatment') ->label(__('visits.fields.treatment')) ->options(fn () => Treatment::all()->mapWithKeys( fn ($t) => [$t->id => ($t->treatment_name ?: $t->treatment_type)] )->toArray()) ->searchable() ->nullable() ->live() ->afterStateUpdated(function ($state, callable $set) { if ($state) { $treatment = Treatment::find($state); if ($treatment && $treatment->treatment_cost > 0) { $set('treatment_cost', $treatment->treatment_cost); } } }), ]) ->columns(2) ->columnSpanFull(), Section::make(__('visits.sections.financial_info')) ->schema([ TextInput::make('treatment_cost') ->label(__('visits.fields.treatment_cost')) ->numeric() ->nullable() ->suffix(__('visits.fields.currency')), TextInput::make('paid_amount') ->label(__('visits.fields.paid_amount')) ->numeric() ->nullable() ->suffix(__('visits.fields.currency')), Select::make('payment_type') ->label(__('visits.fields.payment_type')) ->options(fn () => PaymentType::all()->mapWithKeys( fn ($p) => [$p->id => $p->payment_type] )->toArray()) ->searchable() ->nullable(), ]) ->columns(3) ->columnSpanFull(), Section::make(__('visits.sections.treatment_details')) ->schema([ Textarea::make('treatment_description') ->label(__('visits.fields.treatment_description')) ->rows(4) ->columnSpanFull(), ]) ->columnSpanFull() ->footerActions([ fn (string $operation) => Action::make('create') ->label(__('filament-panels::resources/pages/create-record.form.actions.create.label')) ->submit('create') ->keyBindings(['mod+s']) ->visible($operation === 'create'), fn (string $operation) => Action::make('cancelCreate') ->label(__('filament-panels::resources/pages/create-record.form.actions.cancel.label')) ->url(static::getUrl('index')) ->color('gray') ->visible($operation === 'create'), fn (string $operation) => Action::make('save') ->label(__('filament-panels::resources/pages/edit-record.form.actions.save.label')) ->submit('save') ->keyBindings(['mod+s']) ->visible($operation === 'edit'), fn (string $operation) => Action::make('cancelEdit') ->label(__('filament-panels::resources/pages/edit-record.form.actions.cancel.label')) ->url(static::getUrl('index')) ->color('gray') ->visible($operation === 'edit'), ]), ]) ->columnSpanFull(), ]); } public static function table(Table $table): Table { return $table ->modifyQueryUsing(fn ($query) => $query->with(['patientRecord', 'doctorRecord', 'treatmentRecord', 'paymentTypeRecord'])) ->deferLoading() ->columns([ TextColumn::make('visit_date') ->label(__('visits.fields.visit_date')) ->sortable() ->searchable() ->formatStateUsing(function ($state) { if (! $state) { return '-'; } try { $date = \Carbon\Carbon::parse($state); return Jalalian::fromCarbon($date)->format('Y/m/d'); } catch (\Exception $e) { return $state; } }) ->placeholder('-'), TextColumn::make('patientRecord.first_name') ->label(__('visits.fields.patient')) ->formatStateUsing(fn ($record) => $record->patientRecord?->full_name ?? '-') ->searchable() ->sortable(), TextColumn::make('doctorRecord.first_name') ->label(__('visits.fields.doctor')) ->formatStateUsing(fn ($record) => $record->doctorRecord?->full_name ?? '-') ->sortable() ->placeholder('-'), TextColumn::make('treatmentRecord.treatment_name') ->label(__('visits.fields.treatment')) ->formatStateUsing(fn ($record) => $record->treatmentRecord ? ($record->treatmentRecord->treatment_name ?: $record->treatmentRecord->treatment_type) : '-') ->sortable() ->placeholder('-'), TextColumn::make('treatment_cost') ->label(__('visits.fields.treatment_cost')) ->formatStateUsing(fn ($state) => $state !== null ? number_format((int) $state) : '-') ->sortable() ->placeholder('-'), TextColumn::make('paid_amount') ->label(__('visits.fields.paid_amount')) ->formatStateUsing(fn ($state) => $state !== null ? number_format((int) $state) : '-') ->sortable() ->placeholder('-'), TextColumn::make('remained_amount') ->label(__('visits.fields.remained_amount')) ->state(fn ($record) => $record->remained_amount) ->formatStateUsing(fn ($state) => number_format((int) $state)) ->color(fn ($record) => $record->remained_amount > 0 ? 'danger' : 'success') ->placeholder('-'), TextColumn::make('paymentTypeRecord.payment_type') ->label(__('visits.fields.payment_type')) ->placeholder('-'), TextColumn::make('created_at') ->label(__('visits.fields.created_at')) ->sortable() ->formatStateUsing(function ($state) { if (! $state) { return '-'; } try { $date = \Carbon\Carbon::parse($state); return Jalalian::fromCarbon($date)->format('Y/m/d - H:i'); } catch (\Exception $e) { return $state; } }) ->toggleable(isToggledHiddenByDefault: true), ]) ->defaultSort('created_at', 'desc') ->filters([ SelectFilter::make('doctor') ->label(__('visits.fields.doctor')) ->options(fn () => Doctor::all()->mapWithKeys( fn ($d) => [$d->id => $d->full_name] )->toArray()), SelectFilter::make('treatment') ->label(__('visits.fields.treatment')) ->options(fn () => Treatment::all()->mapWithKeys( fn ($t) => [$t->id => ($t->treatment_name ?: $t->treatment_type)] )->toArray()), SelectFilter::make('payment_type') ->label(__('visits.fields.payment_type')) ->options(fn () => PaymentType::all()->mapWithKeys( fn ($p) => [$p->id => $p->payment_type] )->toArray()), ]) ->recordActions([ EditAction::make(), DeleteAction::make(), ]) ->toolbarActions([ BulkActionGroup::make([ DeleteBulkAction::make(), BulkAction::make('export_excel_selected') ->label(__('table.export_excel_selected')) ->icon('heroicon-o-table-cells') ->color('success') ->action(function (Collection $records) { return self::exportToExcel($records); }), ]), ActionGroup::make([ Action::make('export_excel') ->label(__('table.export_excel')) ->color('success') ->icon('heroicon-o-table-cells') ->action(function () { $records = Visit::with(['patientRecord', 'doctorRecord', 'treatmentRecord', 'paymentTypeRecord']) ->orderBy('created_at', 'desc') ->get(); return self::exportToExcel($records); }), Action::make('export_pdf') ->label(__('table.export_pdf')) ->color('danger') ->icon('heroicon-o-document-arrow-down') ->action(function () { $records = Visit::with(['patientRecord', 'doctorRecord', 'treatmentRecord', 'paymentTypeRecord']) ->orderBy('created_at', 'desc') ->get(); return PdfService::download( 'exports.visits-pdf', ['records' => $records], 'visits-' . now()->format('Y-m-d') . '.pdf', ); }), ]) ->label(__('table.export')) ->color('orange') ->button(), ]); } private static function exportToExcel(Collection $records): mixed { $tempPath = tempnam(sys_get_temp_dir(), 'export_') . '.xlsx'; $writer = new Writer(); $writer->openToFile($tempPath); $writer->addRow(Row::fromValues([ 'ID', __('visits.fields.visit_date'), __('visits.fields.patient'), __('visits.fields.doctor'), __('visits.fields.treatment'), __('visits.fields.treatment_cost'), __('visits.fields.paid_amount'), __('visits.fields.remained_amount'), __('visits.fields.payment_type'), __('visits.fields.treatment_description'), ])); foreach ($records as $record) { $writer->addRow(Row::fromValues([ $record->id, $record->visit_date ?? '', $record->patientRecord?->full_name ?? '', $record->doctorRecord?->full_name ?? '', $record->treatmentRecord ? ($record->treatmentRecord->treatment_name ?: $record->treatmentRecord->treatment_type) : '', $record->treatment_cost ?? 0, $record->paid_amount ?? 0, $record->remained_amount, $record->paymentTypeRecord?->payment_type ?? '', $record->treatment_description ?? '', ])); } $writer->close(); return response()->streamDownload(function () use ($tempPath) { readfile($tempPath); @unlink($tempPath); }, 'visits-' . now()->format('Y-m-d') . '.xlsx', [ 'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', ]); } public static function getPages(): array { return [ 'index' => Pages\ListVisits::route('/'), 'create' => Pages\CreateVisit::route('/create'), 'edit' => Pages\EditVisit::route('/{record}/edit'), ]; } }