['except' => ''], 'current_tab' => ['except' => 'active'], 'filter_doctor' => ['except' => ''] ]; public function mount() { // Default ke tanggal hari ini jika tidak ada di URL $this->search_date = $this->search_date ?? date('Y-m-d'); } public function updatedSearchDate() { $this->resetPage(); } public function updatedFilterDoctor() { $this->resetPage(); } public function updatedCurrentTab() { $this->resetPage(); } /** * Fungsi Utama Update Status & Trigger Suara */ public function updateStatus($id, $status) { $appointment = Appointment::with(['patient', 'doctor', 'serviceCategory'])->findOrFail($id); // LOGIKA KHUSUS JIKA STATUS 'CALLING' if ($status === 'calling') { // Matikan pasien lain yang sedang 'calling' di dokter yang sama agar tidak bentrok Appointment::where('doctor_id', $appointment->doctor_id) ->whereDate('appointment_date', $this->search_date) ->where('status', 'calling') ->where('id', '!=', $id) ->update(['status' => 'waiting']); // Kirim event suara ke Browser $this->dispatch( 'play-calling-sound', name: $appointment->patient->name, number: $appointment->queue_number, poli: $appointment->serviceCategory->name ?? 'Ruang Pemeriksaan', doctor: $appointment->doctor->name ?? '' ); } $appointment->update(['status' => $status]); $message = match ($status) { 'calling' => 'Pasien dipanggil ke ruang periksa.', 'finished' => 'Pemeriksaan telah selesai.', 'cancelled' => 'Antrean berhasil dibatalkan.', 'skipped' => 'Pasien ditandai terlambat.', default => 'Status berhasil diperbarui.' }; Toaster::success($message); } /** * Panggil Ulang Pasien (Hanya trigger suara) */ public function recall($id) { $appointment = Appointment::with(['patient', 'doctor', 'serviceCategory'])->findOrFail($id); $this->dispatch( 'play-calling-sound', name: $appointment->patient->name, number: $appointment->queue_number, poli: $appointment->serviceCategory->name ?? 'Ruang Pemeriksaan', doctor: $appointment->doctor->name ?? '' ); Toaster::info("Memanggil ulang antrean nomor #{$appointment->queue_number}"); } /** * Tandai Dilewati dan Langsung Panggil Berikutnya */ public function skipAndNext($id) { $appointment = Appointment::findOrFail($id); $appointment->update(['status' => 'skipped']); Toaster::warning("Pasien #{$appointment->queue_number} ditandai dilewati."); $this->callNext(); } /** * Kembalikan Status (Undo) */ public function undoStatus($id) { $appointment = Appointment::findOrFail($id); $appointment->update(['status' => 'waiting']); Toaster::info("Antrean #{$appointment->queue_number} dikembalikan ke status menunggu."); } /** * Panggil Pasien Berikutnya secara Otomatis */ public function callNext() { $query = Appointment::where('status', 'waiting') ->whereDate('appointment_date', $this->search_date); // Jika sedang filter dokter tertentu if (!empty($this->filter_doctor)) { $next = $query->where('doctor_id', $this->filter_doctor) ->orderBy('queue_number', 'asc') ->first(); } // Jika mode Global (Semua Dokter) else { $next = $query->orderBy('created_at', 'asc')->first(); } if ($next) { // Sinkronkan filter ke dokter pasien tersebut jika di mode global if (empty($this->filter_doctor)) { $this->filter_doctor = $next->doctor_id; } $this->updateStatus($next->id, 'calling'); Toaster::success("Memanggil berikutnya: #{$next->queue_number}"); } else { Toaster::error('Tidak ada pasien dalam antrean menunggu.'); } } /** * Panggil Berdasarkan Input Nomor Manual */ public function callSpecificNumber() { if (empty($this->target_number)) { Toaster::error('Masukkan nomor antrean.'); return; } $query = Appointment::where('queue_number', $this->target_number) ->whereDate('appointment_date', $this->search_date) ->whereIn('status', ['waiting', 'skipped']); if (!empty($this->filter_doctor)) { $query->where('doctor_id', $this->filter_doctor); } $results = $query->get(); if ($results->count() === 0) { Toaster::error("Nomor #{$this->target_number} tidak ditemukan."); } elseif ($results->count() > 1 && empty($this->filter_doctor)) { Toaster::warning("Ada beberapa nomor sama. Pilih dokter dahulu."); } else { $target = $results->first(); $this->filter_doctor = $target->doctor_id; $this->updateStatus($target->id, 'calling'); $this->target_number = ''; } } /** * Pindahkan Pasien ke Dokter/Poli Lain */ public function transferPatient($id, $newDoctorId) { $appointment = Appointment::findOrFail($id); // Cari nomor terakhir di dokter tujuan hari ini $lastQueue = Appointment::where('doctor_id', $newDoctorId) ->whereDate('appointment_date', $this->search_date) ->max('queue_number') ?? 0; $appointment->update([ 'doctor_id' => $newDoctorId, 'queue_number' => $lastQueue + 1, 'status' => 'waiting' ]); Toaster::success("Pasien berhasil dipindahkan ke Dokter/Poli tujuan."); } /** * Modal Tiket */ public function showTicketModal($id) { $this->selectedAppointment = Appointment::with(['patient', 'doctor', 'serviceCategory'])->findOrFail($id); $this->js("Flux.modal('ticket-modal').show()"); } /** * Walk-in Registration */ public function openWalkInModal() { $this->reset(['walkin_patient_id', 'walkin_doctor_id', 'walkin_service_id', 'walkin_notes']); if ($this->filter_doctor) $this->walkin_doctor_id = $this->filter_doctor; $this->js("Flux.modal('walkin-modal').show()"); } protected function validationAttributes() { return [ 'walkin_patient_id' => 'pasien', 'walkin_doctor_id' => 'dokter', 'walkin_service_id' => 'poli/layanan', ]; } public function saveWalkIn() { // 1. Validasi pastikan ID tersebut ada di tabel patients $this->validate([ 'walkin_patient_id' => 'required|exists:patients,id', 'walkin_doctor_id' => 'required|exists:doctors,id', 'walkin_service_id' => 'required|exists:service_categories,id', ]); // 2. AMBIL DATA PASIEN UNTUK MENDAPATKAN USER_ID NYA $patientData = \App\Models\Patient::with('user')->find($this->walkin_patient_id); if (!$patientData || !$patientData->user_id) { Toaster::error('Gagal memproses data pengguna pasien.'); return; } $lastQueue = Appointment::where('doctor_id', $this->walkin_doctor_id) ->whereDate('appointment_date', $this->search_date) ->max('queue_number') ?? 0; // 3. Simpan appointment menggunakan USER_ID (karena database Anda mendefinisikan patient_id sebagai ID User) $appointment = Appointment::create([ 'patient_id' => $patientData->user_id, // Gunakan user_id dari tabel patients 'doctor_id' => $this->walkin_doctor_id, 'service_category_id' => $this->walkin_service_id, 'appointment_date' => $this->search_date, 'queue_number' => $lastQueue + 1, 'notes' => $this->walkin_notes, 'status' => 'waiting', 'created_by' => auth()->id(), ]); // Ambil data lengkap termasuk relasi untuk dikirim ke JS cetak $this->dispatch('print-after-save', data: [ 'number' => str_pad($appointment->queue_number, 2, '0', STR_PAD_LEFT), 'name' => $patientData->user->name ?? 'Tanpa Nama', 'poli' => $appointment->serviceCategory->name ?? 'UMUM', 'doctor' => $appointment->doctor->name ?? '-', 'date' => $appointment->created_at->translatedFormat('d M Y, H:i') . ' WIB', ]); $this->js("Flux.modal('walkin-modal').close()"); Toaster::success('Pendaftaran Walk-in berhasil.'); $this->reset(['walkin_patient_id', 'walkin_notes']); } public function render() { // Query Dasar berdasarkan Tanggal dan Filter Dokter $baseQuery = Appointment::with(['patient', 'doctor', 'serviceCategory']) ->whereDate('appointment_date', $this->search_date) ->when($this->filter_doctor, function ($q) { return $q->where('doctor_id', $this->filter_doctor); }); // Filter Tabel berdasarkan Tab Aktif $tableQuery = (clone $baseQuery); if ($this->current_tab === 'active') { $tableQuery->whereIn('status', ['waiting', 'calling', 'skipped']); } else { $tableQuery->where('status', $this->current_tab); } return view('livewire.admin.manage-antrean', [ 'appointments' => $tableQuery->orderBy('queue_number', 'asc')->paginate(10), 'currentCalling' => (clone $baseQuery)->where('status', 'calling')->first(), 'stats' => [ 'total' => (clone $baseQuery)->count(), 'waiting' => (clone $baseQuery)->whereIn('status', ['waiting', 'skipped'])->count(), 'finished' => (clone $baseQuery)->where('status', 'finished')->count(), ], 'doctors' => Doctor::all(), 'services' => ServiceCategory::all(), 'patients' => strlen($this->search_patient_name) >= 3 ? User::query() ->has('patient') // Hanya user yang sudah punya data di tabel patients ->with('patient') ->where(function ($q) { $q->where('name', 'like', '%' . $this->search_patient_name . '%') ->orWhereHas('patient', function ($query) { $query->where('nik', 'like', '%' . $this->search_patient_name . '%'); }); }) ->limit(10) ->get() : [], ]); } public function startMedicalRecord($appointmentId) { // 1. Ambil data janji temu beserta relasinya $this->currentExamining = Appointment::with(['doctor', 'serviceCategory'])->find($appointmentId); if ($this->currentExamining) { // 2. Karena 'patient_id' di appointments adalah ID USER, kita langsung cari nama user-nya di sini $userPasien = \App\Models\User::find($this->currentExamining->patient_id); // 3. Kita simpan nama aslinya ke dalam properti baru agar bisa dipanggil dengan aman di Blade $this->currentPatientName = $userPasien ? $userPasien->name : 'Tidak Diketahui'; // Set form default $this->symptoms = $this->currentExamining->notes; $this->diagnosis = ''; $this->treatment = ''; $this->doctor_notes = ''; // Munculkan Modal $this->js("Flux.modal('medical-record-modal').show()"); } } public function saveMedicalRecord() { $this->validate([ 'symptoms' => 'required', 'diagnosis' => 'required', ]); $actualPatient = \App\Models\Patient::where('user_id', $this->currentExamining->patient_id)->first(); if (! $actualPatient) { session()->flash('error', 'Data Pasien tidak ditemukan di sistem.'); return; } // Simpan ke database dengan ID Patient yang benar MedicalRecord::create([ 'patient_id' => $actualPatient->id, // Sekarang menggunakan ID Patient asli (Bukan 52 lagi) 'appointment_id' => $this->currentExamining->id, 'doctor_id' => $this->currentExamining->doctor_id, 'symptoms' => $this->symptoms, 'diagnosis' => $this->diagnosis, 'treatment' => $this->treatment, 'notes' => $this->doctor_notes, ]); // Update status antrean menjadi Selesai (finished) $this->currentExamining->update([ 'status' => 'finished' ]); $this->js("Flux.modal('medical-record-modal').close()"); $this->reset(['currentExamining', 'symptoms', 'diagnosis', 'treatment', 'doctor_notes']); session()->flash('message', 'Pemeriksaan selesai dan Rekam Medis berhasil disimpan.'); } }