2026-04-03 13:55:17 +00:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
|
|
namespace App\Filament\Resources\PatientResource\Pages;
|
|
|
|
|
|
|
|
|
|
|
|
use App\Filament\Resources\PatientResource;
|
2026-04-17 16:45:38 +00:00
|
|
|
|
use App\Models\SurgeryAppointment;
|
|
|
|
|
|
use App\Models\SurgeryCenter;
|
|
|
|
|
|
use Carbon\Carbon;
|
2026-04-03 13:55:17 +00:00
|
|
|
|
use Filament\Actions\Action;
|
|
|
|
|
|
use Filament\Actions\DeleteAction;
|
2026-04-17 16:45:38 +00:00
|
|
|
|
use Filament\Forms\Components\DatePicker;
|
|
|
|
|
|
use Filament\Schemas\Components\Grid;
|
|
|
|
|
|
use Filament\Forms\Components\Placeholder;
|
|
|
|
|
|
use Filament\Forms\Components\Select;
|
|
|
|
|
|
use Filament\Forms\Components\TimePicker;
|
2026-04-03 13:55:17 +00:00
|
|
|
|
use Filament\Resources\Pages\EditRecord;
|
|
|
|
|
|
use Filament\Support\Enums\Width;
|
2026-04-17 16:45:38 +00:00
|
|
|
|
use Illuminate\Support\Facades\Http;
|
|
|
|
|
|
use Morilog\Jalali\Jalalian;
|
2026-04-03 13:55:17 +00:00
|
|
|
|
|
|
|
|
|
|
class EditPatient extends EditRecord
|
|
|
|
|
|
{
|
|
|
|
|
|
protected static string $resource = PatientResource::class;
|
|
|
|
|
|
|
|
|
|
|
|
protected Width|string|null $maxContentWidth = Width::SevenExtraLarge;
|
|
|
|
|
|
|
|
|
|
|
|
protected function getHeaderActions(): array
|
|
|
|
|
|
{
|
|
|
|
|
|
return [
|
2026-04-17 16:45:38 +00:00
|
|
|
|
$this->getSaveFormAction()
|
|
|
|
|
|
->formId('form'),
|
|
|
|
|
|
|
|
|
|
|
|
Action::make('surgeryAppointment')
|
|
|
|
|
|
->label(__('patients.actions.surgery_appointment'))
|
|
|
|
|
|
->icon('heroicon-o-calendar-days')
|
|
|
|
|
|
->color(fn () => $this->getRecord()->surgeryAppointment ? 'success' : 'info')
|
|
|
|
|
|
->modalWidth(Width::Large)
|
|
|
|
|
|
->fillForm(function (): array {
|
|
|
|
|
|
$appointment = $this->getRecord()->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([
|
|
|
|
|
|
Placeholder::make('patient_name')
|
|
|
|
|
|
->label(__('patients.fields.full_name'))
|
|
|
|
|
|
->content(fn () => $this->getRecord()->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 () => $this->getRecord()->surgeryAppointment === null),
|
|
|
|
|
|
|
|
|
|
|
|
Action::make('printAdmissionFromModal')
|
|
|
|
|
|
->label(__('patients.actions.print_admission'))
|
|
|
|
|
|
->color('warning')
|
|
|
|
|
|
->icon('heroicon-o-printer')
|
|
|
|
|
|
->url(fn () => route('surgery-appointment.print.admission', $this->getRecord()), shouldOpenInNewTab: true)
|
|
|
|
|
|
->visible(fn () => $this->getRecord()->surgeryAppointment !== null),
|
|
|
|
|
|
|
|
|
|
|
|
$action->makeModalSubmitAction('deleteAppointment', arguments: ['delete' => true])
|
|
|
|
|
|
->label(__('patients.actions.delete_appointment'))
|
|
|
|
|
|
->color('danger')
|
|
|
|
|
|
->icon('heroicon-o-trash')
|
|
|
|
|
|
->visible(fn () => $this->getRecord()->surgeryAppointment !== null),
|
|
|
|
|
|
])
|
|
|
|
|
|
->action(function (array $data, array $arguments): void {
|
|
|
|
|
|
$record = $this->getRecord();
|
|
|
|
|
|
|
|
|
|
|
|
if (! empty($arguments['delete'])) {
|
|
|
|
|
|
$existingAppointment = $record->surgeryAppointment;
|
|
|
|
|
|
|
2026-04-18 10:15:07 +00:00
|
|
|
|
$existingAppointment?->delete();
|
2026-04-17 16:45:38 +00:00
|
|
|
|
$record->unsetRelation('surgeryAppointment');
|
|
|
|
|
|
|
|
|
|
|
|
if ($existingAppointment) {
|
|
|
|
|
|
\App\Filament\Pages\SmsLogsPage::cancelAppointmentMessages(
|
|
|
|
|
|
$record->hand_phone ?? '',
|
|
|
|
|
|
$existingAppointment->surgery_date->toDateTimeString(),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
\Filament\Notifications\Notification::make()
|
|
|
|
|
|
->title(__('patients.actions.appointment_deleted'))
|
|
|
|
|
|
->success()
|
|
|
|
|
|
->send();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$dateStr = $data['surgery_date'];
|
|
|
|
|
|
$timeStr = $data['surgery_time'] ?? '00:00';
|
|
|
|
|
|
$surgeryDateTime = Carbon::parse($dateStr . ' ' . $timeStr);
|
|
|
|
|
|
$jalaliDate = Jalalian::fromCarbon($surgeryDateTime)->format('Y/m/d');
|
|
|
|
|
|
|
|
|
|
|
|
$apiKey = \App\Models\OsurgInitial::val('sms_api_key', config('sms.api_key'));
|
|
|
|
|
|
$apiUrl = \App\Models\OsurgInitial::val('sms_api_url', config('sms.api_url'));
|
|
|
|
|
|
$smsPayload = [
|
|
|
|
|
|
'phoneNumber' => $record->hand_phone ?? '',
|
|
|
|
|
|
'patientName' => $record->full_name,
|
|
|
|
|
|
'surgeryDate' => $jalaliDate,
|
|
|
|
|
|
'patientId' => $record->id,
|
|
|
|
|
|
'surgeryDateFull' => $surgeryDateTime->toDateTimeString(),
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// مرحله ۱: ارسال پیامک اولیه
|
|
|
|
|
|
try {
|
|
|
|
|
|
$response = Http::timeout(30)
|
|
|
|
|
|
->withHeader('X-API-Key', $apiKey)
|
|
|
|
|
|
->asJson()
|
|
|
|
|
|
->post($apiUrl . '/surgery-reminder', $smsPayload);
|
|
|
|
|
|
|
|
|
|
|
|
if (! ($response->json('success') ?? false)) {
|
|
|
|
|
|
\Filament\Notifications\Notification::make()
|
|
|
|
|
|
->title(__('patients.actions.api_failed'))
|
|
|
|
|
|
->body($response->json('message') ?? '')
|
|
|
|
|
|
->danger()
|
|
|
|
|
|
->send();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
|
\Filament\Notifications\Notification::make()
|
|
|
|
|
|
->title(__('patients.actions.api_failed'))
|
|
|
|
|
|
->body($e->getMessage())
|
|
|
|
|
|
->danger()
|
|
|
|
|
|
->send();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$appointment = SurgeryAppointment::updateOrCreate(
|
|
|
|
|
|
['patient_id' => $record->id],
|
|
|
|
|
|
[
|
|
|
|
|
|
'surgery_date' => $surgeryDateTime,
|
|
|
|
|
|
'surgery_center_id' => $data['surgery_center_id'],
|
|
|
|
|
|
]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
$record->unsetRelation('surgeryAppointment');
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
Http::timeout(30)
|
|
|
|
|
|
->withHeader('X-API-Key', $apiKey)
|
|
|
|
|
|
->asJson()
|
|
|
|
|
|
->post($apiUrl . '/surgery-reminder', array_merge($smsPayload, [
|
|
|
|
|
|
'appointmentId' => $appointment->id,
|
|
|
|
|
|
]));
|
|
|
|
|
|
} catch (\Throwable) {
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
\Filament\Notifications\Notification::make()
|
|
|
|
|
|
->title(__('patients.actions.appointment_saved'))
|
|
|
|
|
|
->body(__('patients.actions.sms_sent'))
|
|
|
|
|
|
->success()
|
|
|
|
|
|
->send();
|
|
|
|
|
|
}),
|
|
|
|
|
|
|
2026-04-03 13:55:17 +00:00
|
|
|
|
Action::make('printPrescription')
|
|
|
|
|
|
->label(__('prescriptions.actions.print_prescription'))
|
|
|
|
|
|
->icon('heroicon-o-printer')
|
|
|
|
|
|
->color('info')
|
|
|
|
|
|
->visible(fn () => $this->getRecord()->prescriptions()->exists())
|
|
|
|
|
|
->url(fn () => route('prescription.print', $this->getRecord()->prescriptions()->latest()->first()))
|
|
|
|
|
|
->openUrlInNewTab(),
|
|
|
|
|
|
|
|
|
|
|
|
Action::make('printAdmission')
|
|
|
|
|
|
->label(__('prescriptions.actions.print_admission'))
|
|
|
|
|
|
->icon('heroicon-o-printer')
|
|
|
|
|
|
->color('warning')
|
|
|
|
|
|
->visible(fn () => $this->getRecord()->surgeryAppointment !== null)
|
|
|
|
|
|
->url(fn () => route('surgery-appointment.print.admission', $this->getRecord()))
|
|
|
|
|
|
->openUrlInNewTab(),
|
|
|
|
|
|
|
|
|
|
|
|
Action::make('printLab')
|
|
|
|
|
|
->label(__('prescriptions.actions.print_lab'))
|
|
|
|
|
|
->icon('heroicon-o-beaker')
|
|
|
|
|
|
->color('gray')
|
|
|
|
|
|
->url(fn () => route('patient.print.lab', $this->getRecord()))
|
|
|
|
|
|
->openUrlInNewTab(),
|
|
|
|
|
|
|
|
|
|
|
|
DeleteAction::make(),
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-17 16:45:38 +00:00
|
|
|
|
protected function mutateFormDataBeforeFill(array $data): array
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (['birth_date'] as $field) {
|
|
|
|
|
|
if (($data[$field] ?? null) === '') {
|
|
|
|
|
|
$data[$field] = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach (['surgery_before', 'sergery_after'] as $field) {
|
|
|
|
|
|
if (($data[$field] ?? null) === '') {
|
|
|
|
|
|
$data[$field] = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach (['current_illness_1', 'current_illness_2', 'has_alergyto'] as $field) {
|
|
|
|
|
|
if (is_null($data[$field] ?? null)) {
|
|
|
|
|
|
$data[$field] = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach (['photos_before', 'photos_after', 'videos', 'audio_files'] as $field) {
|
|
|
|
|
|
$data[$field] = static::normalizeFileList($data[$field] ?? null);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function normalizeFileList(mixed $value): array
|
|
|
|
|
|
{
|
|
|
|
|
|
if (empty($value)) {
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (is_string($value)) {
|
|
|
|
|
|
$decoded = json_decode($value, true);
|
|
|
|
|
|
$value = is_array($decoded) ? $decoded : [$value];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return collect((array) $value)
|
|
|
|
|
|
->map(function ($item) {
|
|
|
|
|
|
if (is_array($item) && isset($item['name'])) {
|
|
|
|
|
|
return $item['name'];
|
|
|
|
|
|
}
|
|
|
|
|
|
if (is_string($item) && $item !== '') {
|
|
|
|
|
|
return $item;
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
})
|
|
|
|
|
|
->filter()
|
|
|
|
|
|
->values()
|
|
|
|
|
|
->toArray();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-03 13:55:17 +00:00
|
|
|
|
protected function getFormActions(): array
|
|
|
|
|
|
{
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|