$record->last_name, ]; } public static function getNavigationIcon(): \BackedEnum|string|null { return 'heroicon-o-user-group'; } public static function getNavigationGroup(): ?string { return __('navigation.groups.patients'); } public static function getNavigationSort(): ?int { return 1; } public static function getNavigationLabel(): string { return __('navigation.patients.title'); } public static function getModelLabel(): string { return __('navigation.patients.singular'); } public static function getPluralModelLabel(): string { return __('navigation.patients.title'); } public static function form(Schema $schema): Schema { $yesNo = [ 'بله' => 'بله', 'خیر' => 'خیر', ]; return $schema ->components([ Grid::make() ->schema([ Section::make(__('patients.sections.patient_info')) ->schema([ TextInput::make('first_name') ->label(__('patients.fields.first_name')) ->required() ->validationMessages([ 'required' => __('patients.validation.first_name_required'), ]) ->maxLength(50), TextInput::make('last_name') ->label(__('patients.fields.last_name')) ->required() ->validationMessages([ 'required' => __('patients.validation.last_name_required'), ]) ->maxLength(50), TextInput::make('father_name') ->label(__('patients.fields.father_name')) ->required() ->validationMessages([ 'required' => __('patients.validation.father_name_required'), ]) ->maxLength(50), TextInput::make('icno') ->label(__('patients.fields.icno')) ->required() ->maxLength(11), TextInput::make('hand_phone') ->label(__('patients.fields.hand_phone')) ->required() ->validationMessages([ 'required' => __('patients.validation.hand_phone_required'), ]) ->maxLength(50), TextInput::make('age') ->label(__('patients.fields.age')) ->numeric() ->minValue(0), DatePicker::make('birth_date') ->label(__('patients.fields.birth_date')) ->jalali(), Radio::make('gender') ->label(__('patients.fields.gender')) ->options(['مرد' => 'مرد', 'زن' => 'زن']) ->inline(), Radio::make('marital_status') ->label(__('patients.fields.marital_status')) ->options(['مجرد' => 'مجرد', 'متاهل' => 'متاهل']) ->inline(), TextInput::make('insurance_no') ->label(__('patients.fields.insurance_no')) ->maxLength(50), TextInput::make('insurance') ->label(__('patients.fields.insurance')) ->maxLength(50), TextInput::make('education') ->label(__('patients.fields.education')) ->maxLength(50), TextInput::make('job') ->label(__('patients.fields.job')) ->maxLength(50), TextInput::make('home_phone') ->label(__('patients.fields.home_phone')) ->maxLength(50), TextInput::make('work_phone') ->label(__('patients.fields.work_phone')) ->maxLength(50), TextInput::make('other_phone') ->label(__('patients.fields.other_phone')) ->maxLength(50), Textarea::make('home_address') ->label(__('patients.fields.home_address')) ->rows(2) ->columnSpan(2), Textarea::make('work_address') ->label(__('patients.fields.work_address')) ->rows(2) ->columnSpan(2), TextInput::make('refered_by') ->label(__('patients.fields.refered_by')) ->maxLength(50), TextInput::make('doc_id') ->label(__('patients.fields.doc_id')) ->maxLength(50), Textarea::make('referal_reason') ->label(__('patients.fields.referal_reason')) ->rows(2) ->columnSpan(2), ]) ->columns(4) ->columnSpanFull(), Section::make(__('patients.sections.illness')) ->schema([ CheckboxList::make('current_illness_1') ->label(__('patients.fields.current_illness_1')) ->options(fn () => \Illuminate\Support\Facades\Cache::remember( 'illness_options_1', now()->addDay(), fn () => PatientIllness::where('row_no', 1)->orderBy('priority')->pluck('illness', 'illness')->toArray() )), CheckboxList::make('current_illness_2') ->label(__('patients.fields.current_illness_2')) ->options(fn () => \Illuminate\Support\Facades\Cache::remember( 'illness_options_2', now()->addDay(), fn () => PatientIllness::where('row_no', 2)->orderBy('priority')->pluck('illness', 'illness')->toArray() )), TextInput::make('blood_sugar') ->label(__('patients.fields.blood_sugar')) ->numeric(), TextInput::make('blood_pressure') ->label(__('patients.fields.blood_pressure')) ->numeric(), Radio::make('is_undercare') ->label(__('patients.fields.is_undercare')) ->options($yesNo) ->inline(), TextInput::make('undercare_reason') ->label(__('patients.fields.undercare_reason')) ->maxLength(50), Radio::make('is_usingdrug') ->label(__('patients.fields.is_usingdrug')) ->options($yesNo) ->inline(), TextInput::make('underdrug_reason') ->label(__('patients.fields.underdrug_reason')) ->maxLength(50), CheckboxList::make('has_alergyto') ->label(__('patients.fields.has_alergyto')) ->options([ 'پنی سیلین' => 'پنی سیلین', 'داروی بی حسی' => 'داروی بی حسی', 'مواد غذایی و دارویی دیگر' => 'مواد غذایی و دارویی دیگر', ]) ->columns(3), TextInput::make('alergy_reason') ->label(__('patients.fields.alergy_reason')) ->maxLength(50), Textarea::make('description') ->label(__('patients.fields.description')) ->rows(2) ->columnSpanFull(), ]) ->columns(2) ->columnSpanFull(), Section::make(__('patients.sections.surgery_notes')) ->schema([ RichEditor::make('surgery_before') ->label(__('patients.fields.surgery_before')) ->extraInputAttributes(['style' => 'min-height: 150px']), RichEditor::make('sergery_after') ->label(__('patients.fields.sergery_after')) ->extraInputAttributes(['style' => 'min-height: 150px']), ]) ->columns(2) ->columnSpanFull(), Section::make(__('patients.sections.media')) ->schema([ FileUpload::make('photos_before') ->label(__('patients.fields.photos_before')) ->multiple() ->image() ->disk('public') ->directory('surgery_photos/before') ->downloadable() ->openable() ->fetchFileInformation(false), FileUpload::make('photos_after') ->label(__('patients.fields.photos_after')) ->multiple() ->image() ->disk('public') ->directory('surgery_photos/after') ->downloadable() ->openable() ->fetchFileInformation(false), FileUpload::make('videos') ->label(__('patients.fields.videos')) ->multiple() ->disk('public') ->directory('surgery_photos/videos') ->acceptedFileTypes(['video/mp4', 'video/quicktime', 'video/x-msvideo', 'video/x-matroska', 'video/webm', 'audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/ogg', 'audio/aac']) ->downloadable() ->fetchFileInformation(false), FileUpload::make('audio_files') ->label(__('patients.fields.audio_files')) ->multiple() ->disk('public') ->directory('audio_files') ->acceptedFileTypes([ 'audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/x-wav', 'audio/ogg', 'audio/opus', 'audio/mp4', 'audio/x-m4a', 'audio/aac', 'audio/x-aac', 'audio/flac', 'audio/x-flac', 'audio/webm', 'audio/amr', 'audio/3gpp', 'audio/3gpp2', 'audio/aiff', 'audio/x-aiff', ]) ->downloadable() ->fetchFileInformation(false), ]) ->columns(2) ->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('createAnother') ->label(__('filament-panels::resources/pages/create-record.form.actions.create_another.label')) ->action('createAnother') ->keyBindings(['mod+shift+s']) ->color('gray') ->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(['creator', 'updator', 'surgeryAppointment'])) ->deferLoading() ->columns([ TextColumn::make('first_name') ->label(__('patients.fields.first_name')) ->searchable() ->sortable(), TextColumn::make('last_name') ->label(__('patients.fields.last_name')) ->searchable() ->sortable(), TextColumn::make('hand_phone') ->label(__('patients.fields.hand_phone')) ->placeholder('-'), TextColumn::make('home_phone') ->label(__('patients.fields.home_phone')) ->placeholder('-'), TextColumn::make('creator.name') ->label(__('patients.fields.created_by')) ->placeholder('-'), TextColumn::make('updator.name') ->label(__('patients.fields.updated_by')) ->placeholder('-'), TextColumn::make('updated_at') ->label(__('patients.fields.updated_at')) ->sortable() ->placeholder('-'), TextColumn::make('refered_by') ->label(__('patients.fields.refered_by')) ->placeholder('-'), TextColumn::make('surgery_before') ->label(__('patients.fields.surgery_before')) ->width('150px') ->extraAttributes(fn (Patient $record): array => empty(strip_tags($record->surgery_before ?? '')) ? ['class' => 'pointer-events-none'] : [] ) ->formatStateUsing(function ($state) { $text = strip_tags($state ?? ''); if (! $text) { return '-'; } if (mb_strlen($text) > 30) { return mb_substr($text, 0, 30) . ' ... بیشتر'; } return $text; }) ->html() ->action( Action::make('viewSurgeryBefore') ->label(__('patients.fields.surgery_before')) ->modalWidth(Width::Large) ->modalContent(fn (Patient $record): \Illuminate\Support\HtmlString => new \Illuminate\Support\HtmlString( '
' . $record->surgery_before . '
' ) ) ->modalSubmitAction(false) ->modalCancelActionLabel('بستن') ), TextColumn::make('sergery_after') ->label(__('patients.fields.sergery_after')) ->width('150px') ->extraAttributes(fn (Patient $record): array => empty(strip_tags($record->sergery_after ?? '')) ? ['class' => 'pointer-events-none'] : [] ) ->formatStateUsing(function ($state) { $text = strip_tags($state ?? ''); if (! $text) { return '-'; } if (mb_strlen($text) > 30) { return mb_substr($text, 0, 30) . ' ... بیشتر'; } return $text; }) ->html() ->action( Action::make('viewSergeryAfter') ->label(__('patients.fields.sergery_after')) ->modalWidth(Width::Large) ->modalContent(fn (Patient $record): \Illuminate\Support\HtmlString => new \Illuminate\Support\HtmlString( '
' . $record->sergery_after . '
' ) ) ->modalSubmitAction(false) ->modalCancelActionLabel('بستن') ), TextColumn::make('surgeryAppointment.surgery_date') ->label(__('patients.fields.surgery_appointment')) ->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; } }) ->icon(fn ($state) => $state ? 'heroicon-o-check-circle' : 'heroicon-o-clock') ->iconColor(fn ($state) => $state ? 'success' : 'gray') ->placeholder('-'), ]) ->defaultSort('created_at', 'desc') ->recordActions([ Action::make('surgeryAppointment') ->label(__('patients.actions.surgery_appointment')) ->icon('heroicon-o-calendar-days') ->iconButton() ->color(fn (Patient $record) => $record->surgeryAppointment ? 'success' : 'info') ->modalWidth(Width::Large) ->fillForm(function (Patient $record): array { $appointment = $record->surgeryAppointment; if ($appointment) { $surgeryDate = $appointment->surgery_date; return [ 'surgery_date' => $surgeryDate?->format('Y-m-d'), 'surgery_time' => $surgeryDate?->format('H:i'), 'surgery_center_id' => $appointment->surgery_center_id, ]; } return [ 'surgery_date' => null, 'surgery_time' => '09:00', 'surgery_center_id' => null, ]; }) ->form([ \Filament\Forms\Components\Placeholder::make('patient_name') ->label(__('patients.fields.full_name')) ->content(fn (Patient $record) => $record->full_name), Grid::make(2) ->schema([ DatePicker::make('surgery_date') ->label(__('patients.fields.surgery_date')) ->jalali() ->minDate(today()) ->required(), TimePicker::make('surgery_time') ->label(__('patients.fields.surgery_time')) ->native(false) ->seconds(false) ->minutesStep(15), ]), Select::make('surgery_center_id') ->label(__('patients.fields.surgery_center')) ->options(fn () => SurgeryCenter::pluck('name', 'id')->toArray()) ->searchable() ->required(), ]) ->modalSubmitAction(false) ->extraModalFooterActions(fn (Action $action): array => [ $action->makeModalSubmitAction('saveAppointment', arguments: ['save' => true]) ->label(__('patients.actions.save_appointment')) ->color('primary') ->icon('heroicon-o-check') ->visible(fn (Patient $record) => $record->surgeryAppointment === null), Action::make('printAdmission') ->label(__('patients.actions.print_admission')) ->color('warning') ->icon('heroicon-o-printer') ->url(fn (Patient $record) => route('surgery-appointment.print.admission', $record), shouldOpenInNewTab: true) ->visible(fn (Patient $record) => $record->surgeryAppointment !== null), $action->makeModalSubmitAction('deleteAppointment', arguments: ['delete' => true]) ->label(__('patients.actions.delete_appointment')) ->color('danger') ->icon('heroicon-o-trash') ->visible(fn (Patient $record) => $record->surgeryAppointment !== null), ]) ->action(function (Patient $record, array $data, array $arguments, Action $action): void { if (! empty($arguments['delete'])) { SurgeryAppointment::where('patient_id', $record->id)->delete(); \Filament\Notifications\Notification::make() ->title(__('patients.actions.appointment_deleted')) ->success() ->send(); return; } $dateStr = $data['surgery_date']; $timeStr = $data['surgery_time'] ?? '00:00'; $surgeryDateTime = \Carbon\Carbon::parse($dateStr . ' ' . $timeStr); $jalaliDate = Jalalian::fromCarbon($surgeryDateTime)->format('Y/m/d'); $appointment = SurgeryAppointment::updateOrCreate( ['patient_id' => $record->id], [ 'surgery_date' => $surgeryDateTime, 'surgery_center_id' => $data['surgery_center_id'], ] ); try { $response = \Illuminate\Support\Facades\Http::timeout(30) ->withHeader('X-API-Key', \App\Models\OsurgInitial::val('sms_api_key', config('sms.api_key'))) ->post(\App\Models\OsurgInitial::val('sms_api_url', config('sms.api_url')) . '/surgery-reminder', [ 'phoneNumber' => $record->hand_phone ?? '', 'patientName' => $record->full_name, 'surgeryDate' => $jalaliDate, 'appointmentId' => $appointment->id, 'patientId' => $record->id, 'surgeryDateFull' => $surgeryDateTime->toDateTimeString(), ]); if (! ($response->json('success') ?? false)) { \Filament\Notifications\Notification::make() ->title(__('patients.actions.appointment_saved')) ->body(__('patients.actions.api_failed') . ': ' . ($response->json('message') ?? '')) ->warning() ->send(); } else { \Filament\Notifications\Notification::make() ->title(__('patients.actions.appointment_saved')) ->body(__('patients.actions.sms_sent')) ->success() ->send(); } } catch (\Throwable $e) { \Illuminate\Support\Facades\Log::warning('Surgery appointment API failed', [ 'patient_id' => $record->id, 'appointment_id' => $appointment->id, 'error' => $e->getMessage(), ]); \Filament\Notifications\Notification::make() ->title(__('patients.actions.appointment_saved')) ->body(__('patients.actions.api_failed') . ': ' . $e->getMessage()) ->warning() ->send(); } $record->refresh(); $action->halt(); }), Action::make('prescription') ->label(__('prescriptions.actions.create')) ->icon('heroicon-o-document-text') ->iconButton() ->color('warning') ->slideOver() ->fillForm(function (Patient $record): array { $prescription = Prescription::where('patient', $record->id) ->latest() ->first(); if ($prescription) { $medicationIds = $prescription->medication_ids ?? []; $content = $prescription->content ?? ''; } else { $defaultMeds = Medication::where('is_active', true) ->where('is_default', true) ->orderBy('name') ->get(); $medicationIds = $defaultMeds->pluck('id')->toArray(); $content = $defaultMeds->isNotEmpty() ? '' : ''; } return [ 'medication_ids' => $medicationIds, 'medication_order' => $medicationIds, 'content' => $content, 'issue_date' => now()->toDateString(), ]; }) ->form([ Grid::make(2) ->schema([ \Filament\Forms\Components\Placeholder::make('patient_name') ->label(__('prescriptions.fields.patient')) ->content(fn (Patient $record) => $record->full_name), \Filament\Forms\Components\DatePicker::make('issue_date') ->label(__('prescriptions.fields.issue_date')) ->jalali() ->required(), ]), \Filament\Forms\Components\Hidden::make('medication_order'), \Filament\Forms\Components\CheckboxList::make('medication_ids') ->label(__('prescriptions.fields.medication_ids')) ->options(fn () => Medication::where('is_active', true) ->orderBy('name') ->get() ->mapWithKeys(fn ($m) => [ $m->id => implode(' ', array_filter([ $m->dosage_form, $m->name, $m->strength, $m->quantity ? '#' . $m->quantity : null, $m->timing, ])), ]) ->toArray() ) ->columns(2) ->live() ->afterStateUpdated(function (array $state, callable $set, callable $get) { $currentOrder = array_map('intval', (array) ($get('medication_order') ?: [])); $state = array_map('intval', $state); $newOrder = array_values( array_filter($currentOrder, fn ($id) => in_array($id, $state)) ); foreach ($state as $id) { if (! in_array($id, $newOrder)) { $newOrder[] = $id; } } $set('medication_order', $newOrder); if (empty($newOrder)) { $set('content', null); return; } $medications = Medication::whereIn('id', $newOrder)->get()->keyBy('id'); $html = ''; $set('content', $html); }) ->columnSpanFull(), \Filament\Forms\Components\RichEditor::make('content') ->label(__('prescriptions.fields.content')) ->columnSpanFull(), ]) ->modalSubmitAction(false) ->extraModalFooterActions(fn (Action $action): array => [ $action->makeModalSubmitAction('printPrescription', arguments: ['printType' => 'prescription']) ->label(__('prescriptions.actions.save_and_print')) ->color('info') ->icon('heroicon-o-printer'), $action->makeModalSubmitAction('printAdmission', arguments: ['printType' => 'admission']) ->label(__('prescriptions.actions.print_admission')) ->color('warning') ->icon('heroicon-o-printer') ->visible(fn (Patient $record) => $record->surgeryAppointment !== null), $action->makeModalSubmitAction('printLab', arguments: ['printType' => 'lab']) ->label(__('prescriptions.actions.print_lab')) ->color('gray') ->icon('heroicon-o-beaker'), ]) ->action(function (Patient $record, array $data, array $arguments, $livewire): void { $prescription = Prescription::updateOrCreate( ['patient' => $record->id], [ 'doctor' => auth()->user()?->name, 'issue_date' => $data['issue_date'] ?? '', 'issue_datetime' => now(), 'medication_ids' => $data['medication_order'] ?? $data['medication_ids'] ?? [], 'content' => $data['content'] ?? '', ] ); $printType = $arguments['printType'] ?? null; if ($printType === 'prescription') { $livewire->redirect(route('prescription.print', $prescription)); } elseif ($printType === 'admission') { $livewire->redirect(route('prescription.print.admission', $prescription)); } elseif ($printType === 'lab') { $livewire->redirect(route('prescription.print', ['prescription' => $prescription, 'type' => 'lab'])); } }) ->successNotificationTitle(__('prescriptions.actions.saved')), Action::make('viewFiles') ->label(__('patients.actions.view_files')) ->icon('heroicon-o-paper-clip') ->iconButton() ->color('warning') ->visible(fn (Patient $record): bool => ! empty(array_filter([ $record->photos_before, $record->photos_after, $record->videos, $record->audio_files, ])) ) ->modalWidth(Width::Large) ->fillForm(fn (Patient $record): array => [ 'photos_before' => $record->photos_before ?? [], 'photos_after' => $record->photos_after ?? [], 'videos' => $record->videos ?? [], 'audio_files' => $record->audio_files ?? [], ]) ->schema([ Grid::make(2) ->schema([ FileUpload::make('photos_before') ->label(__('patients.fields.photos_before')) ->multiple() ->image() ->imagePreviewHeight('150') ->disk('public') ->directory('surgery_photos/before') ->downloadable() ->openable() ->disabled() ->fetchFileInformation(false) ->extraAttributes(['data-hide-dropzone' => 'true']), FileUpload::make('photos_after') ->label(__('patients.fields.photos_after')) ->multiple() ->image() ->imagePreviewHeight('150') ->disk('public') ->directory('surgery_photos/after') ->downloadable() ->openable() ->disabled() ->fetchFileInformation(false) ->extraAttributes(['data-hide-dropzone' => 'true']), FileUpload::make('videos') ->label(__('patients.fields.videos')) ->multiple() ->disk('public') ->directory('surgery_photos/videos') ->acceptedFileTypes(['video/mp4', 'video/quicktime', 'video/x-msvideo', 'video/x-matroska', 'video/webm', 'audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/ogg', 'audio/aac']) ->downloadable() ->disabled() ->fetchFileInformation(false) ->extraAttributes(['data-hide-dropzone' => 'true']), FileUpload::make('audio_files') ->label(__('patients.fields.audio_files')) ->multiple() ->disk('public') ->directory('audio_files') ->acceptedFileTypes([ 'audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/x-wav', 'audio/ogg', 'audio/opus', 'audio/mp4', 'audio/x-m4a', 'audio/aac', 'audio/x-aac', 'audio/flac', 'audio/x-flac', 'audio/webm', 'audio/amr', 'audio/3gpp', 'audio/3gpp2', 'audio/aiff', 'audio/x-aiff', ]) ->downloadable() ->disabled() ->fetchFileInformation(false) ->extraAttributes(['data-hide-dropzone' => 'true']), ]), ]) ->modalSubmitAction(false) ->modalCancelActionLabel('بستن'), EditAction::make()->iconButton(), DeleteAction::make()->iconButton(), ], position: \Filament\Tables\Enums\RecordActionsPosition::BeforeCells) ->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) { $tempPath = tempnam(sys_get_temp_dir(), 'export_') . '.xlsx'; $writer = new Writer(); $writer->openToFile($tempPath); $writer->addRow(Row::fromValues([ 'ID', __('patients.fields.first_name'), __('patients.fields.last_name'), __('patients.fields.icno'), __('patients.fields.gender'), __('patients.fields.hand_phone'), __('patients.fields.insurance'), ])); foreach ($records as $record) { $writer->addRow(Row::fromValues([ $record->id, $record->first_name, $record->last_name, $record->icno, $record->gender, $record->hand_phone, $record->insurance, ])); } $writer->close(); return response()->streamDownload(function () use ($tempPath) { readfile($tempPath); @unlink($tempPath); }, 'patients-' . now()->format('Y-m-d') . '.xlsx', [ 'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', ]); }), ]), ActionGroup::make([ Action::make('export_excel') ->label(__('table.export_excel')) ->color('success') ->icon('heroicon-o-table-cells') ->action(function () { $records = Patient::orderBy('last_name')->orderBy('first_name')->get(); $tempPath = tempnam(sys_get_temp_dir(), 'export_') . '.xlsx'; $writer = new Writer(); $writer->openToFile($tempPath); $writer->addRow(Row::fromValues([ 'ID', __('patients.fields.first_name'), __('patients.fields.last_name'), __('patients.fields.icno'), __('patients.fields.gender'), __('patients.fields.hand_phone'), __('patients.fields.insurance'), ])); foreach ($records as $record) { $writer->addRow(Row::fromValues([ $record->id, $record->first_name, $record->last_name, $record->icno, $record->gender, $record->hand_phone, $record->insurance, ])); } $writer->close(); return response()->streamDownload(function () use ($tempPath) { readfile($tempPath); @unlink($tempPath); }, 'patients-' . now()->format('Y-m-d') . '.xlsx', [ 'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', ]); }), Action::make('export_pdf') ->label(__('table.export_pdf')) ->color('danger') ->icon('heroicon-o-document-arrow-down') ->action(function () { $records = Patient::orderBy('last_name')->orderBy('first_name')->get(); return PdfService::download( 'exports.patients-pdf', ['records' => $records], 'patients-' . now()->format('Y-m-d') . '.pdf', ); }), ]) ->label(__('table.export')) ->color('orange') ->button(), ]); } public static function getRelations(): array { return [ VisitsRelationManager::class, ]; } public static function getPages(): array { return [ 'index' => Pages\ListPatients::route('/'), 'create' => Pages\CreatePatient::route('/create'), 'edit' => Pages\EditPatient::route('/{record}/edit'), ]; } }