final
This commit is contained in:
parent
47e63ba9ed
commit
89c7fc14b8
|
@ -49,7 +49,7 @@ public function dashboard()
|
|||
// Get recent antrian
|
||||
$antrianTerbaru = Antrian::with(['user', 'poli'])
|
||||
->orderBy('created_at', 'desc')
|
||||
->limit(10)
|
||||
->limit(3)
|
||||
->get();
|
||||
|
||||
return view('admin.dashboard', compact(
|
||||
|
@ -65,6 +65,56 @@ public function dashboard()
|
|||
));
|
||||
}
|
||||
|
||||
public function dashboardApi()
|
||||
{
|
||||
// Auto-batalkan antrian yang sudah dipanggil lebih dari 5 menit
|
||||
$this->autoBatalkanAntrianLama();
|
||||
|
||||
// Get counts for each poli
|
||||
$poliUmumCount = Antrian::whereHas('poli', function ($query) {
|
||||
$query->where('nama_poli', 'umum');
|
||||
})->where('status', 'menunggu')->count();
|
||||
|
||||
$poliGigiCount = Antrian::whereHas('poli', function ($query) {
|
||||
$query->where('nama_poli', 'gigi');
|
||||
})->where('status', 'menunggu')->count();
|
||||
|
||||
$poliJiwaCount = Antrian::whereHas('poli', function ($query) {
|
||||
$query->where('nama_poli', 'kesehatan jiwa');
|
||||
})->where('status', 'menunggu')->count();
|
||||
|
||||
$poliTradisionalCount = Antrian::whereHas('poli', function ($query) {
|
||||
$query->where('nama_poli', 'kesehatan tradisional');
|
||||
})->where('status', 'menunggu')->count();
|
||||
|
||||
// Get recent antrian
|
||||
$antrianTerbaru = Antrian::with(['user', 'poli'])
|
||||
->orderBy('created_at', 'desc')
|
||||
->limit(3)
|
||||
->get();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'poliUmumCount' => $poliUmumCount,
|
||||
'poliGigiCount' => $poliGigiCount,
|
||||
'poliJiwaCount' => $poliJiwaCount,
|
||||
'poliTradisionalCount' => $poliTradisionalCount,
|
||||
'antrianTerbaru' => $antrianTerbaru->map(function ($antrian) {
|
||||
return [
|
||||
'id' => $antrian->id,
|
||||
'no_antrian' => $antrian->no_antrian,
|
||||
'status' => $antrian->status,
|
||||
'poli_name' => $antrian->poli->nama_poli,
|
||||
'user_name' => $antrian->user->nama,
|
||||
'created_at' => $antrian->created_at->diffForHumans(),
|
||||
'updated_at' => $antrian->updated_at->diffForHumans(),
|
||||
];
|
||||
})
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function manageUsers(Request $request)
|
||||
{
|
||||
// Auto-batalkan antrian yang sudah dipanggil lebih dari 5 menit
|
||||
|
@ -91,10 +141,39 @@ public function manageUsers(Request $request)
|
|||
|
||||
public function showUser(User $user)
|
||||
{
|
||||
$user->load(['antrians.poli']);
|
||||
// Load user with antrians ordered by created_at desc to get the latest first
|
||||
$user->load([
|
||||
'antrians' => function ($query) {
|
||||
$query->with('poli')->orderBy('created_at', 'desc');
|
||||
}
|
||||
]);
|
||||
|
||||
return view('admin.users.show', compact('user'));
|
||||
}
|
||||
|
||||
public function getUserAntrianTerbaru(User $user)
|
||||
{
|
||||
// Get latest 5 antrians for the user
|
||||
$antrians = $user->antrians()
|
||||
->with('poli')
|
||||
->orderBy('created_at', 'desc')
|
||||
->take(5)
|
||||
->get();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'antrians' => $antrians->map(function ($antrian) {
|
||||
return [
|
||||
'id' => $antrian->id,
|
||||
'no_antrian' => $antrian->no_antrian,
|
||||
'poli_name' => $antrian->poli->nama_poli ?? 'N/A',
|
||||
'status' => $antrian->status,
|
||||
'created_at' => $antrian->created_at ? $antrian->created_at->format('d/m/Y H:i') : 'N/A'
|
||||
];
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
public function createUser()
|
||||
{
|
||||
return view('admin.users.create');
|
||||
|
@ -213,7 +292,12 @@ public function laporan(Request $request)
|
|||
|
||||
// Filter berdasarkan status
|
||||
if ($request->filled('status')) {
|
||||
$query->where('status', $request->status);
|
||||
$status = $request->status;
|
||||
// Convert 'sedang' to 'sedang_diperiksa' for database compatibility
|
||||
if ($status === 'sedang') {
|
||||
$status = 'sedang_diperiksa';
|
||||
}
|
||||
$query->where('status', $status);
|
||||
}
|
||||
|
||||
// Filter berdasarkan jenis kelamin
|
||||
|
@ -223,17 +307,23 @@ public function laporan(Request $request)
|
|||
});
|
||||
}
|
||||
|
||||
$antrian = $query->orderBy('created_at', 'desc')->get();
|
||||
// Clone query for statistics before pagination
|
||||
$statisticQuery = clone $query;
|
||||
$allData = $statisticQuery->get();
|
||||
|
||||
// Paginate the results
|
||||
$antrian = $query->orderBy('created_at', 'desc')->paginate(10);
|
||||
$polis = Poli::all();
|
||||
|
||||
// Statistik
|
||||
$totalAntrian = $antrian->count();
|
||||
$antrianSelesai = $antrian->where('status', 'selesai')->count();
|
||||
$antrianMenunggu = $antrian->where('status', 'menunggu')->count();
|
||||
$antrianDipanggil = $antrian->where('status', 'dipanggil')->count();
|
||||
$antrianSedang = $antrian->where('status', 'sedang')->count();
|
||||
// Statistik dari semua data (tidak hanya yang dipaginate)
|
||||
$totalAntrian = $allData->count();
|
||||
$antrianSelesai = $allData->where('status', 'selesai')->count();
|
||||
$antrianMenunggu = $allData->where('status', 'menunggu')->count();
|
||||
$antrianDipanggil = $allData->where('status', 'dipanggil')->count();
|
||||
$antrianSedang = $allData->where('status', 'sedang_diperiksa')->count();
|
||||
$antrianBatal = $allData->where('status', 'batal')->count();
|
||||
|
||||
return view('admin.laporan.index', compact('antrian', 'polis', 'totalAntrian', 'antrianSelesai', 'antrianMenunggu', 'antrianDipanggil', 'antrianSedang'));
|
||||
return view('admin.laporan.index', compact('antrian', 'polis', 'totalAntrian', 'antrianSelesai', 'antrianMenunggu', 'antrianDipanggil', 'antrianSedang', 'antrianBatal'));
|
||||
}
|
||||
|
||||
public function exportPDF(Request $request)
|
||||
|
@ -256,7 +346,11 @@ public function exportPDF(Request $request)
|
|||
|
||||
// Filter berdasarkan status
|
||||
if ($request->filled('status')) {
|
||||
$query->where('status', $request->status);
|
||||
$status = $request->status;
|
||||
if ($status === 'sedang') {
|
||||
$status = 'sedang_diperiksa';
|
||||
}
|
||||
$query->where('status', $status);
|
||||
}
|
||||
|
||||
// Filter berdasarkan jenis kelamin
|
||||
|
@ -273,7 +367,7 @@ public function exportPDF(Request $request)
|
|||
$antrianSelesai = $antrian->where('status', 'selesai')->count();
|
||||
$antrianMenunggu = $antrian->where('status', 'menunggu')->count();
|
||||
$antrianDipanggil = $antrian->where('status', 'dipanggil')->count();
|
||||
$antrianSedang = $antrian->where('status', 'sedang')->count();
|
||||
$antrianSedang = $antrian->where('status', 'sedang_diperiksa')->count();
|
||||
|
||||
$pdf = Pdf::loadView('admin.laporan.pdf', compact('antrian', 'totalAntrian', 'antrianSelesai', 'antrianMenunggu', 'antrianDipanggil', 'antrianSedang'));
|
||||
return $pdf->download('laporan-antrian-' . date('Y-m-d') . '.pdf');
|
||||
|
@ -299,7 +393,11 @@ public function exportExcel(Request $request)
|
|||
|
||||
// Filter berdasarkan status
|
||||
if ($request->filled('status')) {
|
||||
$query->where('status', $request->status);
|
||||
$status = $request->status;
|
||||
if ($status === 'sedang') {
|
||||
$status = 'sedang_diperiksa';
|
||||
}
|
||||
$query->where('status', $status);
|
||||
}
|
||||
|
||||
// Filter berdasarkan jenis kelamin
|
||||
|
@ -316,7 +414,7 @@ public function exportExcel(Request $request)
|
|||
$antrianSelesai = $antrian->where('status', 'selesai')->count();
|
||||
$antrianMenunggu = $antrian->where('status', 'menunggu')->count();
|
||||
$antrianDipanggil = $antrian->where('status', 'dipanggil')->count();
|
||||
$antrianSedang = $antrian->where('status', 'sedang')->count();
|
||||
$antrianSedang = $antrian->where('status', 'sedang_diperiksa')->count();
|
||||
|
||||
$filename = 'laporan-antrian-' . date('Y-m-d') . '.csv';
|
||||
|
||||
|
@ -339,21 +437,21 @@ public function exportExcel(Request $request)
|
|||
'Tanggal Daftar',
|
||||
'Waktu Daftar',
|
||||
'Waktu Panggil'
|
||||
]);
|
||||
], ';');
|
||||
|
||||
// Data CSV
|
||||
foreach ($antrian as $item) {
|
||||
fputcsv($file, [
|
||||
$item->no_antrian,
|
||||
$item->user->nama,
|
||||
$item->user->no_ktp,
|
||||
"'" . $item->user->no_ktp,
|
||||
$item->user->jenis_kelamin,
|
||||
$item->poli->nama_poli,
|
||||
ucfirst($item->status),
|
||||
$item->created_at ? $item->created_at->format('d/m/Y') : '-',
|
||||
$item->created_at ? $item->created_at->format('H:i') : '-',
|
||||
$item->waktu_panggil ? $item->waktu_panggil->format('H:i') : '-'
|
||||
]);
|
||||
], ';');
|
||||
}
|
||||
|
||||
fclose($file);
|
||||
|
@ -362,7 +460,7 @@ public function exportExcel(Request $request)
|
|||
return response()->stream($callback, 200, $headers);
|
||||
}
|
||||
|
||||
public function poliUmum()
|
||||
public function poliUmum(Request $request)
|
||||
{
|
||||
// Auto-batalkan antrian yang sudah dipanggil lebih dari 5 menit
|
||||
$this->autoBatalkanAntrianLama();
|
||||
|
@ -372,13 +470,13 @@ public function poliUmum()
|
|||
$query->where('nama_poli', 'umum');
|
||||
})
|
||||
->orderBy('created_at', 'asc')
|
||||
->get();
|
||||
->paginate(10);
|
||||
|
||||
$title = 'Poli Umum';
|
||||
return view('admin.poli.index', compact('antrians', 'title'));
|
||||
}
|
||||
|
||||
public function poliGigi()
|
||||
public function poliGigi(Request $request)
|
||||
{
|
||||
// Auto-batalkan antrian yang sudah dipanggil lebih dari 5 menit
|
||||
$this->autoBatalkanAntrianLama();
|
||||
|
@ -388,13 +486,13 @@ public function poliGigi()
|
|||
$query->where('nama_poli', 'gigi');
|
||||
})
|
||||
->orderBy('created_at', 'asc')
|
||||
->get();
|
||||
->paginate(10);
|
||||
|
||||
$title = 'Poli Gigi';
|
||||
return view('admin.poli.index', compact('antrians', 'title'));
|
||||
}
|
||||
|
||||
public function poliJiwa()
|
||||
public function poliJiwa(Request $request)
|
||||
{
|
||||
// Auto-batalkan antrian yang sudah dipanggil lebih dari 5 menit
|
||||
$this->autoBatalkanAntrianLama();
|
||||
|
@ -404,13 +502,13 @@ public function poliJiwa()
|
|||
$query->where('nama_poli', 'kesehatan jiwa');
|
||||
})
|
||||
->orderBy('created_at', 'asc')
|
||||
->get();
|
||||
->paginate(10);
|
||||
|
||||
$title = 'Poli Jiwa';
|
||||
return view('admin.poli.index', compact('antrians', 'title'));
|
||||
}
|
||||
|
||||
public function poliTradisional()
|
||||
public function poliTradisional(Request $request)
|
||||
{
|
||||
// Auto-batalkan antrian yang sudah dipanggil lebih dari 5 menit
|
||||
$this->autoBatalkanAntrianLama();
|
||||
|
@ -420,7 +518,7 @@ public function poliTradisional()
|
|||
$query->where('nama_poli', 'kesehatan tradisional');
|
||||
})
|
||||
->orderBy('created_at', 'asc')
|
||||
->get();
|
||||
->paginate(10);
|
||||
|
||||
$title = 'Poli Tradisional';
|
||||
return view('admin.poli.index', compact('antrians', 'title'));
|
||||
|
@ -513,10 +611,10 @@ public function selesaiAntrian(Request $request)
|
|||
|
||||
$antrian = Antrian::findOrFail($request->antrian_id);
|
||||
|
||||
if ($antrian->status !== 'dipanggil') {
|
||||
if (!in_array($antrian->status, ['dipanggil', 'sedang_diperiksa'])) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Antrian ini tidak dalam status dipanggil'
|
||||
'message' => 'Antrian ini tidak dalam status dipanggil atau sedang diperiksa'
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -535,6 +633,71 @@ public function selesaiAntrian(Request $request)
|
|||
}
|
||||
}
|
||||
|
||||
public function konfirmasiKehadiran(Request $request)
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'antrian_id' => 'required|exists:antrians,id'
|
||||
]);
|
||||
|
||||
$antrian = Antrian::findOrFail($request->antrian_id);
|
||||
|
||||
if ($antrian->status !== 'dipanggil') {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Antrian ini tidak dalam status dipanggil'
|
||||
]);
|
||||
}
|
||||
|
||||
// Update waktu hadir
|
||||
$antrian->update(['waktu_hadir' => now()]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Kehadiran pasien ' . $antrian->no_antrian . ' telah dikonfirmasi'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Terjadi kesalahan: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function mulaiPemeriksaan(Request $request)
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'antrian_id' => 'required|exists:antrians,id'
|
||||
]);
|
||||
|
||||
$antrian = Antrian::findOrFail($request->antrian_id);
|
||||
|
||||
if ($antrian->status !== 'dipanggil' || !$antrian->waktu_hadir) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Pasien harus dikonfirmasi hadir terlebih dahulu'
|
||||
]);
|
||||
}
|
||||
|
||||
// Update status dan waktu mulai pemeriksaan
|
||||
$antrian->update([
|
||||
'status' => 'sedang_diperiksa',
|
||||
'waktu_mulai_periksa' => now()
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Pemeriksaan pasien ' . $antrian->no_antrian . ' dimulai'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Terjadi kesalahan: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function batalAntrian(Request $request)
|
||||
{
|
||||
try {
|
||||
|
@ -607,13 +770,30 @@ public function storeAntrianAdmin(Request $request)
|
|||
$existingQueue = Antrian::where('user_id', $request->user_id)
|
||||
->where('poli_id', $request->poli_id)
|
||||
->whereDate('created_at', today())
|
||||
->whereIn('status', ['menunggu', 'dipanggil'])
|
||||
->whereIn('status', ['menunggu', 'dipanggil', 'sedang_diperiksa'])
|
||||
->first();
|
||||
|
||||
if ($existingQueue) {
|
||||
$statusMessage = '';
|
||||
switch ($existingQueue->status) {
|
||||
case 'menunggu':
|
||||
$statusMessage = 'User ini masih memiliki antrian yang menunggu di ' . $poli->nama_poli . '.';
|
||||
break;
|
||||
case 'dipanggil':
|
||||
$statusMessage = 'User ini memiliki antrian yang sudah dipanggil di ' . $poli->nama_poli . '.';
|
||||
break;
|
||||
case 'sedang_diperiksa':
|
||||
$statusMessage = 'User ini sedang dalam proses pemeriksaan di ' . $poli->nama_poli . '. Tidak dapat membuat antrian baru.';
|
||||
break;
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'User ini sudah memiliki antrian aktif di ' . $poli->nama_poli . ' hari ini.'
|
||||
'message' => $statusMessage,
|
||||
'existing_queue' => [
|
||||
'no_antrian' => $existingQueue->no_antrian,
|
||||
'status' => $existingQueue->status
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -681,14 +861,15 @@ private function getPoliPrefix($namaPoli)
|
|||
|
||||
/**
|
||||
* Auto-batalkan antrian yang sudah dipanggil lebih dari 5 menit
|
||||
* tanpa dikonfirmasi selesai oleh admin
|
||||
* tetapi belum dikonfirmasi kehadirannya (belum ada waktu_hadir)
|
||||
*/
|
||||
private function autoBatalkanAntrianLama()
|
||||
{
|
||||
try {
|
||||
// Cari antrian yang sudah dipanggil lebih dari 5 menit
|
||||
// Cari antrian yang sudah dipanggil lebih dari 5 menit tapi belum konfirmasi hadir
|
||||
$antrianLama = Antrian::where('status', 'dipanggil')
|
||||
->where('waktu_panggil', '<=', now()->subMinutes(5))
|
||||
->whereNull('waktu_hadir') // Hanya yang belum konfirmasi hadir
|
||||
->get();
|
||||
|
||||
$count = 0;
|
||||
|
@ -698,16 +879,16 @@ private function autoBatalkanAntrianLama()
|
|||
$count++;
|
||||
|
||||
// Log untuk tracking
|
||||
\Log::info("Antrian {$antrian->no_antrian} otomatis dibatalkan karena lewat 5 menit sejak dipanggil");
|
||||
Log::info("Antrian {$antrian->no_antrian} otomatis dibatalkan karena lewat 5 menit sejak dipanggil tanpa konfirmasi kehadiran");
|
||||
}
|
||||
|
||||
// Jika ada antrian yang dibatalkan, log jumlahnya
|
||||
if ($count > 0) {
|
||||
\Log::info("Total {$count} antrian otomatis dibatalkan karena timeout");
|
||||
Log::info("Total {$count} antrian otomatis dibatalkan karena timeout tanpa konfirmasi kehadiran");
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Log error jika terjadi masalah
|
||||
\Log::error("Error saat auto-batalkan antrian: " . $e->getMessage());
|
||||
Log::error("Error saat auto-batalkan antrian: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -737,7 +918,7 @@ public function playQueueCallAudio(Request $request)
|
|||
]);
|
||||
|
||||
$poliName = $request->input('poli_name');
|
||||
|
||||
|
||||
// Get audio sequence from AudioService
|
||||
$audioService = app(AudioService::class);
|
||||
$result = $audioService->getQueueCallAudio($poliName);
|
||||
|
@ -765,9 +946,9 @@ public function panggilSelanjutnya(Request $request)
|
|||
$poliName = $request->input('poli_name');
|
||||
|
||||
// Cari antrian berikutnya yang status 'menunggu'
|
||||
$antrianSelanjutnya = Antrian::whereHas('poli', function($query) use ($poliName) {
|
||||
$query->where('nama_poli', $poliName);
|
||||
})
|
||||
$antrianSelanjutnya = Antrian::whereHas('poli', function ($query) use ($poliName) {
|
||||
$query->where('nama_poli', $poliName);
|
||||
})
|
||||
->where('status', 'menunggu')
|
||||
->whereDate('created_at', today())
|
||||
->orderBy('created_at', 'asc')
|
||||
|
@ -790,7 +971,7 @@ public function panggilSelanjutnya(Request $request)
|
|||
\App\Models\RiwayatPanggilan::create([
|
||||
'antrian_id' => $antrianSelanjutnya->id,
|
||||
'waktu_panggilan' => now(),
|
||||
'admin_id' => auth()->id()
|
||||
'admin_id' => Auth::id()
|
||||
]);
|
||||
|
||||
// Get audio sequence
|
||||
|
|
|
@ -43,20 +43,34 @@ public function addQueue(Request $request)
|
|||
$poli = Poli::find($request->poli_id);
|
||||
$poliName = $poli->nama_poli;
|
||||
|
||||
// Check if user already has a queue today for the same poli
|
||||
// Check if user already has an active queue today for the same poli
|
||||
$existingQueue = Antrian::where('user_id', $user->id)
|
||||
->where('poli_id', $request->poli_id)
|
||||
->whereDate('created_at', today())
|
||||
->whereIn('status', ['menunggu', 'dipanggil'])
|
||||
->whereIn('status', ['menunggu', 'dipanggil', 'sedang_diperiksa'])
|
||||
->first();
|
||||
|
||||
if ($existingQueue) {
|
||||
// Determine the appropriate message based on status
|
||||
$statusMessage = '';
|
||||
switch ($existingQueue->status) {
|
||||
case 'menunggu':
|
||||
$statusMessage = 'Anda masih memiliki antrian yang menunggu di ' . $poliName . '.';
|
||||
break;
|
||||
case 'dipanggil':
|
||||
$statusMessage = 'Anda memiliki antrian yang sudah dipanggil di ' . $poliName . '. Silakan datang ke poli.';
|
||||
break;
|
||||
case 'sedang_diperiksa':
|
||||
$statusMessage = 'Anda sedang dalam proses pemeriksaan di ' . $poliName . '. Tidak dapat mengambil antrian baru di poli yang sama.';
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if request wants JSON response
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'type' => 'existing_queue',
|
||||
'message' => 'Anda sudah memiliki antrian di ' . $poliName . ' hari ini.',
|
||||
'message' => $statusMessage,
|
||||
'existing_queue' => [
|
||||
'id' => $existingQueue->id,
|
||||
'no_antrian' => $existingQueue->no_antrian,
|
||||
|
@ -68,7 +82,7 @@ public function addQueue(Request $request)
|
|||
}
|
||||
|
||||
// Fallback to redirect for non-AJAX requests
|
||||
return redirect()->back()->with('error', 'Anda sudah memiliki antrian di ' . $poliName . ' hari ini.');
|
||||
return redirect()->back()->with('error', $statusMessage);
|
||||
}
|
||||
|
||||
// Note: User CAN have multiple queues in different polis
|
||||
|
@ -91,14 +105,14 @@ public function addQueue(Request $request)
|
|||
$lastNumber = (int) preg_replace('/^[A-Z]+/', '', $lastQueue->no_antrian);
|
||||
|
||||
// Debug log
|
||||
\Log::info("Last queue: {$lastQueue->no_antrian}, Extracted number: {$lastNumber}");
|
||||
Log::info("Last queue: {$lastQueue->no_antrian}, Extracted number: {$lastNumber}");
|
||||
}
|
||||
|
||||
$nextNumber = $lastNumber + 1;
|
||||
$nextQueueNumber = $prefix . $nextNumber;
|
||||
|
||||
// Debug log
|
||||
\Log::info("Generated next queue number: {$nextQueueNumber} for poli: {$poliName}");
|
||||
Log::info("Generated next queue number: {$nextQueueNumber} for poli: {$poliName}");
|
||||
|
||||
// Create new queue using current user's data
|
||||
$antrian = Antrian::create([
|
||||
|
@ -152,6 +166,7 @@ public function updateProfile(Request $request)
|
|||
]);
|
||||
|
||||
try {
|
||||
/** @var \App\Models\User $user */
|
||||
$user = Auth::user();
|
||||
|
||||
// Check if KTP number is already used by another user
|
||||
|
@ -205,14 +220,15 @@ private function getPoliPrefix($namaPoli)
|
|||
|
||||
/**
|
||||
* Auto-batalkan antrian yang sudah dipanggil lebih dari 5 menit
|
||||
* tanpa dikonfirmasi selesai oleh admin
|
||||
* tetapi belum dikonfirmasi kehadirannya (belum ada waktu_hadir)
|
||||
*/
|
||||
private function autoBatalkanAntrianLama()
|
||||
{
|
||||
try {
|
||||
// Cari antrian yang sudah dipanggil lebih dari 5 menit
|
||||
// Cari antrian yang sudah dipanggil lebih dari 5 menit tapi belum konfirmasi hadir
|
||||
$antrianLama = Antrian::where('status', 'dipanggil')
|
||||
->where('waktu_panggil', '<=', now()->subMinutes(5))
|
||||
->whereNull('waktu_hadir') // Hanya yang belum konfirmasi hadir
|
||||
->get();
|
||||
|
||||
foreach ($antrianLama as $antrian) {
|
||||
|
@ -220,16 +236,61 @@ private function autoBatalkanAntrianLama()
|
|||
$antrian->update(['status' => 'batal']);
|
||||
|
||||
// Log untuk tracking (optional)
|
||||
\Log::info("Antrian {$antrian->no_antrian} otomatis dibatalkan karena lewat 5 menit sejak dipanggil");
|
||||
Log::info("Antrian {$antrian->no_antrian} otomatis dibatalkan karena lewat 5 menit sejak dipanggil tanpa konfirmasi kehadiran");
|
||||
}
|
||||
|
||||
// Jika ada antrian yang dibatalkan, log jumlahnya
|
||||
if ($antrianLama->count() > 0) {
|
||||
\Log::info("Total {$antrianLama->count()} antrian otomatis dibatalkan karena timeout");
|
||||
Log::info("Total {$antrianLama->count()} antrian otomatis dibatalkan karena timeout tanpa konfirmasi kehadiran");
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Log error jika terjadi masalah
|
||||
\Log::error("Error saat auto-batalkan antrian: " . $e->getMessage());
|
||||
Log::error("Error saat auto-batalkan antrian: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has active queue in specific poli today
|
||||
* @param int $userId
|
||||
* @param int $poliId
|
||||
* @return array|null
|
||||
*/
|
||||
private function checkActiveQueue($userId, $poliId)
|
||||
{
|
||||
$activeQueue = Antrian::where('user_id', $userId)
|
||||
->where('poli_id', $poliId)
|
||||
->whereDate('created_at', today())
|
||||
->whereIn('status', ['menunggu', 'dipanggil', 'sedang_diperiksa'])
|
||||
->first();
|
||||
|
||||
if ($activeQueue) {
|
||||
return [
|
||||
'exists' => true,
|
||||
'queue' => $activeQueue,
|
||||
'message' => $this->getStatusMessage($activeQueue->status, $activeQueue->poli->nama_poli)
|
||||
];
|
||||
}
|
||||
|
||||
return ['exists' => false];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get appropriate message based on queue status
|
||||
* @param string $status
|
||||
* @param string $poliName
|
||||
* @return string
|
||||
*/
|
||||
private function getStatusMessage($status, $poliName)
|
||||
{
|
||||
switch ($status) {
|
||||
case 'menunggu':
|
||||
return "Anda masih memiliki antrian yang menunggu di {$poliName}.";
|
||||
case 'dipanggil':
|
||||
return "Anda memiliki antrian yang sudah dipanggil di {$poliName}. Silakan datang ke poli.";
|
||||
case 'sedang_diperiksa':
|
||||
return "Anda sedang dalam proses pemeriksaan di {$poliName}. Tidak dapat mengambil antrian baru di poli yang sama.";
|
||||
default:
|
||||
return "Anda sudah memiliki antrian aktif di {$poliName}.";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,71 +12,71 @@ public function index()
|
|||
{
|
||||
// Get all available polis
|
||||
$polis = Poli::all();
|
||||
|
||||
|
||||
// Current: sedang dipanggil per poli
|
||||
$poliUmumCurrent = Antrian::whereHas('poli', function($query) {
|
||||
$query->where('nama_poli', 'umum');
|
||||
})
|
||||
->where('status', 'dipanggil')
|
||||
$poliUmumCurrent = Antrian::whereHas('poli', function ($query) {
|
||||
$query->where('nama_poli', 'umum');
|
||||
})
|
||||
->whereIn('status', ['dipanggil', 'sedang_diperiksa'])
|
||||
->whereDate('created_at', today())
|
||||
->orderByDesc('updated_at')
|
||||
->first();
|
||||
|
||||
$poliGigiCurrent = Antrian::whereHas('poli', function($query) {
|
||||
$query->where('nama_poli', 'gigi');
|
||||
})
|
||||
->where('status', 'dipanggil')
|
||||
$poliGigiCurrent = Antrian::whereHas('poli', function ($query) {
|
||||
$query->where('nama_poli', 'gigi');
|
||||
})
|
||||
->whereIn('status', ['dipanggil', 'sedang_diperiksa'])
|
||||
->whereDate('created_at', today())
|
||||
->orderByDesc('updated_at')
|
||||
->first();
|
||||
|
||||
$poliJiwaCurrent = Antrian::whereHas('poli', function($query) {
|
||||
$query->where('nama_poli', 'kesehatan jiwa');
|
||||
})
|
||||
->where('status', 'dipanggil')
|
||||
$poliJiwaCurrent = Antrian::whereHas('poli', function ($query) {
|
||||
$query->where('nama_poli', 'kesehatan jiwa');
|
||||
})
|
||||
->whereIn('status', ['dipanggil', 'sedang_diperiksa'])
|
||||
->whereDate('created_at', today())
|
||||
->orderByDesc('updated_at')
|
||||
->first();
|
||||
|
||||
$poliTradisionalCurrent = Antrian::whereHas('poli', function($query) {
|
||||
$query->where('nama_poli', 'kesehatan tradisional');
|
||||
})
|
||||
->where('status', 'dipanggil')
|
||||
$poliTradisionalCurrent = Antrian::whereHas('poli', function ($query) {
|
||||
$query->where('nama_poli', 'kesehatan tradisional');
|
||||
})
|
||||
->whereIn('status', ['dipanggil', 'sedang_diperiksa'])
|
||||
->whereDate('created_at', today())
|
||||
->orderByDesc('updated_at')
|
||||
->first();
|
||||
|
||||
// Next: menunggu per poli (maks 3)
|
||||
$poliUmumNext = Antrian::whereHas('poli', function($query) {
|
||||
$query->where('nama_poli', 'umum');
|
||||
})
|
||||
$poliUmumNext = Antrian::whereHas('poli', function ($query) {
|
||||
$query->where('nama_poli', 'umum');
|
||||
})
|
||||
->where('status', 'menunggu')
|
||||
->whereDate('created_at', today())
|
||||
->orderBy('created_at', 'asc')
|
||||
->take(3)
|
||||
->get();
|
||||
|
||||
$poliGigiNext = Antrian::whereHas('poli', function($query) {
|
||||
$query->where('nama_poli', 'gigi');
|
||||
})
|
||||
$poliGigiNext = Antrian::whereHas('poli', function ($query) {
|
||||
$query->where('nama_poli', 'gigi');
|
||||
})
|
||||
->where('status', 'menunggu')
|
||||
->whereDate('created_at', today())
|
||||
->orderBy('created_at', 'asc')
|
||||
->take(3)
|
||||
->get();
|
||||
|
||||
$poliJiwaNext = Antrian::whereHas('poli', function($query) {
|
||||
$query->where('nama_poli', 'kesehatan jiwa');
|
||||
})
|
||||
$poliJiwaNext = Antrian::whereHas('poli', function ($query) {
|
||||
$query->where('nama_poli', 'kesehatan jiwa');
|
||||
})
|
||||
->where('status', 'menunggu')
|
||||
->whereDate('created_at', today())
|
||||
->orderBy('created_at', 'asc')
|
||||
->take(3)
|
||||
->get();
|
||||
|
||||
$poliTradisionalNext = Antrian::whereHas('poli', function($query) {
|
||||
$query->where('nama_poli', 'kesehatan tradisional');
|
||||
})
|
||||
$poliTradisionalNext = Antrian::whereHas('poli', function ($query) {
|
||||
$query->where('nama_poli', 'kesehatan tradisional');
|
||||
})
|
||||
->where('status', 'menunggu')
|
||||
->whereDate('created_at', today())
|
||||
->orderBy('created_at', 'asc')
|
||||
|
@ -126,57 +126,57 @@ public function checkNewCalls(Request $request)
|
|||
public function getDisplayData()
|
||||
{
|
||||
// Current: sedang dipanggil per poli
|
||||
$poliUmumCurrent = Antrian::whereHas('poli', function($query) {
|
||||
$query->where('nama_poli', 'umum');
|
||||
})
|
||||
->where('status', 'dipanggil')
|
||||
$poliUmumCurrent = Antrian::whereHas('poli', function ($query) {
|
||||
$query->where('nama_poli', 'umum');
|
||||
})
|
||||
->whereIn('status', ['dipanggil', 'sedang_diperiksa'])
|
||||
->whereDate('created_at', today())
|
||||
->orderByDesc('updated_at')
|
||||
->first();
|
||||
|
||||
$poliGigiCurrent = Antrian::whereHas('poli', function($query) {
|
||||
$query->where('nama_poli', 'gigi');
|
||||
})
|
||||
->where('status', 'dipanggil')
|
||||
$poliGigiCurrent = Antrian::whereHas('poli', function ($query) {
|
||||
$query->where('nama_poli', 'gigi');
|
||||
})
|
||||
->whereIn('status', ['dipanggil', 'sedang_diperiksa'])
|
||||
->whereDate('created_at', today())
|
||||
->orderByDesc('updated_at')
|
||||
->first();
|
||||
|
||||
$poliJiwaCurrent = Antrian::whereHas('poli', function($query) {
|
||||
$query->where('nama_poli', 'kesehatan jiwa');
|
||||
})
|
||||
->where('status', 'dipanggil')
|
||||
$poliJiwaCurrent = Antrian::whereHas('poli', function ($query) {
|
||||
$query->where('nama_poli', 'kesehatan jiwa');
|
||||
})
|
||||
->whereIn('status', ['dipanggil', 'sedang_diperiksa'])
|
||||
->whereDate('created_at', today())
|
||||
->orderByDesc('updated_at')
|
||||
->first();
|
||||
|
||||
$poliTradisionalCurrent = Antrian::where('poli_id', 4)
|
||||
->where('status', 'dipanggil')
|
||||
->whereIn('status', ['dipanggil', 'sedang_diperiksa'])
|
||||
->whereDate('created_at', today())
|
||||
->orderByDesc('updated_at')
|
||||
->first();
|
||||
|
||||
// Next: menunggu per poli (maks 3)
|
||||
$poliUmumNext = Antrian::whereHas('poli', function($query) {
|
||||
$query->where('nama_poli', 'umum');
|
||||
})
|
||||
$poliUmumNext = Antrian::whereHas('poli', function ($query) {
|
||||
$query->where('nama_poli', 'umum');
|
||||
})
|
||||
->where('status', 'menunggu')
|
||||
->whereDate('created_at', today())
|
||||
->orderBy('created_at', 'asc')
|
||||
->take(3)
|
||||
->get();
|
||||
|
||||
$poliGigiNext = Antrian::whereHas('poli', function($query) {
|
||||
$query->where('nama_poli', 'gigi');
|
||||
})
|
||||
$poliGigiNext = Antrian::whereHas('poli', function ($query) {
|
||||
$query->where('nama_poli', 'gigi');
|
||||
})
|
||||
->where('status', 'menunggu')
|
||||
->whereDate('created_at', today())
|
||||
->orderBy('created_at', 'asc')
|
||||
->get();
|
||||
|
||||
$poliJiwaNext = Antrian::whereHas('poli', function($query) {
|
||||
$query->where('nama_poli', 'kesehatan jiwa');
|
||||
})
|
||||
$poliJiwaNext = Antrian::whereHas('poli', function ($query) {
|
||||
$query->where('nama_poli', 'kesehatan jiwa');
|
||||
})
|
||||
->where('status', 'menunggu')
|
||||
->whereDate('created_at', today())
|
||||
->orderBy('created_at', 'asc')
|
||||
|
|
|
@ -18,12 +18,15 @@ class Antrian extends Model
|
|||
'is_call',
|
||||
'status',
|
||||
'waktu_panggil',
|
||||
'loket_id'
|
||||
'waktu_hadir',
|
||||
'waktu_mulai_periksa'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'tanggal_antrian' => 'date',
|
||||
'waktu_panggil' => 'datetime',
|
||||
'waktu_hadir' => 'datetime',
|
||||
'waktu_mulai_periksa' => 'datetime',
|
||||
'is_call' => 'boolean',
|
||||
];
|
||||
|
||||
|
@ -37,11 +40,6 @@ public function poli()
|
|||
return $this->belongsTo(Poli::class);
|
||||
}
|
||||
|
||||
public function loket()
|
||||
{
|
||||
return $this->belongsTo(Loket::class);
|
||||
}
|
||||
|
||||
public function riwayatPanggilan()
|
||||
{
|
||||
return $this->hasMany(RiwayatPanggilan::class);
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Loket extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'lokets';
|
||||
protected $fillable = ['nama_loket'];
|
||||
|
||||
public function antrians()
|
||||
{
|
||||
return $this->hasMany(Antrian::class);
|
||||
}
|
||||
}
|
|
@ -19,7 +19,8 @@ public function register(): void
|
|||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
// Set pagination to use Tailwind CSS styling
|
||||
\Illuminate\Pagination\Paginator::useTailwind();
|
||||
// Set pagination to use custom view
|
||||
\Illuminate\Pagination\Paginator::defaultView('custom.pagination');
|
||||
\Illuminate\Pagination\Paginator::defaultSimpleView('custom.pagination-simple');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
"php": "^8.2",
|
||||
"barryvdh/laravel-dompdf": "^3.1",
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/sanctum": "^4.2",
|
||||
"laravel/tinker": "^2.10.1",
|
||||
"pusher/pusher-php-server": "^7.2"
|
||||
},
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "6a7319662ab12a24a54850a3e8a7b468",
|
||||
"content-hash": "74ecb2b28b89ed06697fc02c059657d8",
|
||||
"packages": [
|
||||
{
|
||||
"name": "barryvdh/laravel-dompdf",
|
||||
|
@ -1559,70 +1559,6 @@
|
|||
},
|
||||
"time": "2025-07-07T14:17:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/sanctum",
|
||||
"version": "v4.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/sanctum.git",
|
||||
"reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/sanctum/zipball/fd6df4f79f48a72992e8d29a9c0ee25422a0d677",
|
||||
"reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"illuminate/console": "^11.0|^12.0",
|
||||
"illuminate/contracts": "^11.0|^12.0",
|
||||
"illuminate/database": "^11.0|^12.0",
|
||||
"illuminate/support": "^11.0|^12.0",
|
||||
"php": "^8.2",
|
||||
"symfony/console": "^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.6",
|
||||
"orchestra/testbench": "^9.0|^10.0",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^11.3"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Laravel\\Sanctum\\SanctumServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laravel\\Sanctum\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.",
|
||||
"keywords": [
|
||||
"auth",
|
||||
"laravel",
|
||||
"sanctum"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/sanctum/issues",
|
||||
"source": "https://github.com/laravel/sanctum"
|
||||
},
|
||||
"time": "2025-07-09T19:45:24+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/serializable-closure",
|
||||
"version": "v2.0.4",
|
||||
|
@ -8747,12 +8683,12 @@
|
|||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {},
|
||||
"stability-flags": [],
|
||||
"prefer-stable": true,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": "^8.2"
|
||||
},
|
||||
"platform-dev": {},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
|
|
|
@ -93,14 +93,25 @@
|
|||
|
|
||||
*/
|
||||
|
||||
'passwords' => [
|
||||
'users' => [
|
||||
'provider' => 'users',
|
||||
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
|
||||
'expire' => 60,
|
||||
'throttle' => 60,
|
||||
],
|
||||
],
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Reset Settings (DISABLED)
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Password reset settings are disabled because this application uses
|
||||
| session-based password reset instead of Laravel's built-in token-based
|
||||
| password reset functionality.
|
||||
|
|
||||
*/
|
||||
|
||||
// 'passwords' => [
|
||||
// 'users' => [
|
||||
// 'provider' => 'users',
|
||||
// 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
|
||||
// 'expire' => 60,
|
||||
// 'throttle' => 60,
|
||||
// ],
|
||||
// ],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Stateful Domains
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Requests from the following domains / hosts will receive stateful API
|
||||
| authentication cookies. Typically, these should include your local
|
||||
| and production domains which access your API via a frontend SPA.
|
||||
|
|
||||
*/
|
||||
|
||||
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
|
||||
'%s%s',
|
||||
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
|
||||
Sanctum::currentApplicationUrlWithPort(),
|
||||
// Sanctum::currentRequestHost(),
|
||||
))),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Sanctum Guards
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This array contains the authentication guards that will be checked when
|
||||
| Sanctum is trying to authenticate a request. If none of these guards
|
||||
| are able to authenticate the request, Sanctum will use the bearer
|
||||
| token that's present on an incoming request for authentication.
|
||||
|
|
||||
*/
|
||||
|
||||
'guard' => ['web'],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Expiration Minutes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value controls the number of minutes until an issued token will be
|
||||
| considered expired. This will override any values set in the token's
|
||||
| "expires_at" attribute, but first-party sessions are not affected.
|
||||
|
|
||||
*/
|
||||
|
||||
'expiration' => null,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Token Prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Sanctum can prefix new tokens in order to take advantage of numerous
|
||||
| security scanning initiatives maintained by open source platforms
|
||||
| that notify developers if they commit tokens into repositories.
|
||||
|
|
||||
| See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning
|
||||
|
|
||||
*/
|
||||
|
||||
'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Sanctum Middleware
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When authenticating your first-party SPA with Sanctum you may need to
|
||||
| customize some of the middleware Sanctum uses while processing the
|
||||
| request. You may change the middleware listed below as required.
|
||||
|
|
||||
*/
|
||||
|
||||
'middleware' => [
|
||||
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
|
||||
'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
|
||||
'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
|
||||
],
|
||||
|
||||
];
|
|
@ -1,27 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('lokets', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('nama_loket', 100);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('lokets');
|
||||
}
|
||||
};
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('personal_access_tokens', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->morphs('tokenable');
|
||||
$table->text('name');
|
||||
$table->string('token', 64)->unique();
|
||||
$table->text('abilities')->nullable();
|
||||
$table->timestamp('last_used_at')->nullable();
|
||||
$table->timestamp('expires_at')->nullable()->index();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('personal_access_tokens');
|
||||
}
|
||||
};
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('jobs', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->string('queue')->index();
|
||||
$table->longText('payload');
|
||||
$table->unsignedTinyInteger('attempts');
|
||||
$table->unsignedInteger('reserved_at')->nullable();
|
||||
$table->unsignedInteger('available_at');
|
||||
$table->unsignedInteger('created_at');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('jobs');
|
||||
}
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('antrians', function (Blueprint $table) {
|
||||
// Ubah enum status untuk menambah 'sedang_diperiksa'
|
||||
$table->enum('status', ['menunggu', 'dipanggil', 'sedang_diperiksa', 'selesai', 'batal'])
|
||||
->default('menunggu')
|
||||
->change();
|
||||
|
||||
// Tambah field waktu konfirmasi kehadiran
|
||||
$table->timestamp('waktu_hadir')->nullable()->after('waktu_panggil');
|
||||
|
||||
// Tambah field waktu mulai pemeriksaan
|
||||
$table->timestamp('waktu_mulai_periksa')->nullable()->after('waktu_hadir');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('antrians', function (Blueprint $table) {
|
||||
// Kembalikan enum status ke yang lama
|
||||
$table->enum('status', ['menunggu', 'dipanggil', 'selesai', 'batal'])
|
||||
->default('menunggu')
|
||||
->change();
|
||||
|
||||
// Hapus field yang ditambahkan
|
||||
$table->dropColumn(['waktu_hadir', 'waktu_mulai_periksa']);
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// 1. Drop foreign key constraint for loket_id in antrians table first
|
||||
Schema::table('antrians', function (Blueprint $table) {
|
||||
$table->dropForeign(['loket_id']);
|
||||
$table->dropColumn('loket_id');
|
||||
});
|
||||
|
||||
// 2. Drop unused tables
|
||||
Schema::dropIfExists('lokets');
|
||||
Schema::dropIfExists('personal_access_tokens');
|
||||
Schema::dropIfExists('jobs');
|
||||
|
||||
// 3. Drop failed_jobs table if exists (related to jobs)
|
||||
Schema::dropIfExists('failed_jobs');
|
||||
Schema::dropIfExists('job_batches');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// Recreate jobs related tables
|
||||
Schema::create('jobs', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->string('queue')->index();
|
||||
$table->longText('payload');
|
||||
$table->unsignedTinyInteger('attempts');
|
||||
$table->unsignedInteger('reserved_at')->nullable();
|
||||
$table->unsignedInteger('available_at');
|
||||
$table->unsignedInteger('created_at');
|
||||
});
|
||||
|
||||
Schema::create('failed_jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('uuid')->unique();
|
||||
$table->text('connection');
|
||||
$table->text('queue');
|
||||
$table->longText('payload');
|
||||
$table->longText('exception');
|
||||
$table->timestamp('failed_at')->useCurrent();
|
||||
});
|
||||
|
||||
Schema::create('job_batches', function (Blueprint $table) {
|
||||
$table->string('id')->primary();
|
||||
$table->string('name');
|
||||
$table->integer('total_jobs');
|
||||
$table->integer('pending_jobs');
|
||||
$table->integer('failed_jobs');
|
||||
$table->longText('failed_job_ids');
|
||||
$table->mediumText('options')->nullable();
|
||||
$table->integer('cancelled_at')->nullable();
|
||||
$table->integer('created_at');
|
||||
$table->integer('finished_at')->nullable();
|
||||
});
|
||||
|
||||
// Recreate personal_access_tokens table
|
||||
Schema::create('personal_access_tokens', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->morphs('tokenable');
|
||||
$table->text('name');
|
||||
$table->string('token', 64)->unique();
|
||||
$table->text('abilities')->nullable();
|
||||
$table->timestamp('last_used_at')->nullable();
|
||||
$table->timestamp('expires_at')->nullable()->index();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
// Recreate lokets table
|
||||
Schema::create('lokets', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('nama_loket', 100);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
// Add back loket_id to antrians table
|
||||
Schema::table('antrians', function (Blueprint $table) {
|
||||
$table->foreignId('loket_id')->nullable()->constrained('lokets')->onDelete('set null');
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Drop password_reset_tokens table since the app uses session-based password reset
|
||||
// instead of token-based password reset
|
||||
Schema::dropIfExists('password_reset_tokens');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// Recreate password_reset_tokens table if rollback is needed
|
||||
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
||||
$table->string('email')->primary();
|
||||
$table->string('token');
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
}
|
||||
};
|
|
@ -30,18 +30,6 @@ public function run(): void
|
|||
);
|
||||
}
|
||||
|
||||
// Seed lokets table with upsert
|
||||
$lokets = [
|
||||
['id' => 1, 'nama_loket' => 'Loket 1'],
|
||||
['id' => 2, 'nama_loket' => 'Loket 2'],
|
||||
];
|
||||
foreach ($lokets as $loket) {
|
||||
DB::table('lokets')->updateOrInsert(
|
||||
['id' => $loket['id']],
|
||||
['nama_loket' => $loket['nama_loket'], 'updated_at' => now()]
|
||||
);
|
||||
}
|
||||
|
||||
// Seed users table with upsert
|
||||
DB::table('users')->updateOrInsert(
|
||||
['id' => 1],
|
||||
|
@ -57,7 +45,7 @@ public function run(): void
|
|||
]
|
||||
);
|
||||
|
||||
// Seed antrians table with upsert
|
||||
// Seed antrians table with upsert (removed loket_id reference)
|
||||
DB::table('antrians')->updateOrInsert(
|
||||
['id' => 1],
|
||||
[
|
||||
|
@ -68,12 +56,13 @@ public function run(): void
|
|||
'is_call' => 0,
|
||||
'status' => 'menunggu',
|
||||
'waktu_panggil' => null,
|
||||
'loket_id' => 1,
|
||||
'waktu_hadir' => null,
|
||||
'waktu_mulai_periksa' => null,
|
||||
'updated_at' => now(),
|
||||
]
|
||||
);
|
||||
|
||||
// Add more antrian data for testing
|
||||
// Add more antrian data for testing - Poli Gigi
|
||||
DB::table('antrians')->updateOrInsert(
|
||||
['id' => 2],
|
||||
[
|
||||
|
@ -84,11 +73,13 @@ public function run(): void
|
|||
'is_call' => 0,
|
||||
'status' => 'menunggu',
|
||||
'waktu_panggil' => null,
|
||||
'loket_id' => 1,
|
||||
'waktu_hadir' => null,
|
||||
'waktu_mulai_periksa' => null,
|
||||
'updated_at' => now(),
|
||||
]
|
||||
);
|
||||
|
||||
// Poli Jiwa - Status dipanggil untuk testing
|
||||
DB::table('antrians')->updateOrInsert(
|
||||
['id' => 3],
|
||||
[
|
||||
|
@ -96,14 +87,16 @@ public function run(): void
|
|||
'poli_id' => 3,
|
||||
'no_antrian' => 'J1',
|
||||
'tanggal_antrian' => now()->toDateString(),
|
||||
'is_call' => 0,
|
||||
'is_call' => 1,
|
||||
'status' => 'dipanggil',
|
||||
'waktu_panggil' => now(),
|
||||
'loket_id' => 1,
|
||||
'waktu_hadir' => null,
|
||||
'waktu_mulai_periksa' => null,
|
||||
'updated_at' => now(),
|
||||
]
|
||||
);
|
||||
|
||||
// Poli Tradisional - Status sedang diperiksa untuk testing
|
||||
DB::table('antrians')->updateOrInsert(
|
||||
['id' => 4],
|
||||
[
|
||||
|
@ -111,10 +104,79 @@ public function run(): void
|
|||
'poli_id' => 4,
|
||||
'no_antrian' => 'T1',
|
||||
'tanggal_antrian' => now()->toDateString(),
|
||||
'is_call' => 1,
|
||||
'status' => 'sedang_diperiksa',
|
||||
'waktu_panggil' => now()->subMinutes(10),
|
||||
'waktu_hadir' => now()->subMinutes(8),
|
||||
'waktu_mulai_periksa' => now()->subMinutes(5),
|
||||
'updated_at' => now(),
|
||||
]
|
||||
);
|
||||
|
||||
// Tambah satu lagi data sedang diperiksa untuk testing filter
|
||||
DB::table('antrians')->updateOrInsert(
|
||||
['id' => 5],
|
||||
[
|
||||
'user_id' => 1,
|
||||
'poli_id' => 1,
|
||||
'no_antrian' => 'U2',
|
||||
'tanggal_antrian' => now()->toDateString(),
|
||||
'is_call' => 1,
|
||||
'status' => 'sedang_diperiksa',
|
||||
'waktu_panggil' => now()->subMinutes(15),
|
||||
'waktu_hadir' => now()->subMinutes(12),
|
||||
'waktu_mulai_periksa' => now()->subMinutes(8),
|
||||
'updated_at' => now(),
|
||||
]
|
||||
);
|
||||
|
||||
// Tambah data dengan status batal untuk testing filter
|
||||
DB::table('antrians')->updateOrInsert(
|
||||
['id' => 6],
|
||||
[
|
||||
'user_id' => 1,
|
||||
'poli_id' => 2,
|
||||
'no_antrian' => 'G2',
|
||||
'tanggal_antrian' => now()->toDateString(),
|
||||
'is_call' => 0,
|
||||
'status' => 'menunggu',
|
||||
'status' => 'batal',
|
||||
'waktu_panggil' => null,
|
||||
'loket_id' => 1,
|
||||
'waktu_hadir' => null,
|
||||
'waktu_mulai_periksa' => null,
|
||||
'updated_at' => now(),
|
||||
]
|
||||
);
|
||||
|
||||
// Tambah satu lagi data batal untuk testing filter
|
||||
DB::table('antrians')->updateOrInsert(
|
||||
['id' => 7],
|
||||
[
|
||||
'user_id' => 1,
|
||||
'poli_id' => 3,
|
||||
'no_antrian' => 'J2',
|
||||
'tanggal_antrian' => now()->toDateString(),
|
||||
'is_call' => 0,
|
||||
'status' => 'batal',
|
||||
'waktu_panggil' => null,
|
||||
'waktu_hadir' => null,
|
||||
'waktu_mulai_periksa' => null,
|
||||
'updated_at' => now(),
|
||||
]
|
||||
);
|
||||
|
||||
// Tambah data dengan status selesai untuk testing filter
|
||||
DB::table('antrians')->updateOrInsert(
|
||||
['id' => 8],
|
||||
[
|
||||
'user_id' => 1,
|
||||
'poli_id' => 4,
|
||||
'no_antrian' => 'T2',
|
||||
'tanggal_antrian' => now()->toDateString(),
|
||||
'is_call' => 1,
|
||||
'status' => 'selesai',
|
||||
'waktu_panggil' => now()->subMinutes(30),
|
||||
'waktu_hadir' => now()->subMinutes(25),
|
||||
'waktu_mulai_periksa' => now()->subMinutes(20),
|
||||
'updated_at' => now(),
|
||||
]
|
||||
);
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\Loket;
|
||||
|
||||
class LoketSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$lokets = [
|
||||
['nama_loket' => 'Loket 1'],
|
||||
['nama_loket' => 'Loket 2'],
|
||||
['nama_loket' => 'Loket 3'],
|
||||
];
|
||||
|
||||
foreach ($lokets as $loket) {
|
||||
Loket::create($loket);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,143 +1,151 @@
|
|||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
@include('admin.partials.top-nav')
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
@include('admin.partials.top-nav')
|
||||
|
||||
<div class="flex">
|
||||
@include('admin.partials.sidebar')
|
||||
<div class="flex">
|
||||
@include('admin.partials.sidebar')
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="flex-1 lg:ml-0">
|
||||
<div class="px-4 sm:px-6 lg:px-8 py-6 md:py-8">
|
||||
<!-- Header -->
|
||||
<div class="mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-900 mb-2">Tambah Antrian Manual</h1>
|
||||
<p class="text-gray-600">Bantu pasien yang tidak bisa antri online dengan membuat antrian manual</p>
|
||||
</div>
|
||||
<!-- Main Content -->
|
||||
<div class="flex-1 lg:ml-0">
|
||||
<div class="px-4 sm:px-6 lg:px-8 py-6 md:py-8">
|
||||
<!-- Header -->
|
||||
<div class="mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-900 mb-2">Tambah Antrian Manual</h1>
|
||||
<p class="text-gray-600">Bantu pasien yang tidak bisa antri online dengan membuat antrian manual</p>
|
||||
</div>
|
||||
|
||||
<!-- Search User Section -->
|
||||
<div class="bg-white rounded-lg shadow-sm border p-6 mb-6">
|
||||
<h2 class="text-lg font-semibold text-gray-900 mb-4">Cari Pasien</h2>
|
||||
|
||||
<div class="flex space-x-4 mb-4">
|
||||
<div class="flex-1">
|
||||
<input type="text" id="searchInput" placeholder="Cari berdasarkan nama, NIK, atau nomor HP..."
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||
<!-- Search User Section -->
|
||||
<div class="bg-white rounded-lg shadow-sm border p-6 mb-6">
|
||||
<h2 class="text-lg font-semibold text-gray-900 mb-4">Cari Pasien</h2>
|
||||
|
||||
<div class="flex space-x-4 mb-4">
|
||||
<div class="flex-1">
|
||||
<input type="text" id="searchInput"
|
||||
placeholder="Cari berdasarkan nama, NIK, atau nomor HP..."
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||
</div>
|
||||
<button id="searchBtn"
|
||||
class="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 focus:ring-2 focus:ring-blue-500">
|
||||
Cari
|
||||
</button>
|
||||
</div>
|
||||
<button id="searchBtn" class="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 focus:ring-2 focus:ring-blue-500">
|
||||
Cari
|
||||
|
||||
<!-- Search Results -->
|
||||
<div id="searchResults" class="hidden">
|
||||
<h3 class="text-md font-medium text-gray-900 mb-3">Hasil Pencarian:</h3>
|
||||
<div id="userList" class="space-y-2"></div>
|
||||
</div>
|
||||
|
||||
<!-- No Results Message -->
|
||||
<div id="noResults" class="hidden text-center py-4 text-gray-500">
|
||||
Tidak ada hasil yang ditemukan
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Selected User & Poli Selection -->
|
||||
<div id="userSelection" class="hidden bg-white rounded-lg shadow-sm border p-6 mb-6">
|
||||
<h2 class="text-lg font-semibold text-gray-900 mb-4">Pilih Poli</h2>
|
||||
|
||||
<div class="mb-4">
|
||||
<h3 class="text-md font-medium text-gray-700 mb-2">Pasien yang dipilih:</h3>
|
||||
<div id="selectedUserInfo" class="bg-gray-50 rounded-lg p-3"></div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="poliSelect" class="block text-sm font-medium text-gray-700 mb-2">Pilih Poli:</label>
|
||||
<select id="poliSelect"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||
<option value="">Pilih Poli</option>
|
||||
@foreach ($polis as $poli)
|
||||
<option value="{{ $poli->id }}">{{ ucfirst($poli->nama_poli) }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button id="createQueueBtn"
|
||||
class="w-full px-6 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 focus:ring-2 focus:ring-green-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
disabled>
|
||||
Buat Antrian
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Search Results -->
|
||||
<div id="searchResults" class="hidden">
|
||||
<h3 class="text-md font-medium text-gray-900 mb-3">Hasil Pencarian:</h3>
|
||||
<div id="userList" class="space-y-2"></div>
|
||||
</div>
|
||||
|
||||
<!-- No Results Message -->
|
||||
<div id="noResults" class="hidden text-center py-4 text-gray-500">
|
||||
Tidak ada hasil yang ditemukan
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Selected User & Poli Selection -->
|
||||
<div id="userSelection" class="hidden bg-white rounded-lg shadow-sm border p-6 mb-6">
|
||||
<h2 class="text-lg font-semibold text-gray-900 mb-4">Pilih Poli</h2>
|
||||
|
||||
<div class="mb-4">
|
||||
<h3 class="text-md font-medium text-gray-700 mb-2">Pasien yang dipilih:</h3>
|
||||
<div id="selectedUserInfo" class="bg-gray-50 rounded-lg p-3"></div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="poliSelect" class="block text-sm font-medium text-gray-700 mb-2">Pilih Poli:</label>
|
||||
<select id="poliSelect" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||
<option value="">Pilih Poli</option>
|
||||
@foreach($polis as $poli)
|
||||
<option value="{{ $poli->id }}">{{ ucfirst($poli->nama_poli) }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button id="createQueueBtn" class="w-full px-6 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 focus:ring-2 focus:ring-green-500 disabled:opacity-50 disabled:cursor-not-allowed" disabled>
|
||||
Buat Antrian
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logout Form -->
|
||||
<form id="logout-form" method="POST" action="{{ route('logout') }}" class="hidden">
|
||||
@csrf
|
||||
</form>
|
||||
<!-- Logout Form -->
|
||||
<form id="logout-form" method="POST" action="{{ route('logout') }}" class="hidden">
|
||||
@csrf
|
||||
</form>
|
||||
|
||||
<script>
|
||||
let selectedUser = null;
|
||||
<script>
|
||||
let selectedUser = null;
|
||||
|
||||
// Search functionality
|
||||
document.getElementById('searchBtn').addEventListener('click', function() {
|
||||
const searchTerm = document.getElementById('searchInput').value.trim();
|
||||
if (searchTerm.length < 3) {
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Peringatan',
|
||||
text: 'Masukkan minimal 3 karakter untuk pencarian',
|
||||
confirmButtonColor: '#3B82F6'
|
||||
// Search functionality
|
||||
document.getElementById('searchBtn').addEventListener('click', function() {
|
||||
const searchTerm = document.getElementById('searchInput').value.trim();
|
||||
if (searchTerm.length < 3) {
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Peringatan',
|
||||
text: 'Masukkan minimal 3 karakter untuk pencarian',
|
||||
confirmButtonColor: '#3B82F6'
|
||||
});
|
||||
return;
|
||||
}
|
||||
searchUsers(searchTerm);
|
||||
});
|
||||
return;
|
||||
}
|
||||
searchUsers(searchTerm);
|
||||
});
|
||||
|
||||
document.getElementById('searchInput').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
document.getElementById('searchBtn').click();
|
||||
}
|
||||
});
|
||||
document.getElementById('searchInput').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
document.getElementById('searchBtn').click();
|
||||
}
|
||||
});
|
||||
|
||||
function searchUsers(searchTerm) {
|
||||
fetch('{{ route("admin.antrian.cari-user") }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
},
|
||||
body: JSON.stringify({ search: searchTerm })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success && data.users.length > 0) {
|
||||
displaySearchResults(data.users);
|
||||
} else {
|
||||
showNoResults();
|
||||
function searchUsers(searchTerm) {
|
||||
fetch('{{ route('admin.antrian.cari-user') }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
search: searchTerm
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success && data.users.length > 0) {
|
||||
displaySearchResults(data.users);
|
||||
} else {
|
||||
showNoResults();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: 'Terjadi kesalahan saat mencari user',
|
||||
confirmButtonColor: '#EF4444'
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: 'Terjadi kesalahan saat mencari user',
|
||||
confirmButtonColor: '#EF4444'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function displaySearchResults(users) {
|
||||
const userList = document.getElementById('userList');
|
||||
const searchResults = document.getElementById('searchResults');
|
||||
const noResults = document.getElementById('noResults');
|
||||
|
||||
userList.innerHTML = '';
|
||||
|
||||
users.forEach(user => {
|
||||
const userDiv = document.createElement('div');
|
||||
userDiv.className = 'flex items-center justify-between p-3 border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer';
|
||||
userDiv.innerHTML = `
|
||||
function displaySearchResults(users) {
|
||||
const userList = document.getElementById('userList');
|
||||
const searchResults = document.getElementById('searchResults');
|
||||
const noResults = document.getElementById('noResults');
|
||||
|
||||
userList.innerHTML = '';
|
||||
|
||||
users.forEach(user => {
|
||||
const userDiv = document.createElement('div');
|
||||
userDiv.className =
|
||||
'flex items-center justify-between p-3 border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer';
|
||||
userDiv.innerHTML = `
|
||||
<div>
|
||||
<div class="font-medium text-gray-900">${user.nama}</div>
|
||||
<div class="text-sm text-gray-600">NIK: ${user.no_ktp} | HP: ${user.no_hp}</div>
|
||||
|
@ -147,117 +155,169 @@ function displaySearchResults(users) {
|
|||
Pilih
|
||||
</button>
|
||||
`;
|
||||
userList.appendChild(userDiv);
|
||||
});
|
||||
|
||||
searchResults.classList.remove('hidden');
|
||||
noResults.classList.add('hidden');
|
||||
}
|
||||
userList.appendChild(userDiv);
|
||||
});
|
||||
|
||||
function showNoResults() {
|
||||
document.getElementById('searchResults').classList.add('hidden');
|
||||
document.getElementById('noResults').classList.remove('hidden');
|
||||
}
|
||||
searchResults.classList.remove('hidden');
|
||||
noResults.classList.add('hidden');
|
||||
}
|
||||
|
||||
function selectUser(user) {
|
||||
selectedUser = user;
|
||||
|
||||
// Display selected user info
|
||||
const selectedUserInfo = document.getElementById('selectedUserInfo');
|
||||
selectedUserInfo.innerHTML = `
|
||||
function showNoResults() {
|
||||
document.getElementById('searchResults').classList.add('hidden');
|
||||
document.getElementById('noResults').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function selectUser(user) {
|
||||
selectedUser = user;
|
||||
|
||||
// Display selected user info
|
||||
const selectedUserInfo = document.getElementById('selectedUserInfo');
|
||||
selectedUserInfo.innerHTML = `
|
||||
<div class="font-medium text-gray-900">${user.nama}</div>
|
||||
<div class="text-sm text-gray-600">NIK: ${user.no_ktp} | HP: ${user.no_hp}</div>
|
||||
<div class="text-sm text-gray-500">${user.jenis_kelamin} - ${user.alamat}</div>
|
||||
`;
|
||||
|
||||
// Show user selection section
|
||||
document.getElementById('userSelection').classList.remove('hidden');
|
||||
|
||||
// Reset poli selection
|
||||
document.getElementById('poliSelect').value = '';
|
||||
document.getElementById('createQueueBtn').disabled = true;
|
||||
|
||||
// Hide search results
|
||||
document.getElementById('searchResults').classList.add('hidden');
|
||||
document.getElementById('searchInput').value = '';
|
||||
}
|
||||
|
||||
// Poli selection change
|
||||
document.getElementById('poliSelect').addEventListener('change', function() {
|
||||
const createQueueBtn = document.getElementById('createQueueBtn');
|
||||
createQueueBtn.disabled = !this.value;
|
||||
});
|
||||
// Show user selection section
|
||||
document.getElementById('userSelection').classList.remove('hidden');
|
||||
|
||||
// Create queue
|
||||
document.getElementById('createQueueBtn').addEventListener('click', function() {
|
||||
if (!selectedUser || !document.getElementById('poliSelect').value) {
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Peringatan',
|
||||
text: 'Pilih user dan poli terlebih dahulu',
|
||||
confirmButtonColor: '#3B82F6'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
createQueue();
|
||||
});
|
||||
// Reset poli selection
|
||||
document.getElementById('poliSelect').value = '';
|
||||
document.getElementById('createQueueBtn').disabled = true;
|
||||
|
||||
function createQueue() {
|
||||
const poliId = document.getElementById('poliSelect').value;
|
||||
|
||||
fetch('{{ route("admin.antrian.store") }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user_id: selectedUser.id,
|
||||
poli_id: poliId
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Berhasil!',
|
||||
text: data.message,
|
||||
confirmButtonColor: '#10B981'
|
||||
}).then(() => {
|
||||
resetForm();
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: data.message,
|
||||
confirmButtonColor: '#EF4444'
|
||||
});
|
||||
// Hide search results
|
||||
document.getElementById('searchResults').classList.add('hidden');
|
||||
document.getElementById('searchInput').value = '';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: 'Terjadi kesalahan saat membuat antrian',
|
||||
confirmButtonColor: '#EF4444'
|
||||
|
||||
// Poli selection change
|
||||
document.getElementById('poliSelect').addEventListener('change', function() {
|
||||
const createQueueBtn = document.getElementById('createQueueBtn');
|
||||
createQueueBtn.disabled = !this.value;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
selectedUser = null;
|
||||
document.getElementById('userSelection').classList.add('hidden');
|
||||
document.getElementById('searchResults').classList.add('hidden');
|
||||
document.getElementById('noResults').classList.add('hidden');
|
||||
document.getElementById('searchInput').value = '';
|
||||
document.getElementById('poliSelect').value = '';
|
||||
document.getElementById('createQueueBtn').disabled = true;
|
||||
}
|
||||
</script>
|
||||
// Create queue
|
||||
document.getElementById('createQueueBtn').addEventListener('click', function() {
|
||||
if (!selectedUser || !document.getElementById('poliSelect').value) {
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Peringatan',
|
||||
text: 'Pilih user dan poli terlebih dahulu',
|
||||
confirmButtonColor: '#3B82F6'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@include('admin.partials.sidebar-script')
|
||||
createQueue();
|
||||
});
|
||||
|
||||
function createQueue() {
|
||||
const poliId = document.getElementById('poliSelect').value;
|
||||
|
||||
fetch('{{ route('admin.antrian.store') }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user_id: selectedUser.id,
|
||||
poli_id: poliId
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Berhasil!',
|
||||
text: data.message,
|
||||
confirmButtonColor: '#10B981'
|
||||
}).then(() => {
|
||||
resetForm();
|
||||
});
|
||||
} else {
|
||||
// Check if it's an existing queue error with additional details
|
||||
if (data.existing_queue) {
|
||||
let statusText = '';
|
||||
let alertIcon = 'warning';
|
||||
let titleText = 'Antrian Sudah Ada!';
|
||||
|
||||
switch (data.existing_queue.status) {
|
||||
case 'menunggu':
|
||||
statusText = 'Menunggu';
|
||||
break;
|
||||
case 'dipanggil':
|
||||
statusText = 'Dipanggil';
|
||||
alertIcon = 'info';
|
||||
titleText = 'Antrian Sedang Dipanggil!';
|
||||
break;
|
||||
case 'sedang_diperiksa':
|
||||
statusText = 'Sedang Diperiksa';
|
||||
alertIcon = 'error';
|
||||
titleText = 'Sedang Dalam Pemeriksaan!';
|
||||
break;
|
||||
}
|
||||
|
||||
Swal.fire({
|
||||
icon: alertIcon,
|
||||
title: titleText,
|
||||
html: `
|
||||
<div class="text-left">
|
||||
<p class="mb-3">${data.message}</p>
|
||||
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-3 mb-3">
|
||||
<div class="flex items-center">
|
||||
<svg class="w-5 h-5 text-yellow-600 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<span class="font-medium text-yellow-800">Detail Antrian Aktif:</span>
|
||||
</div>
|
||||
<div class="mt-2 text-sm text-yellow-700">
|
||||
<p><strong>No. Antrian:</strong> ${data.existing_queue.no_antrian}</p>
|
||||
<p><strong>Status:</strong> ${statusText}</p>
|
||||
</div>
|
||||
</div>
|
||||
${data.existing_queue.status === 'sedang_diperiksa'
|
||||
? '<p class="text-sm text-red-600 font-medium">⚠️ User tidak dapat mengambil antrian baru di poli yang sama selama masih dalam proses pemeriksaan.</p>'
|
||||
: '<p class="text-sm text-gray-600">Silakan tunggu hingga antrian selesai atau dibatalkan terlebih dahulu.</p>'
|
||||
}
|
||||
</div>
|
||||
`,
|
||||
confirmButtonColor: data.existing_queue.status === 'sedang_diperiksa' ?
|
||||
'#EF4444' : '#F59E0B'
|
||||
});
|
||||
} else {
|
||||
// Generic error
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: data.message,
|
||||
confirmButtonColor: '#EF4444'
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: 'Terjadi kesalahan saat membuat antrian',
|
||||
confirmButtonColor: '#EF4444'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
selectedUser = null;
|
||||
document.getElementById('userSelection').classList.add('hidden');
|
||||
document.getElementById('searchResults').classList.add('hidden');
|
||||
document.getElementById('noResults').classList.add('hidden');
|
||||
document.getElementById('searchInput').value = '';
|
||||
document.getElementById('poliSelect').value = '';
|
||||
document.getElementById('createQueueBtn').disabled = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
@include('admin.partials.sidebar-script')
|
||||
@endsection
|
||||
|
|
|
@ -19,68 +19,64 @@
|
|||
</div>
|
||||
|
||||
<!-- Poli Summary -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
<div class="bg-white rounded-2xl shadow-xl p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="p-3 rounded-full bg-blue-100 text-blue-600">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm text-gray-500">Poli Umum</p>
|
||||
<p class="text-2xl font-bold text-gray-900">{{ $poliUmumCount ?? 0 }}</p>
|
||||
<p class="text-xs text-gray-400">Antrian menunggu</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
<div class="bg-white rounded-2xl shadow-xl p-6 flex flex-col lg:flex-row items-center lg:items-start justify-center lg:justify-start text-center lg:text-left"
|
||||
data-poli-count="umum">
|
||||
<div class="p-3 rounded-full bg-blue-100 text-blue-600 mb-2 lg:mb-0 lg:mr-4">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">Poli Umum</p>
|
||||
<p class="text-2xl font-bold text-gray-900">{{ $poliUmumCount ?? 0 }}</p>
|
||||
<p class="text-xs text-gray-400">Antrian menunggu</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-xl p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="p-3 rounded-full bg-green-100 text-green-600">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm text-gray-500">Poli Gigi</p>
|
||||
<p class="text-2xl font-bold text-gray-900">{{ $poliGigiCount ?? 0 }}</p>
|
||||
<p class="text-xs text-gray-400">Antrian menunggu</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-2xl shadow-xl p-6 flex flex-col lg:flex-row items-center lg:items-start justify-center lg:justify-start text-center lg:text-left"
|
||||
data-poli-count="gigi">
|
||||
<div class="p-3 rounded-full bg-green-100 text-green-600 mb-2 lg:mb-0 lg:mr-4">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">Poli Gigi</p>
|
||||
<p class="text-2xl font-bold text-gray-900">{{ $poliGigiCount ?? 0 }}</p>
|
||||
<p class="text-xs text-gray-400">Antrian menunggu</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-xl p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="p-3 rounded-full bg-purple-100 text-purple-600">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm text-gray-500">Poli Jiwa</p>
|
||||
<p class="text-2xl font-bold text-gray-900">{{ $poliJiwaCount ?? 0 }}</p>
|
||||
<p class="text-xs text-gray-400">Antrian menunggu</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-2xl shadow-xl p-6 flex flex-col lg:flex-row items-center lg:items-start justify-center lg:justify-start text-center lg:text-left"
|
||||
data-poli-count="jiwa">
|
||||
<div class="p-3 rounded-full bg-purple-100 text-purple-600 mb-2 lg:mb-0 lg:mr-4">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">Poli Jiwa</p>
|
||||
<p class="text-2xl font-bold text-gray-900">{{ $poliJiwaCount ?? 0 }}</p>
|
||||
<p class="text-xs text-gray-400">Antrian menunggu</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-xl p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="p-3 rounded-full bg-yellow-100 text-yellow-600">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm text-gray-500">Poli Tradisional</p>
|
||||
<p class="text-2xl font-bold text-gray-900">{{ $poliTradisionalCount ?? 0 }}</p>
|
||||
<p class="text-xs text-gray-400">Antrian menunggu</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-2xl shadow-xl p-6 flex flex-col lg:flex-row items-center lg:items-start justify-center lg:justify-start text-center lg:text-left"
|
||||
data-poli-count="tradisional">
|
||||
<div class="p-3 rounded-full bg-yellow-100 text-yellow-600 mb-2 lg:mb-0 lg:mr-4">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">Poli Tradisional</p>
|
||||
<p class="text-2xl font-bold text-gray-900">{{ $poliTradisionalCount ?? 0 }}</p>
|
||||
<p class="text-xs text-gray-400">Antrian menunggu</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -102,8 +98,8 @@
|
|||
class="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition duration-200">
|
||||
Tambah Antrian
|
||||
<svg class="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 5l7 7-7 7"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7">
|
||||
</path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -156,43 +152,105 @@ class="inline-flex items-center px-4 py-2 bg-purple-600 text-white rounded-lg ho
|
|||
<!-- Recent Activity -->
|
||||
<div class="bg-white rounded-2xl shadow-xl p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">Aktivitas Terbaru</h3>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center p-4 bg-gray-50 rounded-lg">
|
||||
<div class="p-2 rounded-full bg-blue-100 text-blue-600">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
||||
</svg>
|
||||
<div class="space-y-4" data-recent-activity>
|
||||
@if (isset($antrianTerbaru) && $antrianTerbaru->count() > 0)
|
||||
@foreach ($antrianTerbaru as $antrian)
|
||||
<div class="flex items-center p-4 bg-gray-50 rounded-lg">
|
||||
@if ($antrian->status == 'menunggu')
|
||||
<div class="p-2 rounded-full bg-blue-100 text-blue-600">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm font-medium text-gray-900">Antrian baru ditambahkan</p>
|
||||
<p class="text-xs text-gray-500">{{ $antrian->poli->nama_poli }} -
|
||||
{{ $antrian->created_at->diffForHumans() }}</p>
|
||||
</div>
|
||||
@elseif($antrian->status == 'dipanggil')
|
||||
<div class="p-2 rounded-full bg-yellow-100 text-yellow-600">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm font-medium text-gray-900">Antrian dipanggil</p>
|
||||
<p class="text-xs text-gray-500">{{ $antrian->poli->nama_poli }} -
|
||||
{{ $antrian->updated_at->diffForHumans() }}</p>
|
||||
</div>
|
||||
@elseif($antrian->status == 'selesai')
|
||||
<div class="p-2 rounded-full bg-green-100 text-green-600">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm font-medium text-gray-900">Antrian selesai</p>
|
||||
<p class="text-xs text-gray-500">{{ $antrian->poli->nama_poli }} -
|
||||
{{ $antrian->updated_at->diffForHumans() }}</p>
|
||||
</div>
|
||||
@elseif($antrian->status == 'sedang_diperiksa')
|
||||
<div class="p-2 rounded-full bg-purple-100 text-purple-600">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm font-medium text-gray-900">Sedang diperiksa</p>
|
||||
<p class="text-xs text-gray-500">{{ $antrian->poli->nama_poli }} -
|
||||
{{ $antrian->updated_at->diffForHumans() }}</p>
|
||||
</div>
|
||||
@elseif($antrian->status == 'batal')
|
||||
<div class="p-2 rounded-full bg-red-100 text-red-600">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm font-medium text-gray-900">Antrian dibatalkan</p>
|
||||
<p class="text-xs text-gray-500">{{ $antrian->poli->nama_poli }} -
|
||||
{{ $antrian->updated_at->diffForHumans() }}</p>
|
||||
</div>
|
||||
@else
|
||||
<div class="p-2 rounded-full bg-gray-100 text-gray-600">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z">
|
||||
</path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm font-medium text-gray-900">Status tidak dikenal</p>
|
||||
<p class="text-xs text-gray-500">{{ $antrian->poli->nama_poli }} -
|
||||
{{ $antrian->updated_at->diffForHumans() }}</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
@else
|
||||
<div class="flex items-center justify-center p-6 bg-gray-50 rounded-lg">
|
||||
<div class="text-center">
|
||||
<svg class="w-8 h-8 mx-auto text-gray-400 mb-2" fill="none"
|
||||
stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z">
|
||||
</path>
|
||||
</svg>
|
||||
<p class="text-gray-500 text-sm">Belum ada aktivitas hari ini</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm font-medium text-gray-900">Antrian baru ditambahkan</p>
|
||||
<p class="text-xs text-gray-500">Poli Umum - 2 menit yang lalu</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center p-4 bg-gray-50 rounded-lg">
|
||||
<div class="p-2 rounded-full bg-green-100 text-green-600">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm font-medium text-gray-900">Antrian selesai</p>
|
||||
<p class="text-xs text-gray-500">Poli Gigi - 5 menit yang lalu</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center p-4 bg-gray-50 rounded-lg">
|
||||
<div class="p-2 rounded-full bg-yellow-100 text-yellow-600">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm font-medium text-gray-900">Antrian dipanggil</p>
|
||||
<p class="text-xs text-gray-500">Poli Jiwa - 8 menit yang lalu</p>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -206,4 +264,121 @@ class="inline-flex items-center px-4 py-2 bg-purple-600 text-white rounded-lg ho
|
|||
</form>
|
||||
|
||||
@include('admin.partials.sidebar-script')
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
// Auto refresh dashboard data every 30 seconds
|
||||
function refreshDashboard() {
|
||||
fetch('{{ route('admin.dashboard.api') }}', {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Update poli counts
|
||||
updatePoliCount('umum', data.data.poliUmumCount);
|
||||
updatePoliCount('gigi', data.data.poliGigiCount);
|
||||
updatePoliCount('jiwa', data.data.poliJiwaCount);
|
||||
updatePoliCount('tradisional', data.data.poliTradisionalCount);
|
||||
|
||||
// Update recent activities
|
||||
updateRecentActivities(data.data.antrianTerbaru);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('Dashboard refresh error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function updatePoliCount(poliType, count) {
|
||||
const countElement = document.querySelector(`[data-poli-count="${poliType}"] .text-2xl`);
|
||||
if (countElement) {
|
||||
countElement.textContent = count;
|
||||
}
|
||||
}
|
||||
|
||||
function updateRecentActivities(activities) {
|
||||
const container = document.querySelector('[data-recent-activity]');
|
||||
if (!container) return;
|
||||
|
||||
if (activities && activities.length > 0) {
|
||||
container.innerHTML = activities.map(antrian => {
|
||||
let iconColor, iconPath, statusText;
|
||||
|
||||
switch (antrian.status) {
|
||||
case 'menunggu':
|
||||
iconColor = 'bg-blue-100 text-blue-600';
|
||||
iconPath = 'M12 6v6m0 0v6m0-6h6m-6 0H6';
|
||||
statusText = 'Antrian baru ditambahkan';
|
||||
break;
|
||||
case 'dipanggil':
|
||||
iconColor = 'bg-yellow-100 text-yellow-600';
|
||||
iconPath = 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z';
|
||||
statusText = 'Antrian dipanggil';
|
||||
break;
|
||||
case 'sedang_diperiksa':
|
||||
iconColor = 'bg-purple-100 text-purple-600';
|
||||
iconPath = 'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z';
|
||||
statusText = 'Sedang diperiksa';
|
||||
break;
|
||||
case 'selesai':
|
||||
iconColor = 'bg-green-100 text-green-600';
|
||||
iconPath = 'M5 13l4 4L19 7';
|
||||
statusText = 'Antrian selesai';
|
||||
break;
|
||||
case 'batal':
|
||||
iconColor = 'bg-red-100 text-red-600';
|
||||
iconPath = 'M6 18L18 6M6 6l12 12';
|
||||
statusText = 'Antrian dibatalkan';
|
||||
break;
|
||||
default:
|
||||
iconColor = 'bg-gray-100 text-gray-600';
|
||||
iconPath =
|
||||
'M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z';
|
||||
statusText = 'Status tidak dikenal';
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="flex items-center p-4 bg-gray-50 rounded-lg">
|
||||
<div class="p-2 rounded-full ${iconColor}">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${iconPath}"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm font-medium text-gray-900">${statusText}</p>
|
||||
<p class="text-xs text-gray-500">${antrian.poli_name} - ${antrian.created_at}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
} else {
|
||||
container.innerHTML = `
|
||||
<div class="flex items-center justify-center p-6 bg-gray-50 rounded-lg">
|
||||
<div class="text-center">
|
||||
<svg class="w-8 h-8 mx-auto text-gray-400 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
<p class="text-gray-500 text-sm">Belum ada aktivitas hari ini</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// Start auto refresh
|
||||
setInterval(refreshDashboard, 30000); // Refresh every 30 seconds
|
||||
|
||||
// Also refresh when page becomes visible (user switches back to tab)
|
||||
document.addEventListener('visibilitychange', function() {
|
||||
if (!document.hidden) {
|
||||
refreshDashboard();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
@endsection
|
||||
|
|
|
@ -4,142 +4,10 @@
|
|||
|
||||
@section('content')
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
<!-- Top Navigation -->
|
||||
<nav class="bg-white shadow-lg sticky top-0 z-40">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between h-16">
|
||||
<div class="flex items-center">
|
||||
<button id="sidebar-toggle" class="lg:hidden text-gray-700 hover:text-primary mr-4">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4 6h16M4 12h16M4 18h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="flex-shrink-0">
|
||||
<h1 class="text-xl md:text-2xl font-bold text-primary">🏥 Admin Puskesmas</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden md:flex items-center space-x-4">
|
||||
|
||||
<span class="text-gray-700">Selamat datang, Admin</span>
|
||||
<button onclick="confirmLogout()"
|
||||
class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-md text-sm font-medium transition duration-200">
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@include('admin.partials.top-nav')
|
||||
|
||||
<div class="flex">
|
||||
<!-- Sidebar -->
|
||||
<aside id="sidebar"
|
||||
class="fixed inset-y-0 left-0 z-50 w-64 bg-white shadow-xl transform -translate-x-full lg:translate-x-0 lg:static lg:inset-0 transition duration-200 ease-in-out">
|
||||
<div class="flex items-center justify-between h-16 px-6 border-b border-gray-200">
|
||||
<h2 class="text-lg font-semibold text-gray-900">Menu Admin</h2>
|
||||
<button id="sidebar-close" class="lg:hidden text-gray-500 hover:text-gray-700">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12">
|
||||
</path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav class="px-6 py-6">
|
||||
<div class="space-y-6">
|
||||
<!-- Dashboard -->
|
||||
<div>
|
||||
<a href="{{ route('admin.dashboard') }}"
|
||||
class="flex items-center px-4 py-3 rounded-xl {{ request()->routeIs('admin.dashboard') ? 'bg-blue-50 text-blue-700 border border-blue-200' : 'text-gray-700 hover:bg-gray-50' }} transition duration-200">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M8 5a2 2 0 012-2h4a2 2 0 012 2v6H8V5z"></path>
|
||||
</svg>
|
||||
<span class="font-medium">Dashboard</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Daftar Antrian -->
|
||||
<div>
|
||||
<div
|
||||
class="flex items-center px-4 py-2 text-sm font-semibold text-gray-500 uppercase tracking-wider">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2">
|
||||
</path>
|
||||
</svg>
|
||||
Daftar Antrian
|
||||
</div>
|
||||
<div class="mt-3 space-y-1">
|
||||
<a href="{{ route('admin.poli.umum') }}"
|
||||
class="flex items-center px-4 py-2 rounded-lg {{ request()->routeIs('admin.poli.umum') ? 'bg-blue-50 text-blue-700' : 'text-gray-600 hover:bg-gray-50' }} transition duration-200">
|
||||
<div class="w-2 h-2 bg-blue-500 rounded-full mr-3"></div>
|
||||
<span class="text-sm">Poli Umum</span>
|
||||
</a>
|
||||
<a href="{{ route('admin.poli.gigi') }}"
|
||||
class="flex items-center px-4 py-2 rounded-lg {{ request()->routeIs('admin.poli.gigi') ? 'bg-green-50 text-green-700' : 'text-gray-600 hover:bg-gray-50' }} transition duration-200">
|
||||
<div class="w-2 h-2 bg-green-500 rounded-full mr-3"></div>
|
||||
<span class="text-sm">Poli Gigi</span>
|
||||
</a>
|
||||
<a href="{{ route('admin.poli.jiwa') }}"
|
||||
class="flex items-center px-4 py-2 rounded-lg {{ request()->routeIs('admin.poli.jiwa') ? 'bg-purple-50 text-purple-700' : 'text-gray-600 hover:bg-gray-50' }} transition duration-200">
|
||||
<div class="w-2 h-2 bg-purple-500 rounded-full mr-3"></div>
|
||||
<span class="text-sm">Poli Jiwa</span>
|
||||
</a>
|
||||
<a href="{{ route('admin.poli.tradisional') }}"
|
||||
class="flex items-center px-4 py-2 rounded-lg {{ request()->routeIs('admin.poli.tradisional') ? 'bg-yellow-50 text-yellow-700' : 'text-gray-600 hover:bg-gray-50' }} transition duration-200">
|
||||
<div class="w-2 h-2 bg-yellow-500 rounded-full mr-3"></div>
|
||||
<span class="text-sm">Poli Tradisional</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Kelola User -->
|
||||
<div>
|
||||
<a href="{{ route('admin.users.index') }}"
|
||||
class="flex items-center px-4 py-3 rounded-xl {{ request()->routeIs('admin.users.*') ? 'bg-blue-50 text-blue-700 border border-blue-200' : 'text-gray-700 hover:bg-gray-50' }} transition duration-200">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z">
|
||||
</path>
|
||||
</svg>
|
||||
<span class="font-medium">Kelola User</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Laporan -->
|
||||
<div>
|
||||
<a href="{{ route('admin.laporan.index') }}"
|
||||
class="flex items-center px-4 py-3 rounded-xl {{ request()->routeIs('admin.laporan.*') ? 'bg-blue-50 text-blue-700 border border-blue-200' : 'text-gray-700 hover:bg-gray-50' }} transition duration-200">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z">
|
||||
</path>
|
||||
</svg>
|
||||
<span class="font-medium">Laporan</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Display -->
|
||||
<div>
|
||||
<a href="{{ route('display') }}"
|
||||
class="flex items-center px-4 py-3 rounded-xl text-gray-700 hover:bg-gray-50 transition duration-200">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z">
|
||||
</path>
|
||||
</svg>
|
||||
<span class="font-medium">Display</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- Overlay for mobile -->
|
||||
<div id="sidebar-overlay" class="fixed inset-0 bg-black bg-opacity-50 z-40 lg:hidden hidden"></div>
|
||||
@include('admin.partials.sidebar')
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="flex-1 lg:ml-0">
|
||||
|
@ -266,7 +134,7 @@ class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shad
|
|||
</div>
|
||||
|
||||
<!-- Statistics Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-5 gap-6 mb-8">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6 gap-6 mb-8">
|
||||
<div class="bg-white rounded-2xl shadow-xl border p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0">
|
||||
|
@ -358,6 +226,24 @@ class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shad
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-xl border p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-8 h-8 bg-red-100 rounded-lg flex items-center justify-center">
|
||||
<svg class="w-5 h-5 text-red-600" fill="none" stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-medium text-gray-600">Batal</p>
|
||||
<p class="text-2xl font-bold text-gray-900">{{ $antrianBatal }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Data Table -->
|
||||
|
@ -427,7 +313,7 @@ class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium b
|
|||
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
||||
Dipanggil
|
||||
</span>
|
||||
@elseif($item->status == 'sedang')
|
||||
@elseif($item->status == 'sedang_diperiksa')
|
||||
<span
|
||||
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800">
|
||||
Sedang
|
||||
|
@ -504,7 +390,7 @@ class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium b
|
|||
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
||||
Dipanggil
|
||||
</span>
|
||||
@elseif($item->status == 'sedang')
|
||||
@elseif($item->status == 'sedang_diperiksa')
|
||||
<span
|
||||
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800">
|
||||
Sedang
|
||||
|
@ -568,6 +454,11 @@ class="text-gray-600">{{ $item->waktu_panggil ? $item->waktu_panggil->format('H:
|
|||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="mt-8">
|
||||
{{ $antrian->withQueryString()->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -661,4 +552,6 @@ function toggleLaporanDetails(id) {
|
|||
}
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@include('admin.partials.sidebar-script')
|
||||
@endsection
|
||||
|
|
|
@ -116,7 +116,16 @@
|
|||
<td>{{ $item->user->no_ktp }}</td>
|
||||
<td>{{ $item->poli->nama_poli }}</td>
|
||||
<td class="status-{{ $item->status }}">
|
||||
{{ ucfirst($item->status) }}
|
||||
@php
|
||||
$statusMap = [
|
||||
'menunggu' => 'Menunggu',
|
||||
'dipanggil' => 'Dipanggil',
|
||||
'sedang_diperiksa' => 'Sedang Diperiksa',
|
||||
'selesai' => 'Selesai',
|
||||
'batal' => 'Batal',
|
||||
];
|
||||
@endphp
|
||||
{{ $statusMap[$item->status] ?? ucfirst(str_replace('_', ' ', $item->status)) }}
|
||||
</td>
|
||||
<td>{{ $item->created_at ? $item->created_at->format('d/m/Y') : '-' }}</td>
|
||||
<td>{{ $item->created_at ? $item->created_at->format('H:i') : '-' }}</td>
|
||||
|
|
|
@ -1,49 +1,113 @@
|
|||
<script>
|
||||
// Sidebar functionality
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const sidebarToggle = document.getElementById('sidebar-toggle');
|
||||
const sidebarClose = document.getElementById('sidebar-close');
|
||||
const sidebarOverlay = document.getElementById('sidebar-overlay');
|
||||
const mainContent = document.querySelector('.flex-1');
|
||||
// Sidebar functionality
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const sidebarToggle = document.getElementById('sidebar-toggle');
|
||||
const sidebarClose = document.getElementById('sidebar-close');
|
||||
const sidebarOverlay = document.getElementById('sidebar-overlay');
|
||||
|
||||
// Toggle sidebar on mobile
|
||||
if (sidebarToggle) {
|
||||
sidebarToggle.addEventListener('click', function() {
|
||||
sidebar.classList.remove('-translate-x-full');
|
||||
sidebarOverlay.classList.remove('hidden');
|
||||
// Toggle sidebar on mobile
|
||||
if (sidebarToggle) {
|
||||
sidebarToggle.addEventListener('click', function() {
|
||||
sidebar.classList.remove('-translate-x-full');
|
||||
sidebarOverlay.classList.remove('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// Close sidebar on mobile
|
||||
if (sidebarClose) {
|
||||
sidebarClose.addEventListener('click', function() {
|
||||
sidebar.classList.add('-translate-x-full');
|
||||
sidebarOverlay.classList.add('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// Close sidebar when clicking overlay
|
||||
if (sidebarOverlay) {
|
||||
sidebarOverlay.addEventListener('click', function() {
|
||||
sidebar.classList.add('-translate-x-full');
|
||||
sidebarOverlay.classList.add('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// Close sidebar on escape key
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
sidebar.classList.add('-translate-x-full');
|
||||
sidebarOverlay.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Close sidebar on mobile
|
||||
if (sidebarClose) {
|
||||
sidebarClose.addEventListener('click', function() {
|
||||
sidebar.classList.add('-translate-x-full');
|
||||
sidebarOverlay.classList.add('hidden');
|
||||
});
|
||||
}
|
||||
// Mobile menu dropdown functionality
|
||||
const mobileMenuButton = document.getElementById('mobile-menu-button');
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
|
||||
// Close sidebar when clicking overlay
|
||||
if (sidebarOverlay) {
|
||||
sidebarOverlay.addEventListener('click', function() {
|
||||
sidebar.classList.add('-translate-x-full');
|
||||
sidebarOverlay.classList.add('hidden');
|
||||
});
|
||||
}
|
||||
if (mobileMenuButton && mobileMenu) {
|
||||
mobileMenuButton.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
mobileMenu.classList.toggle('hidden');
|
||||
});
|
||||
|
||||
// Close sidebar on escape key
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
sidebar.classList.add('-translate-x-full');
|
||||
sidebarOverlay.classList.add('hidden');
|
||||
// Close mobile menu when clicking outside
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!mobileMenuButton.contains(e.target) && !mobileMenu.contains(e.target)) {
|
||||
mobileMenu.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// Close mobile menu on escape key
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
mobileMenu.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Logout confirmation
|
||||
function confirmLogout() {
|
||||
if (confirm('Apakah Anda yakin ingin keluar?')) {
|
||||
document.getElementById('logout-form').submit();
|
||||
// Logout confirmation
|
||||
function confirmLogout() {
|
||||
Swal.fire({
|
||||
title: 'Konfirmasi Logout',
|
||||
text: 'Apakah Anda yakin ingin keluar dari sistem?',
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#d33',
|
||||
cancelButtonColor: '#3085d6',
|
||||
confirmButtonText: 'Ya, Keluar',
|
||||
cancelButtonText: 'Batal',
|
||||
customClass: {
|
||||
popup: 'rounded-lg',
|
||||
confirmButton: 'btn btn-danger px-4 py-2 rounded-md',
|
||||
cancelButton: 'btn btn-secondary px-4 py-2 rounded-md'
|
||||
}
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
// Show loading
|
||||
Swal.fire({
|
||||
title: 'Logging out...',
|
||||
text: 'Mohon tunggu sebentar',
|
||||
icon: 'info',
|
||||
allowOutsideClick: false,
|
||||
showConfirmButton: false,
|
||||
willOpen: () => {
|
||||
Swal.showLoading();
|
||||
}
|
||||
});
|
||||
|
||||
// Create and submit logout form
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = '{{ route('logout') }}';
|
||||
|
||||
const csrfToken = document.createElement('input');
|
||||
csrfToken.type = 'hidden';
|
||||
csrfToken.name = '_token';
|
||||
csrfToken.value = '{{ csrf_token() }}';
|
||||
|
||||
form.appendChild(csrfToken);
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- Sidebar -->
|
||||
<aside id="sidebar"
|
||||
class="fixed inset-y-0 left-0 z-50 w-64 bg-white shadow-xl transform -translate-x-full lg:translate-x-0 lg:static lg:inset-0 transition duration-200 ease-in-out">
|
||||
class="fixed inset-y-0 left-0 z-40 w-64 bg-white shadow-xl transform -translate-x-full lg:translate-x-0 lg:static lg:inset-0 transition duration-200 ease-in-out">
|
||||
<div class="flex items-center justify-between h-16 px-6 border-b border-gray-200">
|
||||
<h2 class="text-lg font-semibold text-gray-900">Menu Admin</h2>
|
||||
<button id="sidebar-close" class="lg:hidden text-gray-500 hover:text-gray-700">
|
||||
|
@ -99,11 +99,9 @@ class="flex items-center px-4 py-3 rounded-xl {{ request()->routeIs('admin.lapor
|
|||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Audio Management -->
|
||||
<!-- Audio Management - HIDDEN FOR NOW -->
|
||||
<!-- Uncomment section below to re-enable Audio Management -->
|
||||
<!--
|
||||
<div>
|
||||
<a href="{{ route('admin.audio.index') }}"
|
||||
class="flex items-center px-4 py-3 rounded-xl {{ request()->routeIs('admin.audio.*') ? 'bg-blue-50 text-blue-700 border border-blue-200' : 'text-gray-700 hover:bg-gray-50' }} transition duration-200">
|
||||
|
@ -115,6 +113,7 @@ class="flex items-center px-4 py-3 rounded-xl {{ request()->routeIs('admin.audio
|
|||
<span class="font-medium">Audio Management</span>
|
||||
</a>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<!-- Display -->
|
||||
<div>
|
||||
|
@ -129,8 +128,21 @@ class="flex items-center px-4 py-3 rounded-xl text-gray-700 hover:bg-gray-50 tra
|
|||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logout Section (Mobile Only) -->
|
||||
<div class="lg:hidden mt-6 pt-6 border-t border-gray-200">
|
||||
<button onclick="confirmLogout()"
|
||||
class="flex items-center w-full px-4 py-3 rounded-xl text-red-600 hover:bg-red-50 transition duration-200">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1">
|
||||
</path>
|
||||
</svg>
|
||||
<span class="font-medium">Logout</span>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- Overlay for mobile -->
|
||||
<div id="sidebar-overlay" class="fixed inset-0 bg-black bg-opacity-50 z-40 lg:hidden hidden"></div>
|
||||
<div id="sidebar-overlay" class="fixed inset-0 bg-black bg-opacity-50 z-30 lg:hidden hidden"></div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<!-- Top Navigation -->
|
||||
<nav class="bg-white shadow-lg sticky top-0 z-40">
|
||||
<nav class="bg-white shadow-lg sticky top-0 z-50">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between h-16">
|
||||
<div class="flex items-center">
|
||||
|
@ -13,14 +13,44 @@
|
|||
<h1 class="text-xl md:text-2xl font-bold text-primary">🏥 Admin Puskesmas</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Desktop Navigation -->
|
||||
<div class="hidden md:flex items-center space-x-4">
|
||||
|
||||
<span class="text-gray-700">Selamat datang, Admin</span>
|
||||
<button onclick="confirmLogout()"
|
||||
class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-md text-sm font-medium transition duration-200">
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Navigation -->
|
||||
<div class="md:hidden flex items-center">
|
||||
<div class="relative">
|
||||
<button id="mobile-menu-button" class="text-gray-700 hover:text-primary p-2 rounded-md">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Mobile Dropdown Menu -->
|
||||
<div id="mobile-menu"
|
||||
class="hidden absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-50">
|
||||
<div class="px-4 py-2 text-sm text-gray-700 border-b">
|
||||
<span class="font-medium">Admin</span>
|
||||
</div>
|
||||
<button onclick="confirmLogout()"
|
||||
class="block w-full text-left px-4 py-2 text-sm text-red-600 hover:bg-red-50 transition duration-200">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1">
|
||||
</path>
|
||||
</svg>
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
|
|
@ -2,144 +2,51 @@
|
|||
|
||||
@section('title', $title)
|
||||
|
||||
@push('styles')
|
||||
<style>
|
||||
/* Tooltip styles */
|
||||
[title]:hover {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
[title]:hover:after {
|
||||
content: attr(title);
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: white;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
max-width: 300px;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
[title]:hover:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(100%);
|
||||
border: 5px solid transparent;
|
||||
border-top-color: rgba(0, 0, 0, 0.8);
|
||||
z-index: 1000;
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
<!-- Top Navigation -->
|
||||
<nav class="bg-white shadow-lg sticky top-0 z-40">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between h-16">
|
||||
<div class="flex items-center">
|
||||
<button id="sidebar-toggle" class="lg:hidden text-gray-700 hover:text-primary mr-4">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4 6h16M4 12h16M4 18h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="flex-shrink-0">
|
||||
<h1 class="text-xl md:text-2xl font-bold text-primary">🏥 Admin Puskesmas</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden md:flex items-center space-x-4">
|
||||
|
||||
<span class="text-gray-700">Selamat datang, Admin</span>
|
||||
<button onclick="confirmLogout()"
|
||||
class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-md text-sm font-medium transition duration-200">
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@include('admin.partials.top-nav')
|
||||
|
||||
<div class="flex">
|
||||
<!-- Sidebar -->
|
||||
<aside id="sidebar"
|
||||
class="fixed inset-y-0 left-0 z-50 w-64 bg-white shadow-xl transform -translate-x-full lg:translate-x-0 lg:static lg:inset-0 transition duration-200 ease-in-out">
|
||||
<div class="flex items-center justify-between h-16 px-6 border-b border-gray-200">
|
||||
<h2 class="text-lg font-semibold text-gray-900">Menu Admin</h2>
|
||||
<button id="sidebar-close" class="lg:hidden text-gray-500 hover:text-gray-700">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12">
|
||||
</path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav class="px-6 py-6">
|
||||
<div class="space-y-6">
|
||||
<!-- Dashboard -->
|
||||
<div>
|
||||
<a href="{{ route('admin.dashboard') }}"
|
||||
class="flex items-center px-4 py-3 rounded-xl {{ request()->routeIs('admin.dashboard') ? 'bg-blue-50 text-blue-700 border border-blue-200' : 'text-gray-700 hover:bg-gray-50' }} transition duration-200">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M8 5a2 2 0 012-2h4a2 2 0 012 2v6H8V5z"></path>
|
||||
</svg>
|
||||
<span class="font-medium">Dashboard</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Daftar Antrian -->
|
||||
<div>
|
||||
<div
|
||||
class="flex items-center px-4 py-2 text-sm font-semibold text-gray-500 uppercase tracking-wider">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2">
|
||||
</path>
|
||||
</svg>
|
||||
Daftar Antrian
|
||||
</div>
|
||||
<div class="mt-3 space-y-1">
|
||||
<a href="{{ route('admin.poli.umum') }}"
|
||||
class="flex items-center px-4 py-2 rounded-lg {{ request()->routeIs('admin.poli.umum') ? 'bg-blue-50 text-blue-700' : 'text-gray-600 hover:bg-gray-50' }} transition duration-200">
|
||||
<div class="w-2 h-2 bg-blue-500 rounded-full mr-3"></div>
|
||||
<span class="text-sm">Poli Umum</span>
|
||||
</a>
|
||||
<a href="{{ route('admin.poli.gigi') }}"
|
||||
class="flex items-center px-4 py-2 rounded-lg {{ request()->routeIs('admin.poli.gigi') ? 'bg-green-50 text-green-700' : 'text-gray-600 hover:bg-gray-50' }} transition duration-200">
|
||||
<div class="w-2 h-2 bg-green-500 rounded-full mr-3"></div>
|
||||
<span class="text-sm">Poli Gigi</span>
|
||||
</a>
|
||||
<a href="{{ route('admin.poli.jiwa') }}"
|
||||
class="flex items-center px-4 py-2 rounded-lg {{ request()->routeIs('admin.poli.jiwa') ? 'bg-purple-50 text-purple-700' : 'text-gray-600 hover:bg-gray-50' }} transition duration-200">
|
||||
<div class="w-2 h-2 bg-purple-500 rounded-full mr-3"></div>
|
||||
<span class="text-sm">Poli Jiwa</span>
|
||||
</a>
|
||||
<a href="{{ route('admin.poli.tradisional') }}"
|
||||
class="flex items-center px-4 py-2 rounded-lg {{ request()->routeIs('admin.poli.tradisional') ? 'bg-yellow-50 text-yellow-700' : 'text-gray-600 hover:bg-gray-50' }} transition duration-200">
|
||||
<div class="w-2 h-2 bg-yellow-500 rounded-full mr-3"></div>
|
||||
<span class="text-sm">Poli Tradisional</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Kelola User -->
|
||||
<div>
|
||||
<a href="{{ route('admin.users.index') }}"
|
||||
class="flex items-center px-4 py-3 rounded-xl {{ request()->routeIs('admin.users.*') ? 'bg-blue-50 text-blue-700 border border-blue-200' : 'text-gray-700 hover:bg-gray-50' }} transition duration-200">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z">
|
||||
</path>
|
||||
</svg>
|
||||
<span class="font-medium">Kelola User</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Laporan -->
|
||||
<div>
|
||||
<a href="{{ route('admin.laporan.index') }}"
|
||||
class="flex items-center px-4 py-3 rounded-xl {{ request()->routeIs('admin.laporan.*') ? 'bg-blue-50 text-blue-700 border border-blue-200' : 'text-gray-700 hover:bg-gray-50' }} transition duration-200">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z">
|
||||
</path>
|
||||
</svg>
|
||||
<span class="font-medium">Laporan</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Display -->
|
||||
<div>
|
||||
<a href="{{ route('display') }}"
|
||||
class="flex items-center px-4 py-3 rounded-xl text-gray-700 hover:bg-gray-50 transition duration-200">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z">
|
||||
</path>
|
||||
</svg>
|
||||
<span class="font-medium">Display</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- Overlay for mobile -->
|
||||
<div id="sidebar-overlay" class="fixed inset-0 bg-black bg-opacity-50 z-40 lg:hidden hidden"></div>
|
||||
@include('admin.partials.sidebar')
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="flex-1 lg:ml-0">
|
||||
|
@ -158,28 +65,28 @@ class="flex items-center px-4 py-3 rounded-xl text-gray-700 hover:bg-gray-50 tra
|
|||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-20">
|
||||
No Antrian</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Nama</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-32">
|
||||
Alamat</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Jenis Kelamin</th>
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-24">
|
||||
JK</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Nomor HP</th>
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-28">
|
||||
No HP</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Nomor KTP</th>
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-32">
|
||||
No KTP</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-24">
|
||||
Status</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-32">
|
||||
Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -190,9 +97,15 @@ class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-
|
|||
{{ $antrian->no_antrian }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-gray-900">
|
||||
{{ $antrian->user?->nama }}</td>
|
||||
<td class="px-6 py-4 text-gray-700">{{ $antrian->user?->alamat }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-gray-700">
|
||||
{{ $antrian->user?->jenis_kelamin }}</td>
|
||||
<td class="px-6 py-4 text-gray-700 max-w-32"
|
||||
title="{{ $antrian->user?->alamat }}">
|
||||
<div class="truncate">
|
||||
{{ Str::limit($antrian->user?->alamat, 30) }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-gray-700"
|
||||
title="{{ $antrian->user?->jenis_kelamin }}">
|
||||
{{ $antrian->user?->jenis_kelamin == 'laki-laki' ? 'L' : 'P' }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-gray-700">
|
||||
{{ $antrian->user?->no_hp }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-gray-700">
|
||||
|
@ -208,6 +121,11 @@ class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-yellow-100 te
|
|||
class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-blue-100 text-blue-800">
|
||||
Dipanggil
|
||||
</span>
|
||||
@elseif($antrian->status == 'sedang_diperiksa')
|
||||
<span
|
||||
class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-purple-100 text-purple-800">
|
||||
Sedang Diperiksa
|
||||
</span>
|
||||
@elseif($antrian->status == 'selesai')
|
||||
<span
|
||||
class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800">
|
||||
|
@ -228,17 +146,38 @@ class="bg-blue-500 hover:bg-blue-600 text-white px-3 py-1 rounded-md text-xs fon
|
|||
Panggil
|
||||
</button>
|
||||
@elseif($antrian->status == 'dipanggil')
|
||||
<div class="flex flex-col space-y-1">
|
||||
@if (!$antrian->waktu_hadir)
|
||||
<button onclick="konfirmasiKehadiran({{ $antrian->id }})"
|
||||
class="bg-orange-500 hover:bg-orange-600 text-white px-2 py-1 rounded-md text-xs font-medium transition duration-200">
|
||||
Pasien Hadir
|
||||
</button>
|
||||
@else
|
||||
<button onclick="mulaiPemeriksaan({{ $antrian->id }})"
|
||||
class="bg-purple-500 hover:bg-purple-600 text-white px-2 py-1 rounded-md text-xs font-medium transition duration-200">
|
||||
Mulai Periksa
|
||||
</button>
|
||||
@endif
|
||||
<div class="flex space-x-1">
|
||||
<button
|
||||
onclick="selesai('{{ route('admin.selesai-antrian') }}', {{ $antrian->id }})"
|
||||
class="bg-green-500 hover:bg-green-600 text-white px-2 py-1 rounded-md text-xs font-medium transition duration-200">
|
||||
Selesai
|
||||
</button>
|
||||
<button
|
||||
onclick="batal('{{ route('admin.batal-antrian') }}', {{ $antrian->id }})"
|
||||
class="bg-red-500 hover:bg-red-600 text-white px-2 py-1 rounded-md text-xs font-medium transition duration-200">
|
||||
Batal
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@elseif($antrian->status == 'sedang_diperiksa')
|
||||
<div class="flex space-x-2">
|
||||
<button
|
||||
onclick="selesai('{{ route('admin.selesai-antrian') }}', {{ $antrian->id }})"
|
||||
class="bg-green-500 hover:bg-green-600 text-white px-3 py-1 rounded-md text-xs font-medium transition duration-200">
|
||||
Selesai
|
||||
</button>
|
||||
<button
|
||||
onclick="batal('{{ route('admin.batal-antrian') }}', {{ $antrian->id }})"
|
||||
class="bg-red-500 hover:bg-red-600 text-white px-3 py-1 rounded-md text-xs font-medium transition duration-200">
|
||||
Batal
|
||||
</button>
|
||||
</div>
|
||||
@else
|
||||
<span class="text-gray-400 text-xs">-</span>
|
||||
|
@ -291,6 +230,11 @@ class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-yellow-100 te
|
|||
class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-blue-100 text-blue-800">
|
||||
Dipanggil
|
||||
</span>
|
||||
@elseif($antrian->status == 'sedang_diperiksa')
|
||||
<span
|
||||
class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-purple-100 text-purple-800">
|
||||
Sedang Diperiksa
|
||||
</span>
|
||||
@elseif($antrian->status == 'selesai')
|
||||
<span
|
||||
class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800">
|
||||
|
@ -312,17 +256,38 @@ class="bg-blue-500 hover:bg-blue-600 text-white px-3 py-1 rounded-md text-xs fon
|
|||
Panggil
|
||||
</button>
|
||||
@elseif($antrian->status == 'dipanggil')
|
||||
<div class="flex flex-col space-y-1">
|
||||
@if (!$antrian->waktu_hadir)
|
||||
<button onclick="konfirmasiKehadiran({{ $antrian->id }})"
|
||||
class="bg-orange-500 hover:bg-orange-600 text-white px-2 py-1 rounded-md text-xs font-medium transition duration-200">
|
||||
Pasien Hadir
|
||||
</button>
|
||||
@else
|
||||
<button onclick="mulaiPemeriksaan({{ $antrian->id }})"
|
||||
class="bg-purple-500 hover:bg-purple-600 text-white px-2 py-1 rounded-md text-xs font-medium transition duration-200">
|
||||
Mulai Periksa
|
||||
</button>
|
||||
@endif
|
||||
<div class="flex space-x-1">
|
||||
<button
|
||||
onclick="selesai('{{ route('admin.selesai-antrian') }}', {{ $antrian->id }})"
|
||||
class="bg-green-500 hover:bg-green-600 text-white px-2 py-1 rounded-md text-xs font-medium transition duration-200">
|
||||
Selesai
|
||||
</button>
|
||||
<button
|
||||
onclick="batal('{{ route('admin.batal-antrian') }}', {{ $antrian->id }})"
|
||||
class="bg-red-500 hover:bg-red-600 text-white px-2 py-1 rounded-md text-xs font-medium transition duration-200">
|
||||
Batal
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@elseif($antrian->status == 'sedang_diperiksa')
|
||||
<div class="flex space-x-2">
|
||||
<button
|
||||
onclick="selesai('{{ route('admin.selesai-antrian') }}', {{ $antrian->id }})"
|
||||
class="bg-green-500 hover:bg-green-600 text-white px-3 py-1 rounded-md text-xs font-medium transition duration-200">
|
||||
Selesai
|
||||
</button>
|
||||
<button
|
||||
onclick="batal('{{ route('admin.batal-antrian') }}', {{ $antrian->id }})"
|
||||
class="bg-red-500 hover:bg-red-600 text-white px-3 py-1 rounded-md text-xs font-medium transition duration-200">
|
||||
Batal
|
||||
</button>
|
||||
</div>
|
||||
@else
|
||||
<span class="text-gray-400 text-xs">-</span>
|
||||
|
@ -348,7 +313,10 @@ class="w-4 h-4 ml-1 transform transition-transform" fill="none"
|
|||
<div class="grid grid-cols-1 gap-2 text-sm">
|
||||
<div>
|
||||
<span class="font-medium text-gray-700">Alamat:</span>
|
||||
<span class="text-gray-600">{{ $antrian->user?->alamat }}</span>
|
||||
<span class="text-gray-600 block mt-1"
|
||||
title="{{ $antrian->user?->alamat }}">
|
||||
{{ Str::limit($antrian->user?->alamat, 50) }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-medium text-gray-700">Jenis Kelamin:</span>
|
||||
|
@ -380,6 +348,11 @@ class="text-gray-600">{{ $antrian->user?->jenis_kelamin }}</span>
|
|||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="mt-8">
|
||||
{{ $antrians->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -679,6 +652,80 @@ function batal(url, antrianId) {
|
|||
});
|
||||
}
|
||||
|
||||
function konfirmasiKehadiran(antrianId) {
|
||||
Swal.fire({
|
||||
title: 'Konfirmasi Kehadiran Pasien?',
|
||||
text: 'Pasien sudah datang dan siap untuk diperiksa?',
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Ya, Pasien Hadir',
|
||||
cancelButtonText: 'Batal'
|
||||
}).then((res) => {
|
||||
if (!res.isConfirmed) return;
|
||||
fetch('{{ route('admin.konfirmasi-kehadiran') }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
antrian_id: antrianId
|
||||
})
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
Swal.fire({
|
||||
icon: d.success ? 'success' : 'warning',
|
||||
title: d.success ? 'Berhasil' : 'Gagal',
|
||||
text: d.message
|
||||
})
|
||||
.then(() => location.reload());
|
||||
})
|
||||
.catch(() => Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: 'Terjadi kesalahan'
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
function mulaiPemeriksaan(antrianId) {
|
||||
Swal.fire({
|
||||
title: 'Mulai Pemeriksaan?',
|
||||
text: 'Pasien akan dipindah ke status sedang diperiksa',
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Ya, Mulai Periksa',
|
||||
cancelButtonText: 'Batal'
|
||||
}).then((res) => {
|
||||
if (!res.isConfirmed) return;
|
||||
fetch('{{ route('admin.mulai-pemeriksaan') }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
antrian_id: antrianId
|
||||
})
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
Swal.fire({
|
||||
icon: d.success ? 'success' : 'warning',
|
||||
title: d.success ? 'Berhasil' : 'Gagal',
|
||||
text: d.message
|
||||
})
|
||||
.then(() => location.reload());
|
||||
})
|
||||
.catch(() => Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: 'Terjadi kesalahan'
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
function confirmLogout() {
|
||||
Swal.fire({
|
||||
title: 'Konfirmasi Logout',
|
||||
|
@ -724,4 +771,6 @@ function toggleDetails(antrianId) {
|
|||
}
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@include('admin.partials.sidebar-script')
|
||||
@endsection
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
@section('content')
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
<!-- Top Navigation -->
|
||||
<nav class="bg-white shadow-lg sticky top-0 z-40">
|
||||
<nav class="bg-white shadow-lg sticky top-0 z-50">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between h-16">
|
||||
<div class="flex items-center">
|
||||
|
@ -20,7 +20,6 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="hidden md:flex items-center space-x-4">
|
||||
|
||||
<span class="text-gray-700">Selamat datang, Admin</span>
|
||||
<button onclick="confirmLogout()"
|
||||
class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-md text-sm font-medium transition duration-200">
|
||||
|
@ -32,107 +31,7 @@ class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-md text-sm font-
|
|||
</nav>
|
||||
|
||||
<div class="flex">
|
||||
<!-- Sidebar -->
|
||||
<aside id="sidebar"
|
||||
class="fixed inset-y-0 left-0 z-50 w-64 bg-white shadow-xl transform -translate-x-full lg:translate-x-0 lg:static lg:inset-0 transition duration-200 ease-in-out">
|
||||
<div class="flex items-center justify-between h-16 px-6 border-b border-gray-200">
|
||||
<h2 class="text-lg font-semibold text-gray-900">Menu Admin</h2>
|
||||
<button id="sidebar-close" class="lg:hidden text-gray-500 hover:text-gray-700">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12">
|
||||
</path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav class="px-6 py-6">
|
||||
<div class="space-y-6">
|
||||
<!-- Dashboard -->
|
||||
<div>
|
||||
<a href="{{ route('admin.dashboard') }}"
|
||||
class="flex items-center px-4 py-3 rounded-xl {{ request()->routeIs('admin.dashboard') ? 'bg-blue-50 text-blue-700 border border-blue-200' : 'text-gray-700 hover:bg-gray-50' }} transition duration-200">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M8 5a2 2 0 012-2h4a2 2 0 012 2v6H8V5z"></path>
|
||||
</svg>
|
||||
<span class="font-medium">Dashboard</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Daftar Antrian -->
|
||||
<div>
|
||||
<div
|
||||
class="flex items-center px-4 py-2 text-sm font-semibold text-gray-500 uppercase tracking-wider">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2">
|
||||
</path>
|
||||
</svg>
|
||||
Daftar Antrian
|
||||
</div>
|
||||
<div class="mt-3 space-y-1">
|
||||
<a href="{{ route('admin.poli.umum') }}"
|
||||
class="flex items-center px-4 py-2 rounded-lg {{ request()->routeIs('admin.poli.umum') ? 'bg-blue-50 text-blue-700' : 'text-gray-600 hover:bg-gray-50' }} transition duration-200">
|
||||
<div class="w-2 h-2 bg-blue-500 rounded-full mr-3"></div>
|
||||
<span class="text-sm">Poli Umum</span>
|
||||
</a>
|
||||
<a href="{{ route('admin.poli.gigi') }}"
|
||||
class="flex items-center px-4 py-2 rounded-lg {{ request()->routeIs('admin.poli.gigi') ? 'bg-green-50 text-green-700' : 'text-gray-600 hover:bg-gray-50' }} transition duration-200">
|
||||
<div class="w-2 h-2 bg-green-500 rounded-full mr-3"></div>
|
||||
<span class="text-sm">Poli Gigi</span>
|
||||
</a>
|
||||
<a href="{{ route('admin.poli.jiwa') }}"
|
||||
class="flex items-center px-4 py-2 rounded-lg {{ request()->routeIs('admin.poli.jiwa') ? 'bg-purple-50 text-purple-700' : 'text-gray-600 hover:bg-gray-50' }} transition duration-200">
|
||||
<div class="w-2 h-2 bg-purple-500 rounded-full mr-3"></div>
|
||||
<span class="text-sm">Poli Jiwa</span>
|
||||
</a>
|
||||
<a href="{{ route('admin.poli.tradisional') }}"
|
||||
class="flex items-center px-4 py-2 rounded-lg {{ request()->routeIs('admin.poli.tradisional') ? 'bg-yellow-50 text-yellow-700' : 'text-gray-600 hover:bg-gray-50' }} transition duration-200">
|
||||
<div class="w-2 h-2 bg-yellow-500 rounded-full mr-3"></div>
|
||||
<span class="text-sm">Poli Tradisional</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Kelola User -->
|
||||
<div>
|
||||
<div
|
||||
class="flex items-center px-4 py-2 text-sm font-semibold text-gray-500 uppercase tracking-wider">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z">
|
||||
</path>
|
||||
</svg>
|
||||
Kelola User
|
||||
</div>
|
||||
<div class="mt-3 space-y-1">
|
||||
<a href="{{ route('admin.users.index') }}"
|
||||
class="flex items-center px-4 py-2 rounded-lg {{ request()->routeIs('admin.users.*') ? 'bg-blue-50 text-blue-700' : 'text-gray-600 hover:bg-gray-50' }} transition duration-200">
|
||||
<div class="w-2 h-2 bg-blue-500 rounded-full mr-3"></div>
|
||||
<span class="text-sm">Daftar User</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Laporan -->
|
||||
<div>
|
||||
<a href="{{ route('admin.laporan.index') }}"
|
||||
class="flex items-center px-4 py-3 rounded-xl {{ request()->routeIs('admin.laporan.*') ? 'bg-blue-50 text-blue-700 border border-blue-200' : 'text-gray-700 hover:bg-gray-50' }} transition duration-200">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z">
|
||||
</path>
|
||||
</svg>
|
||||
<span class="font-medium">Laporan</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
@include('admin.partials.sidebar')
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="flex-1 lg:ml-64">
|
||||
|
@ -179,8 +78,7 @@ class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shad
|
|||
<div>
|
||||
<label for="nama" class="block text-sm font-medium text-gray-700 mb-2">Nama
|
||||
Lengkap <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="nama" id="nama" value="{{ old('nama') }}"
|
||||
required
|
||||
<input type="text" name="nama" id="nama" value="{{ old('nama') }}" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
</div>
|
||||
|
||||
|
@ -189,7 +87,8 @@ class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outlin
|
|||
<label for="no_ktp" class="block text-sm font-medium text-gray-700 mb-2">Nomor KTP
|
||||
<span class="text-red-500">*</span></label>
|
||||
<input type="text" name="no_ktp" id="no_ktp" value="{{ old('no_ktp') }}"
|
||||
maxlength="16" required
|
||||
maxlength="16" required pattern="[0-9]*" inputmode="numeric"
|
||||
oninput="this.value = this.value.replace(/[^0-9]/g, '')"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
placeholder="16 digit angka">
|
||||
</div>
|
||||
|
@ -212,8 +111,9 @@ class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outlin
|
|||
<div>
|
||||
<label for="no_hp" class="block text-sm font-medium text-gray-700 mb-2">Nomor HP
|
||||
<span class="text-red-500">*</span></label>
|
||||
<input type="text" name="no_hp" id="no_hp" value="{{ old('no_hp') }}"
|
||||
required
|
||||
<input type="text" name="no_hp" id="no_hp" value="{{ old('no_hp') }}" required
|
||||
pattern="[0-9]*" inputmode="numeric"
|
||||
oninput="this.value = this.value.replace(/[^0-9]/g, '')"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
</div>
|
||||
|
||||
|
@ -221,8 +121,8 @@ class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outlin
|
|||
<div>
|
||||
<label for="pekerjaan" class="block text-sm font-medium text-gray-700 mb-2">Pekerjaan
|
||||
<span class="text-red-500">*</span></label>
|
||||
<input type="text" name="pekerjaan" id="pekerjaan"
|
||||
value="{{ old('pekerjaan') }}" required
|
||||
<input type="text" name="pekerjaan" id="pekerjaan" value="{{ old('pekerjaan') }}"
|
||||
required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
</div>
|
||||
|
||||
|
|
|
@ -108,6 +108,8 @@ class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:rin
|
|||
<label for="edit_no_hp" class="block text-sm font-medium text-gray-700 mb-2">Nomor
|
||||
HP</label>
|
||||
<input type="tel" name="no_hp" id="edit_no_hp" value="{{ $user->no_hp }}" required
|
||||
pattern="[0-9]*" inputmode="numeric"
|
||||
oninput="this.value = this.value.replace(/[^0-9]/g, '')"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition duration-200">
|
||||
</div>
|
||||
|
||||
|
@ -115,6 +117,8 @@ class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:rin
|
|||
<label for="edit_no_ktp" class="block text-sm font-medium text-gray-700 mb-2">Nomor
|
||||
KTP</label>
|
||||
<input type="text" name="no_ktp" id="edit_no_ktp" value="{{ $user->no_ktp }}" required
|
||||
pattern="[0-9]*" inputmode="numeric"
|
||||
oninput="this.value = this.value.replace(/[^0-9]/g, '')"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition duration-200">
|
||||
</div>
|
||||
|
||||
|
@ -232,24 +236,38 @@ class="px-6 py-3 bg-red-600 hover:bg-red-700 text-white rounded-lg font-medium t
|
|||
<h2 class="text-xl font-semibold text-gray-900">Statistik Antrian User</h2>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
|
||||
<div class="bg-gradient-to-br from-blue-50 to-blue-100 rounded-xl p-4 border border-blue-200">
|
||||
<div class="text-2xl font-bold text-blue-600">
|
||||
{{ $user->antrians->where('status', 'menunggu')->count() }}
|
||||
</div>
|
||||
<div class="text-sm text-blue-700 font-medium">Antrian Menunggu</div>
|
||||
<div class="text-sm text-blue-700 font-medium">Menunggu</div>
|
||||
</div>
|
||||
<div
|
||||
class="bg-gradient-to-br from-yellow-50 to-yellow-100 rounded-xl p-4 border border-yellow-200">
|
||||
<div class="text-2xl font-bold text-yellow-600">
|
||||
{{ $user->antrians->where('status', 'dipanggil')->count() }}
|
||||
</div>
|
||||
<div class="text-sm text-yellow-700 font-medium">Dipanggil</div>
|
||||
</div>
|
||||
<div
|
||||
class="bg-gradient-to-br from-purple-50 to-purple-100 rounded-xl p-4 border border-purple-200">
|
||||
<div class="text-2xl font-bold text-purple-600">
|
||||
{{ $user->antrians->where('status', 'sedang_diperiksa')->count() }}
|
||||
</div>
|
||||
<div class="text-sm text-purple-700 font-medium">Sedang Periksa</div>
|
||||
</div>
|
||||
<div class="bg-gradient-to-br from-green-50 to-green-100 rounded-xl p-4 border border-green-200">
|
||||
<div class="text-2xl font-bold text-green-600">
|
||||
{{ $user->antrians->where('status', 'selesai')->count() }}
|
||||
</div>
|
||||
<div class="text-sm text-green-700 font-medium">Antrian Selesai</div>
|
||||
<div class="text-sm text-green-700 font-medium">Selesai</div>
|
||||
</div>
|
||||
<div class="bg-gradient-to-br from-red-50 to-red-100 rounded-xl p-4 border border-red-200">
|
||||
<div class="text-2xl font-bold text-red-600">
|
||||
{{ $user->antrians->where('status', 'batal')->count() }}
|
||||
</div>
|
||||
<div class="text-sm text-red-700 font-medium">Antrian Batal</div>
|
||||
<div class="text-sm text-red-700 font-medium">Batal</div>
|
||||
</div>
|
||||
<div class="bg-gradient-to-br from-gray-50 to-gray-100 rounded-xl p-4 border border-gray-200">
|
||||
<div class="text-2xl font-bold text-gray-600">
|
||||
|
@ -287,7 +305,7 @@ class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
@foreach ($user->antrians->take(5) as $antrian)
|
||||
@forelse ($user->antrians->take(5) as $antrian)
|
||||
<tr class="hover:bg-gray-50 transition duration-200">
|
||||
<td class="px-4 py-3 whitespace-nowrap">
|
||||
<span
|
||||
|
@ -310,6 +328,11 @@ class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-yellow-100 te
|
|||
class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-blue-100 text-blue-800">
|
||||
Dipanggil
|
||||
</span>
|
||||
@elseif($antrian->status == 'sedang_diperiksa')
|
||||
<span
|
||||
class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-purple-100 text-purple-800">
|
||||
Sedang Diperiksa
|
||||
</span>
|
||||
@elseif($antrian->status == 'selesai')
|
||||
<span
|
||||
class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800">
|
||||
|
@ -326,7 +349,21 @@ class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-red-100 text-
|
|||
{{ $antrian->created_at ? $antrian->created_at->format('d/m/Y H:i') : 'N/A' }}
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="4" class="px-4 py-8 text-center text-gray-500">
|
||||
<svg class="w-12 h-12 mx-auto mb-4 text-gray-400" fill="none"
|
||||
stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z">
|
||||
</path>
|
||||
</svg>
|
||||
<p class="text-lg font-medium">Belum ada antrian</p>
|
||||
<p class="text-sm text-gray-400">User ini belum pernah mengambil antrian
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -466,5 +503,100 @@ function togglePassword(inputId) {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Auto refresh table riwayat antrian setiap 10 detik
|
||||
setInterval(function() {
|
||||
refreshRiwayatAntrian();
|
||||
}, 10000);
|
||||
|
||||
function refreshRiwayatAntrian() {
|
||||
fetch(`{{ route('admin.users.antrian-terbaru', $user->id) }}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
updateRiwayatAntrianTable(data.antrians);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('Error refreshing riwayat antrian:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function updateRiwayatAntrianTable(antrians) {
|
||||
const tableBody = document.querySelector('tbody');
|
||||
if (!tableBody) return;
|
||||
|
||||
if (antrians.length === 0) {
|
||||
tableBody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="4" class="px-4 py-8 text-center text-gray-500">
|
||||
<svg class="w-12 h-12 mx-auto mb-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
<p class="text-lg font-medium">Belum ada antrian</p>
|
||||
<p class="text-sm text-gray-400">User ini belum pernah mengambil antrian</p>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
tableBody.innerHTML = antrians.map(antrian => {
|
||||
let statusBadge = '';
|
||||
switch (antrian.status) {
|
||||
case 'menunggu':
|
||||
statusBadge =
|
||||
'<span class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-yellow-100 text-yellow-800">Menunggu</span>';
|
||||
break;
|
||||
case 'dipanggil':
|
||||
statusBadge =
|
||||
'<span class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-blue-100 text-blue-800">Dipanggil</span>';
|
||||
break;
|
||||
case 'sedang_diperiksa':
|
||||
statusBadge =
|
||||
'<span class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-purple-100 text-purple-800">Sedang Diperiksa</span>';
|
||||
break;
|
||||
case 'selesai':
|
||||
statusBadge =
|
||||
'<span class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800">Selesai</span>';
|
||||
break;
|
||||
default:
|
||||
statusBadge =
|
||||
'<span class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-red-100 text-red-800">Batal</span>';
|
||||
}
|
||||
|
||||
return `
|
||||
<tr class="hover:bg-gray-50 transition duration-200">
|
||||
<td class="px-4 py-3 whitespace-nowrap">
|
||||
<span class="text-lg font-semibold text-blue-600">${antrian.no_antrian}</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap">
|
||||
<span class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-blue-100 text-blue-800">
|
||||
${antrian.poli_name}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap">
|
||||
${statusBadge}
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
|
||||
${antrian.created_at}
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
// Add subtle animation to indicate update
|
||||
tableBody.style.transition = 'opacity 0.3s ease';
|
||||
tableBody.style.opacity = '0.7';
|
||||
setTimeout(() => {
|
||||
tableBody.style.opacity = '1';
|
||||
}, 300);
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
|
|
|
@ -52,8 +52,27 @@ class="block w-full pl-10 pr-3 py-3 border border-gray-300 rounded-xl placeholde
|
|||
</svg>
|
||||
</div>
|
||||
<input id="password" name="password" type="password" required
|
||||
class="block w-full pl-10 pr-3 py-3 border border-gray-300 rounded-xl placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary transition duration-200 text-sm"
|
||||
class="block w-full pl-10 pr-12 py-3 border border-gray-300 rounded-xl placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary transition duration-200 text-sm"
|
||||
placeholder="Masukkan password Anda">
|
||||
<button type="button" id="togglePassword"
|
||||
class="absolute inset-y-0 right-0 pr-3 flex items-center cursor-pointer hover:text-gray-600 transition-colors duration-200">
|
||||
<!-- Eye Icon (show) -->
|
||||
<svg id="eyeOpen" class="h-5 w-5 text-gray-400" fill="none" stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z">
|
||||
</path>
|
||||
</svg>
|
||||
<!-- Eye Slash Icon (hide) -->
|
||||
<svg id="eyeClosed" class="h-5 w-5 text-gray-400 hidden" fill="none"
|
||||
stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L3 3m6.878 6.878L21 21">
|
||||
</path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -122,6 +141,23 @@ class="text-blue-100 hover:text-white text-sm transition duration-200 flex items
|
|||
|
||||
@push('scripts')
|
||||
<script>
|
||||
// Toggle password visibility
|
||||
document.getElementById('togglePassword').addEventListener('click', function() {
|
||||
const passwordField = document.getElementById('password');
|
||||
const eyeOpen = document.getElementById('eyeOpen');
|
||||
const eyeClosed = document.getElementById('eyeClosed');
|
||||
|
||||
if (passwordField.type === 'password') {
|
||||
passwordField.type = 'text';
|
||||
eyeOpen.classList.add('hidden');
|
||||
eyeClosed.classList.remove('hidden');
|
||||
} else {
|
||||
passwordField.type = 'password';
|
||||
eyeOpen.classList.remove('hidden');
|
||||
eyeClosed.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// Show SweetAlert2 for errors
|
||||
@if ($errors->any())
|
||||
Swal.fire({
|
||||
|
|
|
@ -92,7 +92,8 @@ class="block w-full pl-10 pr-3 py-3 border border-gray-300 rounded-xl text-gray-
|
|||
</div>
|
||||
<input id="no_hp" name="no_hp" type="tel" required
|
||||
class="block w-full pl-10 pr-3 py-3 border border-gray-300 rounded-xl placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary transition duration-200 text-sm"
|
||||
placeholder="Masukkan nomor HP" value="{{ old('no_hp') }}">
|
||||
placeholder="Masukkan nomor HP" value="{{ old('no_hp') }}" pattern="[0-9]*"
|
||||
inputmode="numeric" oninput="this.value = this.value.replace(/[^0-9]/g, '')">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
@if ($paginator->hasPages())
|
||||
<nav role="navigation" aria-label="Pagination Navigation"
|
||||
class="flex justify-between items-center bg-white px-4 py-3 border-t border-gray-200 rounded-lg shadow-sm">
|
||||
@if ($paginator->onFirstPage())
|
||||
<span
|
||||
class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-400 bg-gray-100 border border-gray-300 cursor-default leading-5 rounded-lg">
|
||||
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd"
|
||||
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
Previous
|
||||
</span>
|
||||
@else
|
||||
<a href="{{ $paginator->previousPageUrl() }}"
|
||||
class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-lg hover:bg-blue-50 hover:text-blue-600 hover:border-blue-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200">
|
||||
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd"
|
||||
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
Previous
|
||||
</a>
|
||||
@endif
|
||||
|
||||
<div class="text-sm text-gray-600 bg-gray-50 px-3 py-2 rounded-lg">
|
||||
<span class="font-semibold text-gray-900">{{ $paginator->currentPage() }}</span>
|
||||
dari
|
||||
<span class="font-semibold text-gray-900">{{ $paginator->lastPage() }}</span>
|
||||
</div>
|
||||
|
||||
@if ($paginator->hasMorePages())
|
||||
<a href="{{ $paginator->nextPageUrl() }}"
|
||||
class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-lg hover:bg-blue-50 hover:text-blue-600 hover:border-blue-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200">
|
||||
Next
|
||||
<svg class="w-4 h-4 ml-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd"
|
||||
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</a>
|
||||
@else
|
||||
<span
|
||||
class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-400 bg-gray-100 border border-gray-300 cursor-default leading-5 rounded-lg">
|
||||
Next
|
||||
<svg class="w-4 h-4 ml-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd"
|
||||
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</span>
|
||||
@endif
|
||||
</nav>
|
||||
@endif
|
|
@ -0,0 +1,147 @@
|
|||
@if ($paginator->hasPages())
|
||||
<nav role="navigation" aria-label="Pagination Navigation"
|
||||
class="flex items-center justify-between bg-white px-4 py-3 border-t border-gray-200 sm:px-6 rounded-lg shadow-sm">
|
||||
<div class="flex justify-between flex-1 sm:hidden">
|
||||
@if ($paginator->onFirstPage())
|
||||
<span
|
||||
class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-400 bg-gray-100 border border-gray-300 cursor-default leading-5 rounded-lg">
|
||||
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd"
|
||||
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
Previous
|
||||
</span>
|
||||
@else
|
||||
<a href="{{ $paginator->previousPageUrl() }}"
|
||||
class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-lg hover:bg-blue-50 hover:text-blue-600 hover:border-blue-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200">
|
||||
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd"
|
||||
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
Previous
|
||||
</a>
|
||||
@endif
|
||||
|
||||
@if ($paginator->hasMorePages())
|
||||
<a href="{{ $paginator->nextPageUrl() }}"
|
||||
class="relative inline-flex items-center px-4 py-2 ml-3 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-lg hover:bg-blue-50 hover:text-blue-600 hover:border-blue-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200">
|
||||
Next
|
||||
<svg class="w-4 h-4 ml-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd"
|
||||
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</a>
|
||||
@else
|
||||
<span
|
||||
class="relative inline-flex items-center px-4 py-2 ml-3 text-sm font-medium text-gray-400 bg-gray-100 border border-gray-300 cursor-default leading-5 rounded-lg">
|
||||
Next
|
||||
<svg class="w-4 h-4 ml-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd"
|
||||
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-600 leading-5 bg-gray-50 px-3 py-2 rounded-lg">
|
||||
Menampilkan
|
||||
<span class="font-semibold text-gray-900">{{ $paginator->firstItem() }}</span>
|
||||
sampai
|
||||
<span class="font-semibold text-gray-900">{{ $paginator->lastItem() }}</span>
|
||||
dari
|
||||
<span class="font-semibold text-gray-900">{{ $paginator->total() }}</span>
|
||||
hasil
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="relative z-0 inline-flex shadow-lg rounded-lg overflow-hidden">
|
||||
{{-- Previous Page Link --}}
|
||||
@if ($paginator->onFirstPage())
|
||||
<span aria-disabled="true" aria-label="Previous">
|
||||
<span
|
||||
class="relative inline-flex items-center px-3 py-2 text-sm font-medium text-gray-400 bg-gray-100 border border-gray-300 cursor-default leading-5"
|
||||
aria-hidden="true">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd"
|
||||
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
@else
|
||||
<a href="{{ $paginator->previousPageUrl() }}" rel="prev"
|
||||
class="relative inline-flex items-center px-3 py-2 text-sm font-medium text-gray-600 bg-white border border-gray-300 leading-5 hover:bg-blue-50 hover:text-blue-600 hover:border-blue-300 focus:z-10 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200"
|
||||
aria-label="Previous">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd"
|
||||
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</a>
|
||||
@endif
|
||||
|
||||
{{-- Pagination Elements --}}
|
||||
@foreach ($elements as $element)
|
||||
{{-- "Three Dots" Separator --}}
|
||||
@if (is_string($element))
|
||||
<span aria-disabled="true">
|
||||
<span
|
||||
class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-gray-700 bg-white border border-gray-300 cursor-default leading-5">{{ $element }}</span>
|
||||
</span>
|
||||
@endif
|
||||
|
||||
{{-- Array Of Links --}}
|
||||
@if (is_array($element))
|
||||
@foreach ($element as $page => $url)
|
||||
@if ($page == $paginator->currentPage())
|
||||
<span aria-current="page">
|
||||
<span
|
||||
class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-semibold text-white bg-gradient-to-r from-blue-500 to-blue-600 border border-blue-600 cursor-default leading-5 shadow-md">{{ $page }}</span>
|
||||
</span>
|
||||
@else
|
||||
<a href="{{ $url }}"
|
||||
class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 hover:bg-blue-50 hover:text-blue-600 hover:border-blue-300 focus:z-10 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200"
|
||||
aria-label="Go to page {{ $page }}">
|
||||
{{ $page }}
|
||||
</a>
|
||||
@endif
|
||||
@endforeach
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
{{-- Next Page Link --}}
|
||||
@if ($paginator->hasMorePages())
|
||||
<a href="{{ $paginator->nextPageUrl() }}" rel="next"
|
||||
class="relative inline-flex items-center px-3 py-2 -ml-px text-sm font-medium text-gray-600 bg-white border border-gray-300 leading-5 hover:bg-blue-50 hover:text-blue-600 hover:border-blue-300 focus:z-10 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200"
|
||||
aria-label="Next">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd"
|
||||
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</a>
|
||||
@else
|
||||
<span aria-disabled="true" aria-label="Next">
|
||||
<span
|
||||
class="relative inline-flex items-center px-3 py-2 -ml-px text-sm font-medium text-gray-400 bg-gray-100 border border-gray-300 cursor-default leading-5"
|
||||
aria-hidden="true">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd"
|
||||
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
@endif
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@endif
|
|
@ -224,6 +224,11 @@ class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-yellow-100 te
|
|||
class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-blue-100 text-blue-800">
|
||||
Dipanggil
|
||||
</span>
|
||||
@elseif(($antrian->status ?? '') == 'sedang_diperiksa')
|
||||
<span
|
||||
class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-purple-100 text-purple-800">
|
||||
Sedang Diperiksa
|
||||
</span>
|
||||
@elseif(($antrian->status ?? '') == 'selesai')
|
||||
<span
|
||||
class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800">
|
||||
|
@ -333,6 +338,11 @@ class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-yellow-100 te
|
|||
class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-blue-100 text-blue-800">
|
||||
Dipanggil
|
||||
</span>
|
||||
@elseif(($antrian->status ?? '') == 'sedang_diperiksa')
|
||||
<span
|
||||
class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-purple-100 text-purple-800">
|
||||
Sedang Diperiksa
|
||||
</span>
|
||||
@elseif(($antrian->status ?? '') == 'selesai')
|
||||
<span
|
||||
class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800">
|
||||
|
@ -445,7 +455,8 @@ class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:rin
|
|||
<label for="edit_no_hp" class="block text-sm font-medium text-gray-700 mb-2">Nomor
|
||||
HP</label>
|
||||
<input type="tel" name="no_hp" id="edit_no_hp"
|
||||
value="{{ auth()->user()->no_hp }}" required
|
||||
value="{{ auth()->user()->no_hp }}" required pattern="[0-9]*" inputmode="numeric"
|
||||
oninput="this.value = this.value.replace(/[^0-9]/g, '')"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition duration-200">
|
||||
</div>
|
||||
|
||||
|
@ -453,7 +464,8 @@ class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:rin
|
|||
<label for="edit_no_ktp" class="block text-sm font-medium text-gray-700 mb-2">Nomor
|
||||
KTP</label>
|
||||
<input type="text" name="no_ktp" id="edit_no_ktp"
|
||||
value="{{ auth()->user()->no_ktp }}" required
|
||||
value="{{ auth()->user()->no_ktp }}" required pattern="[0-9]*" inputmode="numeric"
|
||||
oninput="this.value = this.value.replace(/[^0-9]/g, '')"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition duration-200">
|
||||
</div>
|
||||
|
||||
|
@ -683,9 +695,29 @@ function closeEditModal() {
|
|||
// Handle different error types
|
||||
if (data.type === 'existing_queue') {
|
||||
// User already has queue in same poli
|
||||
let statusText = '';
|
||||
let alertIcon = 'warning';
|
||||
let titleText = 'Antrian Sudah Ada!';
|
||||
|
||||
switch (data.existing_queue.status) {
|
||||
case 'menunggu':
|
||||
statusText = 'Menunggu';
|
||||
break;
|
||||
case 'dipanggil':
|
||||
statusText = 'Dipanggil';
|
||||
alertIcon = 'info';
|
||||
titleText = 'Antrian Sedang Dipanggil!';
|
||||
break;
|
||||
case 'sedang_diperiksa':
|
||||
statusText = 'Sedang Diperiksa';
|
||||
alertIcon = 'error';
|
||||
titleText = 'Sedang Dalam Pemeriksaan!';
|
||||
break;
|
||||
}
|
||||
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Antrian Sudah Ada!',
|
||||
icon: alertIcon,
|
||||
title: titleText,
|
||||
html: `
|
||||
<div class="text-left">
|
||||
<p class="mb-3">${data.message}</p>
|
||||
|
@ -694,20 +726,24 @@ function closeEditModal() {
|
|||
<svg class="w-5 h-5 text-yellow-600 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<span class="font-medium text-yellow-800">Detail Antrian:</span>
|
||||
<span class="font-medium text-yellow-800">Detail Antrian Aktif:</span>
|
||||
</div>
|
||||
<div class="mt-2 text-sm text-yellow-700">
|
||||
<p><strong>No. Antrian:</strong> ${data.existing_queue.no_antrian}</p>
|
||||
<p><strong>Poli:</strong> ${data.existing_queue.poli_name}</p>
|
||||
<p><strong>Status:</strong> ${data.existing_queue.status === 'menunggu' ? 'Menunggu' : 'Dipanggil'}</p>
|
||||
<p><strong>Status:</strong> ${statusText}</p>
|
||||
<p><strong>Waktu Ambil:</strong> ${data.existing_queue.created_at}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600">Silakan tunggu hingga antrian Anda dipanggil atau batalkan antrian ini terlebih dahulu.</p>
|
||||
${data.existing_queue.status === 'sedang_diperiksa'
|
||||
? '<p class="text-sm text-red-600 font-medium">⚠️ Anda tidak dapat mengambil antrian baru di poli yang sama selama masih dalam proses pemeriksaan.</p>'
|
||||
: '<p class="text-sm text-gray-600">Silakan tunggu hingga antrian Anda dipanggil atau batalkan antrian ini terlebih dahulu.</p>'
|
||||
}
|
||||
</div>
|
||||
`,
|
||||
confirmButtonText: 'OK',
|
||||
confirmButtonColor: '#F59E0B'
|
||||
confirmButtonColor: data.existing_queue.status === 'sedang_diperiksa' ?
|
||||
'#EF4444' : '#F59E0B'
|
||||
});
|
||||
} else {
|
||||
// Generic error
|
||||
|
|
|
@ -599,20 +599,49 @@ function updateDisplayData() {
|
|||
fetch('/api/display-data')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Update current numbers
|
||||
if (data.poliUmumCurrent) {
|
||||
document.getElementById('poli-umum-current').textContent = data.poliUmumCurrent.no_antrian;
|
||||
// Poli Umum
|
||||
const umumCurrent = document.getElementById('poli-umum-current');
|
||||
if (data.poliUmumCurrent && ['menunggu', 'dipanggil', 'sedang_diperiksa'].includes(data
|
||||
.poliUmumCurrent.status)) {
|
||||
umumCurrent.textContent = data.poliUmumCurrent.no_antrian;
|
||||
} else {
|
||||
umumCurrent.textContent = '---';
|
||||
}
|
||||
if (data.poliGigiCurrent) {
|
||||
document.getElementById('poli-gigi-current').textContent = data.poliGigiCurrent.no_antrian;
|
||||
umumCurrent.classList.remove('text-blue-200');
|
||||
umumCurrent.classList.add('text-blue-600');
|
||||
|
||||
// Poli Gigi
|
||||
const gigiCurrent = document.getElementById('poli-gigi-current');
|
||||
if (data.poliGigiCurrent && ['menunggu', 'dipanggil', 'sedang_diperiksa'].includes(data
|
||||
.poliGigiCurrent.status)) {
|
||||
gigiCurrent.textContent = data.poliGigiCurrent.no_antrian;
|
||||
} else {
|
||||
gigiCurrent.textContent = '---';
|
||||
}
|
||||
if (data.poliJiwaCurrent) {
|
||||
document.getElementById('poli-jiwa-current').textContent = data.poliJiwaCurrent.no_antrian;
|
||||
gigiCurrent.classList.remove('text-green-200');
|
||||
gigiCurrent.classList.add('text-green-600');
|
||||
|
||||
// Poli Jiwa
|
||||
const jiwaCurrent = document.getElementById('poli-jiwa-current');
|
||||
if (data.poliJiwaCurrent && ['menunggu', 'dipanggil', 'sedang_diperiksa'].includes(data
|
||||
.poliJiwaCurrent.status)) {
|
||||
jiwaCurrent.textContent = data.poliJiwaCurrent.no_antrian;
|
||||
} else {
|
||||
jiwaCurrent.textContent = '---';
|
||||
}
|
||||
if (data.poliTradisionalCurrent) {
|
||||
document.getElementById('poli-tradisional-current').textContent = data.poliTradisionalCurrent
|
||||
.no_antrian;
|
||||
jiwaCurrent.classList.remove('text-pink-200');
|
||||
jiwaCurrent.classList.add('text-pink-600');
|
||||
|
||||
// Poli Tradisional
|
||||
const tradCurrent = document.getElementById('poli-tradisional-current');
|
||||
if (data.poliTradisionalCurrent && ['menunggu', 'dipanggil', 'sedang_diperiksa'].includes(data
|
||||
.poliTradisionalCurrent.status)) {
|
||||
tradCurrent.textContent = data.poliTradisionalCurrent.no_antrian;
|
||||
} else {
|
||||
tradCurrent.textContent = '---';
|
||||
}
|
||||
tradCurrent.classList.remove('text-yellow-200');
|
||||
tradCurrent.classList.add('text-yellow-600');
|
||||
|
||||
// Update next queues
|
||||
updateNextQueue('poli-umum-next', data.poliUmumNext);
|
||||
|
|
|
@ -4,9 +4,16 @@
|
|||
|
||||
@section('content')
|
||||
@if (session('success'))
|
||||
<div id="success-message"
|
||||
class="fixed top-4 right-4 bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded shadow-lg z-50">
|
||||
{{ session('success') }}
|
||||
<div id="success-alert"
|
||||
class="fixed top-4 right-4 bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded shadow-lg z-50 transition-all duration-300 transform translate-x-0 opacity-100">
|
||||
<div class="flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
|
@ -60,7 +67,7 @@ class="bg-primary hover:bg-secondary text-white block px-3 py-2 rounded-md text-
|
|||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 md:py-24">
|
||||
<div class="text-center animate-fade-in">
|
||||
<h1 class="text-4xl md:text-6xl lg:text-7xl font-bold mb-6 leading-tight">
|
||||
Sistem Antrian Puskesmas
|
||||
Puskesmas Mlandingan
|
||||
</h1>
|
||||
<p class="text-xl md:text-2xl mb-8 text-blue-100 max-w-4xl mx-auto">
|
||||
Antrian digital yang memudahkan pelayanan kesehatan masyarakat
|
||||
|
@ -87,27 +94,60 @@ class="border-2 border-white text-white hover:bg-white hover:text-primary px-8 p
|
|||
<p class="text-lg md:text-xl text-gray-600 max-w-3xl mx-auto">Berbagai layanan kesehatan yang tersedia</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 md:gap-12">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 md:gap-12">
|
||||
<div
|
||||
class="bg-white p-6 md:p-8 rounded-2xl shadow-xl border border-gray-200 hover:shadow-2xl transition duration-300 transform hover:-translate-y-2 animate-slide-up">
|
||||
<div class="text-4xl md:text-5xl mb-4">👨⚕️</div>
|
||||
<div class="text-4xl md:text-5xl mb-4 flex justify-center">
|
||||
<svg class="w-12 h-12 md:w-16 md:h-16 text-red-600" fill="none" stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-xl md:text-2xl font-semibold mb-3">Poli Umum</h3>
|
||||
<p class="text-gray-600 text-sm md:text-base">Layanan pemeriksaan kesehatan umum untuk semua usia</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-6 md:p-8 rounded-2xl shadow-xl border border-gray-200 hover:shadow-2xl transition duration-300 transform hover:-translate-y-2 animate-slide-up"
|
||||
style="animation-delay: 0.1s;">
|
||||
<div class="text-4xl md:text-5xl mb-4">👶</div>
|
||||
<h3 class="text-xl md:text-2xl font-semibold mb-3">Poli Anak</h3>
|
||||
<p class="text-gray-600 text-sm md:text-base">Layanan kesehatan khusus untuk anak-anak</p>
|
||||
<div class="text-4xl md:text-5xl mb-4 flex justify-center">
|
||||
<svg class="w-12 h-12 md:w-16 md:h-16 text-blue-600" fill="none" stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z">
|
||||
</path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-xl md:text-2xl font-semibold mb-3">Poli Gigi</h3>
|
||||
<p class="text-gray-600 text-sm md:text-base">Layanan perawatan dan pengobatan kesehatan gigi dan mulut
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-6 md:p-8 rounded-2xl shadow-xl border border-gray-200 hover:shadow-2xl transition duration-300 transform hover:-translate-y-2 animate-slide-up"
|
||||
style="animation-delay: 0.2s;">
|
||||
<div class="text-4xl md:text-5xl mb-4">🤰</div>
|
||||
<h3 class="text-xl md:text-2xl font-semibold mb-3">Poli Ibu Hamil</h3>
|
||||
<p class="text-gray-600 text-sm md:text-base">Layanan kesehatan untuk ibu hamil dan keluarga berencana
|
||||
</p>
|
||||
<div class="text-4xl md:text-5xl mb-4 flex justify-center">
|
||||
<svg class="w-12 h-12 md:w-16 md:h-16 text-purple-600" fill="none" stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z">
|
||||
</path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-xl md:text-2xl font-semibold mb-3">Poli Kesehatan Jiwa</h3>
|
||||
<p class="text-gray-600 text-sm md:text-base">Layanan konsultasi dan terapi kesehatan mental</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-6 md:p-8 rounded-2xl shadow-xl border border-gray-200 hover:shadow-2xl transition duration-300 transform hover:-translate-y-2 animate-slide-up"
|
||||
style="animation-delay: 0.3s;">
|
||||
<div class="text-4xl md:text-5xl mb-4 flex justify-center">
|
||||
<svg class="w-12 h-12 md:w-16 md:h-16 text-green-600" fill="none" stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M9 1v6m6-6v6"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-xl md:text-2xl font-semibold mb-3">Poli Kesehatan Tradisional</h3>
|
||||
<p class="text-gray-600 text-sm md:text-base">Layanan pengobatan tradisional dan herbal</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -212,29 +252,58 @@ class="bg-primary text-white rounded-full w-16 h-16 md:w-20 md:h-20 flex items-c
|
|||
<h4 class="text-lg md:text-xl font-semibold mb-4">Layanan</h4>
|
||||
<ul class="space-y-2 text-gray-400 text-sm md:text-base">
|
||||
<li>Poli Umum</li>
|
||||
<li>Poli Anak</li>
|
||||
<li>Poli Ibu Hamil</li>
|
||||
<li>Poli Gigi</li>
|
||||
<li>Poli Kesehatan Jiwa</li>
|
||||
<li>Poli Kesehatan Tradisional</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-lg md:text-xl font-semibold mb-4">Kontak</h4>
|
||||
<ul class="space-y-2 text-gray-400 text-sm md:text-base">
|
||||
<li>📞 (021) 1234-5678</li>
|
||||
<li>📧 info@puskesmas.com</li>
|
||||
<li>📍 Jl. Kesehatan No. 123</li>
|
||||
</ul>
|
||||
<div class="space-y-3 text-gray-400 text-sm md:text-base">
|
||||
<div class="flex items-center space-x-3">
|
||||
<svg class="w-5 h-5 text-gray-400 flex-shrink-0" fill="none" stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z">
|
||||
</path>
|
||||
</svg>
|
||||
<span>(62) 812-4901-0290</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<svg class="w-5 h-5 text-gray-400 flex-shrink-0" fill="none" stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z">
|
||||
</path>
|
||||
</svg>
|
||||
<span>pkmmlandinganeksotik@gmail.com</span>
|
||||
</div>
|
||||
<div class="flex items-start space-x-3">
|
||||
<svg class="w-5 h-5 text-gray-400 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z">
|
||||
</path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
</svg>
|
||||
<span>Jalan Raya Mandingan No.437, Pesisir, Mlandingan Kulon, Kec. Mlandingan, Kabupaten
|
||||
Situbondo, Jawa Timur 68353</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-lg md:text-xl font-semibold mb-4">Jam Operasional</h4>
|
||||
<ul class="space-y-2 text-gray-400 text-sm md:text-base">
|
||||
<li>Senin - Jumat: 08:00 - 16:00</li>
|
||||
<li>Sabtu: 08:00 - 12:00</li>
|
||||
<li>Senin - Kamis: 08:00 - 12:00</li>
|
||||
<li>Jumat: 08:00 - 10:00</li>
|
||||
<li>Sabtu: 08:00 - 11:30</li>
|
||||
<li>Minggu: Tutup</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-800 mt-8 md:mt-12 pt-8 text-center text-gray-400">
|
||||
<p class="text-sm md:text-base">© 2024 Sistem Antrian Puskesmas. All rights reserved.</p>
|
||||
<p class="text-sm md:text-base">© 2025 Sistem Antrian Puskesmas. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
@ -261,56 +330,51 @@ class="bg-primary text-white rounded-full w-16 h-16 md:w-20 md:h-20 flex items-c
|
|||
});
|
||||
});
|
||||
|
||||
// Show SweetAlert2 for success messages
|
||||
// Auto-hide success alert after 3 seconds
|
||||
@if (session('success'))
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Berhasil!',
|
||||
text: '{{ session('success') }}',
|
||||
confirmButtonText: 'OK',
|
||||
confirmButtonColor: '#10B981',
|
||||
timer: 3000,
|
||||
timerProgressBar: true
|
||||
});
|
||||
@endif
|
||||
setTimeout(function() {
|
||||
const successAlert = document.getElementById('success-alert');
|
||||
if (successAlert) {
|
||||
// Add fade out animation
|
||||
successAlert.classList.add('translate-x-full', 'opacity-0');
|
||||
|
||||
// Show logout success message
|
||||
@if (session('success') && str_contains(session('success'), 'logout'))
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Logout Berhasil!',
|
||||
text: '{{ session('success') }}',
|
||||
confirmButtonText: 'OK',
|
||||
confirmButtonColor: '#10B981',
|
||||
timer: 3000,
|
||||
timerProgressBar: true
|
||||
});
|
||||
// Remove element after animation completes
|
||||
setTimeout(function() {
|
||||
successAlert.remove();
|
||||
}, 300);
|
||||
}
|
||||
}, 3000);
|
||||
@endif
|
||||
|
||||
// Show SweetAlert2 for error messages
|
||||
@if (session('error'))
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error!',
|
||||
text: '{{ session('error') }}',
|
||||
confirmButtonText: 'OK',
|
||||
confirmButtonColor: '#EF4444',
|
||||
timer: 4000,
|
||||
timerProgressBar: true
|
||||
});
|
||||
@endif
|
||||
|
||||
// Show system error message
|
||||
@if (session('error') && str_contains(session('error'), 'sistem'))
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error Sistem!',
|
||||
text: '{{ session('error') }}',
|
||||
confirmButtonText: 'OK',
|
||||
confirmButtonColor: '#EF4444',
|
||||
timer: 4000,
|
||||
timerProgressBar: true
|
||||
});
|
||||
@if (str_contains(session('error'), 'sistem'))
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error Sistem!',
|
||||
text: '{{ session('error') }}',
|
||||
confirmButtonText: 'OK',
|
||||
confirmButtonColor: '#EF4444',
|
||||
timer: 5000,
|
||||
timerProgressBar: true,
|
||||
showCloseButton: true,
|
||||
allowOutsideClick: true,
|
||||
allowEscapeKey: true
|
||||
});
|
||||
@else
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error!',
|
||||
text: '{{ session('error') }}',
|
||||
confirmButtonText: 'OK',
|
||||
confirmButtonColor: '#EF4444',
|
||||
timer: 4000,
|
||||
timerProgressBar: true,
|
||||
showCloseButton: true,
|
||||
allowOutsideClick: true,
|
||||
allowEscapeKey: true
|
||||
});
|
||||
@endif
|
||||
@endif
|
||||
|
||||
// Intersection Observer for animations
|
||||
|
|
|
@ -63,6 +63,8 @@
|
|||
Route::get('/admin/poli/tradisional', [AdminController::class, 'poliTradisional'])->name('admin.poli.tradisional');
|
||||
Route::post('/admin/panggil-antrian', [AdminController::class, 'panggilAntrian'])->name('admin.panggil-antrian');
|
||||
Route::post('/admin/selesai-antrian', [AdminController::class, 'selesaiAntrian'])->name('admin.selesai-antrian');
|
||||
Route::post('/admin/konfirmasi-kehadiran', [AdminController::class, 'konfirmasiKehadiran'])->name('admin.konfirmasi-kehadiran');
|
||||
Route::post('/admin/mulai-pemeriksaan', [AdminController::class, 'mulaiPemeriksaan'])->name('admin.mulai-pemeriksaan');
|
||||
Route::post('/admin/antrian/batal', [AdminController::class, 'batalAntrian'])->name('admin.batal-antrian');
|
||||
Route::post('/admin/panggil-antrian/{antrian}', [AdminController::class, 'panggilAntrianById'])->name('admin.panggil-antrian-id');
|
||||
Route::post('/admin/play-audio', [AdminController::class, 'playQueueCallAudio'])->name('admin.play-audio');
|
||||
|
@ -74,6 +76,7 @@
|
|||
Route::get('/admin/users/create', [AdminController::class, 'createUser'])->name('admin.users.create');
|
||||
Route::post('/admin/users', [AdminController::class, 'storeUser'])->name('admin.users.store');
|
||||
Route::get('/admin/users/{user}', [AdminController::class, 'showUser'])->name('admin.users.show');
|
||||
Route::get('/admin/users/{user}/antrian-terbaru', [AdminController::class, 'getUserAntrianTerbaru'])->name('admin.users.antrian-terbaru');
|
||||
Route::put('/admin/users/{user}', [AdminController::class, 'updateUser'])->name('admin.users.update');
|
||||
Route::post('/admin/users/{user}/reset-password', [AdminController::class, 'resetUserPassword'])->name('admin.users.reset-password');
|
||||
|
||||
|
@ -88,6 +91,8 @@
|
|||
Route::post('/admin/antrian/store', [AdminController::class, 'storeAntrianAdmin'])->name('admin.antrian.store');
|
||||
Route::get('/admin/antrian/{antrian}/cetak', [AdminController::class, 'cetakAntrian'])->name('admin.antrian.cetak');
|
||||
|
||||
// Dashboard API for live updates
|
||||
Route::get('/admin/dashboard/api', [AdminController::class, 'dashboardApi'])->name('admin.dashboard.api');
|
||||
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue