si-klinik/sk-klinik.my.id/resources/views/livewire/patient/booking-antrean.blade.php

242 lines
12 KiB
PHP

<div class="space-y-6 mx-auto" wire:poll.5s>
{{-- Header --}}
<div class="flex md:flex-row flex-col justify-between items-start md:items-center gap-4">
<div class="flex flex-col gap-1">
<flux:heading size="xl">Booking & Monitor Antrean Klinik</flux:heading>
<flux:subheading>Pantau antrean berjalan dan ambil nomor antrean Anda.</flux:subheading>
</div>
{{-- Filter untuk Monitor --}}
<div class="w-full md:w-64">
<flux:select wire:model.live="filter_doctor" placeholder="Semua Dokter (Umum)">
<flux:select.option value="">Semua Dokter</flux:select.option>
@foreach (\App\Models\Doctor::all() as $doc)
<flux:select.option value="{{ $doc->id }}">{{ $doc->name }}</flux:select.option>
@endforeach
</flux:select>
</div>
</div>
<flux:separator variant="subtle" />
{{-- MONITOR ANTREAN SEKARANG --}}
<div class="relative bg-zinc-900 shadow-2xl p-6 border border-zinc-800 rounded-[2rem] overflow-hidden text-white">
<div class="flex md:flex-row flex-col justify-between items-center gap-6">
<div class="flex items-center gap-4">
<div class="bg-blue-600 shadow-blue-600/20 shadow-lg p-3 rounded-2xl">
<flux:icon.megaphone variant="solid" class="size-8 text-white" />
</div>
<div>
<h3 class="font-black text-zinc-400 text-xs uppercase tracking-widest">Dipanggil Sekarang</h3>
<p class="font-bold text-blue-400 text-xl">
{{ $currentCalling ? $currentCalling->doctor->name : 'Monitor Antrean' }}
</p>
<p class="text-[10px] text-zinc-500 uppercase">
{{ $currentCalling ? $currentCalling->serviceCategory->name : 'Silakan pilih dokter untuk monitor spesifik' }}
</p>
</div>
</div>
<div class="flex items-center gap-8">
<div class="text-center">
{{-- ID: calling-number untuk dideteksi script --}}
<div id="calling-number"
class="font-black tabular-nums text-blue-500 text-6xl md:text-8xl tracking-tighter">
{{ $currentCalling ? str_pad($currentCalling->queue_number, 2, '0', STR_PAD_LEFT) : '--' }}
</div>
</div>
<div class="hidden md:block bg-zinc-800 w-[1px] h-16"></div>
<div class="text-left">
@if ($currentCalling)
<p class="font-bold text-zinc-400 text-xs uppercase">Nama Pasien</p>
{{-- ID: calling-name untuk dibaca script --}}
<p id="calling-name" class="font-black text-white text-2xl capitalize leading-tight">
{{ Str::limit($currentCalling->patient->name, 20) }}
</p>
@else
<p class="font-medium text-zinc-500 italic">Belum ada pemanggilan</p>
@endif
</div>
</div>
<div class="hidden lg:block bg-zinc-800/50 px-4 py-2 border border-zinc-700 rounded-xl text-center">
<p class="font-bold text-[10px] text-zinc-500 uppercase tracking-tighter">Waktu</p>
<p class="font-bold tabular-nums">{{ date('H:i') }} WIB</p>
<button onclick="checkVoice()" class="block mt-1 text-[9px] text-blue-400 hover:underline">Cek
Speaker</button>
</div>
</div>
</div>
<div class="gap-6 grid grid-cols-1 lg:grid-cols-12">
{{-- Sisi Kiri: Form Booking --}}
<div class="lg:col-span-4">
<flux:card class="space-y-6">
<div>
<flux:heading size="lg">Buat Janji Temu</flux:heading>
<flux:subheading>Lengkapi data di bawah ini.</flux:subheading>
</div>
<form wire:submit.prevent="save" class="space-y-6">
<flux:input wire:model="appointment_date" type="date" label="Tanggal Kunjungan"
icon="calendar" />
<flux:select wire:model="service_category_id" label="Pilih Layanan/Poli">
<flux:select.option value="">Pilih Poli...</flux:select.option>
@foreach ($categories as $cat)
<flux:select.option value="{{ $cat->id }}">{{ $cat->name }}</flux:select.option>
@endforeach
</flux:select>
<flux:select wire:model="doctor_id" label="Pilih Dokter">
<flux:select.option value="">Pilih Dokter...</flux:select.option>
@foreach ($doctors as $doc)
<flux:select.option value="{{ $doc->id }}">{{ $doc->name }}</flux:select.option>
@endforeach
</flux:select>
<flux:textarea wire:model="notes" label="Keluhan Singkat"
placeholder="Contoh: Sakit gigi geraham..." />
<flux:button type="submit" variant="primary" class="w-full" icon="ticket">Ambil Antrean
</flux:button>
</form>
</flux:card>
</div>
{{-- Sisi Kanan: Daftar Antrean Saya --}}
<div class="lg:col-span-8">
<flux:card>
<div class="mb-4">
<flux:heading size="lg">Riwayat Antrean Saya</flux:heading>
<flux:subheading>Daftar antrean Anda yang aktif maupun yang sudah selesai.</flux:subheading>
</div>
<flux:table>
<flux:table.columns>
<flux:table.column>No. Antre</flux:table.column>
<flux:table.column>Tanggal</flux:table.column>
<flux:table.column>Status</flux:table.column>
<flux:table.column>Keluhan Singkat</flux:table.column>
<flux:table.column align="end">Aksi</flux:table.column>
</flux:table.columns>
<flux:table.rows>
@forelse ($myAppointments as $app)
<flux:table.row :key="$app->id">
<flux:table.cell>
<flux:badge color="blue" variant="flat" class="px-3 py-1 font-bold text-lg">
#{{ $app->queue_number }}
</flux:badge>
</flux:table.cell>
<flux:table.cell>
<span
class="font-medium text-zinc-800 dark:text-white">{{ $app->appointment_date->translatedFormat('d F Y') }}</span>
</flux:table.cell>
<flux:table.cell>
@php
$statusConfig = match ($app->status) {
'waiting' => ['color' => 'yellow', 'label' => 'Menunggu'],
'calling' => ['color' => 'blue', 'label' => 'Dipanggil'],
'finished' => ['color' => 'green', 'label' => 'Selesai'],
'cancelled' => ['color' => 'red', 'label' => 'Batal'],
default => ['color' => 'zinc', 'label' => $app->status],
};
@endphp
<flux:badge color="{{ $statusConfig['color'] }}" size="sm" variant="flat">
{{ strtoupper($statusConfig['label']) }}
</flux:badge>
</flux:table.cell>
<flux:table.cell>
<span class="text-zinc-500 text-sm italic">{{ Str::limit($app->notes, 30) }}</span>
</flux:table.cell>
<flux:table.cell align="end">
<flux:button wire:click="showTicketModal({{ $app->id }})" variant="ghost"
icon="eye" size="sm" />
</flux:table.cell>
</flux:table.row>
@empty
<flux:table.row>
<flux:table.cell colspan="4" class="py-12 text-zinc-400 text-center">Belum ada data
antrean.</flux:table.cell>
</flux:table.row>
@endforelse
</flux:table.rows>
</flux:table>
</flux:card>
</div>
</div>
{{-- MODAL KARTU ANTREAN (Tetap sama) --}}
<flux:modal name="ticket-modal" class="!p-0 md:min-w-[400px] overflow-hidden">
@if ($selectedAppointment)
<div class="relative" id="printable-ticket">
<div class="bg-blue-600 p-6 text-white text-center">
<flux:heading size="xl" class="!font-black text-white uppercase tracking-widest">
{{ $site->name ?? 'KLINIK' }}
</flux:heading>
<flux:subheading class="text-white">Bukti Antrean Digital</flux:subheading>
</div>
<div class="space-y-6 bg-white dark:bg-zinc-900 p-6 pt-8">
<div class="space-y-2 text-center">
<span class="font-bold text-zinc-400 text-xs uppercase tracking-[0.2em]">Nomor Antrean</span>
<div class="font-black text-zinc-800 dark:text-white text-7xl tracking-tighter">
{{ str_pad($selectedAppointment->queue_number, 3, '0', STR_PAD_LEFT) }}
</div>
</div>
<div class="flex gap-2">
<flux:button variant="ghost" class="flex-1" x-on:click="Flux.modal('ticket-modal').close()">
Tutup</flux:button>
</div>
</div>
</div>
@endif
</flux:modal>
{{-- SCRIPT SUARA PANGGILAN --}}
<script>
let lastCalledNumber = null;
function checkVoice() {
const msg = new SpeechSynthesisUtterance("Sistem pemanggilan aktif");
msg.lang = 'id-ID';
window.speechSynthesis.speak(msg);
}
document.addEventListener('livewire:initialized', () => {
// Monitor perubahan setiap 1 detik
setInterval(() => {
const numElem = document.getElementById('calling-number');
const nameElem = document.getElementById('calling-name');
if (numElem) {
const activeNumber = numElem.innerText.trim();
const activeName = nameElem ? nameElem.innerText.trim() : "";
// Trigger hanya jika nomor berubah dan bukan kosong (--)
if (activeNumber !== '--' && activeNumber !== lastCalledNumber) {
lastCalledNumber = activeNumber;
speakQueue(activeNumber, activeName);
}
}
}, 1000);
});
function speakQueue(number, name) {
// Hapus antrean suara sebelumnya agar tidak menumpuk
window.speechSynthesis.cancel();
const text = `Nomor antrean ${number}. Atas nama ${name}. Silakan menuju ruang pemeriksaan.`;
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = 'id-ID';
utterance.rate = 0.85; // Sedikit diperlambat agar artikulasi jelas
utterance.pitch = 1;
utterance.volume = 1;
window.speechSynthesis.speak(utterance);
}
</script>
</div>