385 lines
19 KiB
PHP
385 lines
19 KiB
PHP
<div class="mx-auto">
|
|
<div class="flex justify-between items-center mb-8">
|
|
<div>
|
|
<flux:heading size="xl">Database Pasien</flux:heading>
|
|
<flux:subheading>Daftar seluruh pasien yang telah melengkapi profil medis.</flux:subheading>
|
|
</div>
|
|
<flux:button wire:click="create" variant="primary" icon="plus">Tambah Pasien</flux:button>
|
|
</div>
|
|
|
|
<div class="space-y-4">
|
|
{{-- Filter Area --}}
|
|
<div class="flex md:flex-row flex-col gap-4 w-full">
|
|
<div class="flex flex-1 gap-2">
|
|
<div class="flex-1">
|
|
<flux:input wire:model.live.debounce.300ms="search" placeholder="Cari Nama, NIK atau No. RM..."
|
|
icon="magnifying-glass" class="w-full" />
|
|
</div>
|
|
<flux:button icon="camera" x-on:click="startScanner()">Scan Kartu</flux:button>
|
|
</div>
|
|
|
|
<div class="w-full md:w-64">
|
|
<flux:select wire:model.live="filterInsurance" icon="funnel">
|
|
<option value="">Semua Penjamin</option>
|
|
<option value="Umum">Umum (Mandiri)</option>
|
|
<option value="BPJS">BPJS Kesehatan</option>
|
|
<option value="Asuransi Swasta">Asuransi Swasta</option>
|
|
</flux:select>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Table --}}
|
|
<flux:table>
|
|
<flux:table.columns>
|
|
<flux:table.column>Pasien</flux:table.column>
|
|
<flux:table.column>No. RM</flux:table.column>
|
|
<flux:table.column>NIK</flux:table.column>
|
|
<flux:table.column>Kontak</flux:table.column>
|
|
<flux:table.column>Penjamin</flux:table.column>
|
|
<flux:table.column align="end">Aksi</flux:table.column>
|
|
</flux:table.columns>
|
|
|
|
<flux:table.rows>
|
|
@forelse ($patients as $patient)
|
|
<flux:table.row :key="$patient->id">
|
|
<flux:table.cell>
|
|
<div class="flex items-center gap-3">
|
|
<flux:avatar initials="{{ $patient->user->initials() }}" size="sm" />
|
|
<div>
|
|
<div class="font-medium text-zinc-800 dark:text-white">{{ $patient->user->name }}
|
|
</div>
|
|
<div class="text-zinc-500 text-xs">
|
|
{{ $patient->gender == 'L' ? 'Laki-laki' : 'Perempuan' }}</div>
|
|
</div>
|
|
</div>
|
|
</flux:table.cell>
|
|
|
|
<flux:table.cell class="font-mono text-xs">{{ $patient->medical_record_number }}</flux:table.cell>
|
|
|
|
<flux:table.cell class="font-mono text-xs">{{ $patient->nik }}</flux:table.cell>
|
|
|
|
<flux:table.cell>
|
|
<div class="text-sm">{{ $patient->phone }}</div>
|
|
<div class="max-w-[150px] text-zinc-500 text-xs truncate">{{ $patient->address }}</div>
|
|
</flux:table.cell>
|
|
|
|
<flux:table.cell>
|
|
<flux:badge size="sm"
|
|
:color="match($patient->insurance_type) { 'BPJS' => 'green', 'Asuransi Swasta' => 'indigo', default => 'zinc' }">
|
|
{{ $patient->insurance_type }}
|
|
</flux:badge>
|
|
</flux:table.cell>
|
|
|
|
<flux:table.cell align="end">
|
|
<div class="flex justify-end gap-2">
|
|
<flux:button href="{{ route('admin.patients.history', $patient->id) }}" icon:trailing="arrow-up-right">
|
|
Lihat Riwayat
|
|
</flux:button>
|
|
<flux:button wire:click="showDetail({{ $patient->id }})" variant="ghost" icon="eye"
|
|
size="sm" />
|
|
<flux:button wire:click="edit({{ $patient->id }})" variant="ghost" icon="pencil-square"
|
|
size="sm" />
|
|
<flux:button
|
|
x-on:click="confirmDelete({{ $patient->id }}, '{{ $patient->user->name }}')"
|
|
variant="ghost" icon="trash" size="sm" class="text-red-500" />
|
|
</div>
|
|
</flux:table.cell>
|
|
</flux:table.row>
|
|
@empty
|
|
<flux:table.row>
|
|
<flux:table.cell colspan="6" align="center">
|
|
<div class="py-6 text-zinc-500">Tidak ada data pasien.</div>
|
|
</flux:table.cell>
|
|
</flux:table.row>
|
|
@endforelse
|
|
</flux:table.rows>
|
|
</flux:table>
|
|
|
|
<div class="mt-4">{{ $patients->links() }}</div>
|
|
</div>
|
|
|
|
{{-- MODAL DETAIL --}}
|
|
<flux:modal name="patient-detail" class="p-0 md:w-[650px] overflow-hidden">
|
|
@if ($selectedPatient)
|
|
<div class="bg-zinc-50 dark:bg-zinc-900 px-6 py-8 border-zinc-200 dark:border-zinc-800 border-b">
|
|
<div class="flex items-center gap-5">
|
|
<flux:avatar initials="{{ $selectedPatient->user->initials() }}" class="size-20" />
|
|
<div>
|
|
<flux:heading size="xl">{{ $selectedPatient->user->name }}</flux:heading>
|
|
<div class="flex gap-2 mt-2">
|
|
<flux:badge size="sm" color="zinc">{{ $selectedPatient->nik }}</flux:badge>
|
|
<flux:badge size="sm"
|
|
:color="$selectedPatient->gender === 'L' ? 'blue' : 'secondary'">
|
|
{{ $selectedPatient->gender === 'L' ? 'Laki-laki' : 'Perempuan' }}
|
|
</flux:badge>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="space-y-6 p-6">
|
|
<div class="gap-4 grid grid-cols-3">
|
|
<div class="space-y-1">
|
|
<p class="font-semibold text-zinc-500 text-xs uppercase">Gol. Darah</p>
|
|
<flux:badge size="sm" color="zinc">{{ $selectedPatient->blood_type ?? '-' }}
|
|
</flux:badge>
|
|
</div>
|
|
<div class="space-y-1">
|
|
<p class="font-semibold text-zinc-500 text-xs uppercase">Tgl Lahir</p>
|
|
<p class="font-medium text-sm">
|
|
{{ \Carbon\Carbon::parse($selectedPatient->date_of_birth)->format('d/m/Y') }}</p>
|
|
</div>
|
|
<div class="space-y-1">
|
|
<p class="font-semibold text-zinc-500 text-xs uppercase">Penjamin</p>
|
|
<flux:badge size="sm" color="indigo">{{ $selectedPatient->insurance_type }}</flux:badge>
|
|
</div>
|
|
</div>
|
|
<div class="space-y-1">
|
|
<p class="font-semibold text-zinc-500 text-xs uppercase">Alamat</p>
|
|
<p class="bg-zinc-50 dark:bg-zinc-800 p-3 border rounded-lg text-sm leading-relaxed">
|
|
{{ $selectedPatient->address }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="flex justify-end gap-2 bg-zinc-50/50 p-6 border-t">
|
|
<flux:modal.close>
|
|
<flux:button variant="ghost">Tutup</flux:button>
|
|
</flux:modal.close>
|
|
{{-- Tombol Cetak diarahkan ke route cetak yang kamu miliki --}}
|
|
<flux:button variant="primary" icon="printer" tag="a" target="_blank"
|
|
href="{{ route('admin.patients.print', $selectedPatient->id) }}">
|
|
Cetak Kartu
|
|
</flux:button>
|
|
</div>
|
|
@endif
|
|
</flux:modal>
|
|
|
|
{{-- MODAL EDIT --}}
|
|
<flux:modal name="edit-patient" class="space-y-6 md:w-[700px]">
|
|
<div>
|
|
<flux:heading size="lg">Edit Profil Pasien Lengkap</flux:heading>
|
|
<flux:subheading>Pastikan informasi medis dan kontak darurat sudah sesuai.</flux:subheading>
|
|
</div>
|
|
|
|
<form wire:submit="update" class="space-y-6">
|
|
{{-- Section 1: Identitas Dasar --}}
|
|
<div class="gap-4 grid grid-cols-1 md:grid-cols-2">
|
|
<flux:input label="Nama Lengkap" wire:model="editForm.name" class="md:col-span-2" />
|
|
|
|
<flux:input label="NIK" wire:model="editForm.nik" />
|
|
<flux:select label="Jenis Kelamin" wire:model="editForm.gender">
|
|
<option value="L">Laki-laki</option>
|
|
<option value="P">Perempuan</option>
|
|
</flux:select>
|
|
|
|
<flux:input label="Tempat Lahir" wire:model="editForm.place_of_birth" />
|
|
<flux:input type="date" label="Tanggal Lahir" wire:model="editForm.date_of_birth" />
|
|
</div>
|
|
|
|
<hr class="border-zinc-200 dark:border-zinc-800" />
|
|
|
|
{{-- Section 2: Kontak & Alamat --}}
|
|
<div class="gap-4 grid grid-cols-1 md:grid-cols-2">
|
|
<flux:input label="Nomor Telepon" wire:model="editForm.phone" />
|
|
<flux:select label="Golongan Darah" wire:model="editForm.blood_type">
|
|
<option value="-">-</option>
|
|
<option value="A">A</option>
|
|
<option value="B">B</option>
|
|
<option value="AB">AB</option>
|
|
<option value="O">O</option>
|
|
</flux:select>
|
|
<flux:textarea label="Alamat Domisili" wire:model="editForm.address" class="md:col-span-2"
|
|
rows="2" />
|
|
</div>
|
|
|
|
<div class="space-y-4 bg-orange-50 dark:bg-orange-900/20 p-4 rounded-xl">
|
|
<p class="font-bold text-orange-700 dark:text-orange-400 text-sm">Kontak Darurat</p>
|
|
<div class="gap-4 grid grid-cols-1 md:grid-cols-3">
|
|
<flux:input label="Nama" wire:model="editForm.emergency_contact_name" />
|
|
<flux:input label="Hubungan" wire:model="editForm.emergency_contact_relation" />
|
|
<flux:input label="Nomor HP" wire:model="editForm.emergency_contact_phone" />
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Section 3: Asuransi --}}
|
|
<div class="gap-4 grid grid-cols-1 md:grid-cols-2">
|
|
<flux:select label="Metode Penjamin" wire:model="editForm.insurance_type">
|
|
<option value="Umum">Umum</option>
|
|
<option value="BPJS">BPJS</option>
|
|
<option value="Asuransi Swasta">Asuransi Swasta</option>
|
|
</flux:select>
|
|
<flux:input label="Nomor Kartu Asuransi" wire:model="editForm.insurance_number"
|
|
:disabled="$editForm['insurance_type'] === 'Umum'" />
|
|
</div>
|
|
|
|
<div class="flex justify-end gap-2 pt-4">
|
|
<flux:modal.close>
|
|
<flux:button variant="ghost">Batal</flux:button>
|
|
</flux:modal.close>
|
|
<flux:button type="submit" variant="primary">Simpan Perubahan Data</flux:button>
|
|
</div>
|
|
</form>
|
|
</flux:modal>
|
|
|
|
{{-- MODAL TAMBAH PASIEN --}}
|
|
<flux:modal name="add-patient" class="space-y-8 p-6 md:w-[800px] max-h-[90vh] overflow-y-auto">
|
|
<div>
|
|
<flux:heading size="xl">Registrasi Pasien Baru</flux:heading>
|
|
<flux:subheading>Input data medis lengkap untuk pendaftaran pasien baru.</flux:subheading>
|
|
</div>
|
|
|
|
<form wire:submit="store" class="space-y-8">
|
|
{{-- Seksi 1: Akun & Identitas Dasar --}}
|
|
<section class="space-y-4">
|
|
<flux:heading variant="subtle" class="font-bold text-xs uppercase tracking-wider">Akun & Identitas
|
|
Dasar</flux:heading>
|
|
<flux:separator variant="subtle" />
|
|
|
|
<div class="gap-4 grid grid-cols-1 md:grid-cols-2">
|
|
<flux:input wire:model="createForm.name" label="Nama Lengkap (Sesuai KTP)"
|
|
placeholder="Nama lengkap..." />
|
|
<flux:input wire:model="createForm.email" label="Alamat Email" type="email"
|
|
placeholder="email@pasien.com" />
|
|
|
|
<flux:input wire:model="createForm.nik" label="NIK" mask="9999999999999999"
|
|
placeholder="16 digit angka" />
|
|
|
|
<div class="gap-2 grid grid-cols-2">
|
|
<flux:select wire:model="createForm.gender" label="Jenis Kelamin">
|
|
<option value="">Pilih...</option>
|
|
<option value="L">Laki-laki</option>
|
|
<option value="P">Perempuan</option>
|
|
</flux:select>
|
|
<flux:select wire:model="createForm.blood_type" label="Gol. Darah">
|
|
<option value="">Pilih...</option>
|
|
<option value="A">A</option>
|
|
<option value="B">B</option>
|
|
<option value="AB">AB</option>
|
|
<option value="O">O</option>
|
|
<option value="-">-</option>
|
|
</flux:select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="gap-4 grid grid-cols-1 md:grid-cols-2">
|
|
<flux:input wire:model="createForm.place_of_birth" label="Tempat Lahir" />
|
|
<flux:input wire:model="createForm.date_of_birth" type="date" label="Tanggal Lahir" />
|
|
</div>
|
|
</section>
|
|
|
|
{{-- Seksi 2: Kontak --}}
|
|
<section class="space-y-4">
|
|
<flux:heading variant="subtle" class="font-bold text-xs uppercase tracking-wider">Informasi Kontak
|
|
</flux:heading>
|
|
<flux:separator variant="subtle" />
|
|
|
|
<flux:input wire:model="createForm.phone" label="Nomor Telepon/WhatsApp" icon="phone" />
|
|
<flux:textarea wire:model="createForm.address" label="Alamat Domisili" rows="2" />
|
|
</section>
|
|
|
|
{{-- Seksi 3: Kontak Darurat --}}
|
|
<section
|
|
class="space-y-4 bg-zinc-50 dark:bg-white/5 p-4 border border-zinc-200 dark:border-white/10 rounded-xl">
|
|
<flux:heading variant="subtle"
|
|
class="font-bold text-orange-600 dark:text-orange-400 text-xs uppercase tracking-wider">Kontak
|
|
Darurat</flux:heading>
|
|
|
|
<div class="gap-4 grid grid-cols-1 md:grid-cols-3">
|
|
<flux:input wire:model="createForm.emergency_contact_name" label="Nama Kontak" />
|
|
<flux:input wire:model="createForm.emergency_contact_relation" label="Hubungan"
|
|
placeholder="Misal: Istri" />
|
|
<flux:input wire:model="createForm.emergency_contact_phone" label="No. Telepon" />
|
|
</div>
|
|
</section>
|
|
|
|
{{-- Seksi 4: Penjamin --}}
|
|
<section class="space-y-4">
|
|
<flux:heading variant="subtle" class="font-bold text-xs uppercase tracking-wider">Metode Pembayaran /
|
|
Penjamin</flux:heading>
|
|
<flux:separator variant="subtle" />
|
|
|
|
<div class="gap-4 grid grid-cols-1 md:grid-cols-2">
|
|
<flux:select wire:model.live="createForm.insurance_type" label="Jenis Penjaminan">
|
|
<option value="Umum">Umum (Mandiri)</option>
|
|
<option value="BPJS">BPJS Kesehatan</option>
|
|
<option value="Asuransi Swasta">Asuransi Swasta</option>
|
|
</flux:select>
|
|
|
|
<div x-show="$wire.createForm.insurance_type !== 'Umum'">
|
|
<flux:input wire:model="createForm.insurance_number" label="Nomor Kartu Asuransi" />
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<div class="flex justify-end gap-3 pt-6 border-t">
|
|
<flux:modal.close>
|
|
<flux:button variant="ghost">Batal</flux:button>
|
|
</flux:modal.close>
|
|
<flux:button type="submit" variant="primary" icon="check">
|
|
Daftarkan Pasien
|
|
</flux:button>
|
|
</div>
|
|
</form>
|
|
</flux:modal>
|
|
|
|
{{-- MODAL SCANNER --}}
|
|
<flux:modal name="scanner-modal" class="space-y-4 md:w-[500px]">
|
|
<flux:heading size="lg">Scan Kartu</flux:heading>
|
|
<div id="reader" class="bg-black border rounded-xl aspect-square overflow-hidden"></div>
|
|
<flux:modal.close>
|
|
<flux:button variant="ghost" class="w-full" x-on:click="stopScanner()">Batal</flux:button>
|
|
</flux:modal.close>
|
|
</flux:modal>
|
|
</div>
|
|
|
|
@script
|
|
<script>
|
|
// SweetAlert Hapus
|
|
window.confirmDelete = (id, name) => {
|
|
Swal.fire({
|
|
title: 'Hapus Pasien?',
|
|
text: `Data ${name} akan dihapus permanen!`,
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#ef4444',
|
|
confirmButtonText: 'Ya, Hapus',
|
|
cancelButtonText: 'Batal'
|
|
}).then((res) => {
|
|
if (res.isConfirmed) $wire.delete(id)
|
|
});
|
|
}
|
|
|
|
// Scanner Logic
|
|
let html5QrCode;
|
|
window.startScanner = () => {
|
|
Flux.modal('scanner-modal').show();
|
|
html5QrCode = new window.Html5Qrcode("reader");
|
|
html5QrCode.start({
|
|
facingMode: "environment"
|
|
}, {
|
|
fps: 10,
|
|
qrbox: {
|
|
width: 250,
|
|
height: 250
|
|
}
|
|
},
|
|
(text) => {
|
|
// Logika pembersihan string jika QR berisi URL (contoh: https://rs.com/patient/123456)
|
|
const nik = text.includes('/') ? text.split('/').pop() : text;
|
|
$wire.set('search', nik);
|
|
stopScanner();
|
|
Flux.modal('scanner-modal').close();
|
|
}
|
|
).catch(err => {
|
|
console.error("Gagal menjalankan kamera", err);
|
|
alert("Kamera tidak dapat diakses.");
|
|
});
|
|
}
|
|
|
|
window.stopScanner = () => {
|
|
if (html5QrCode && html5QrCode.isScanning) {
|
|
html5QrCode.stop().then(() => html5QrCode.clear());
|
|
}
|
|
}
|
|
</script>
|
|
@endscript
|