242 lines
12 KiB
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>
|