385 lines
25 KiB
PHP
385 lines
25 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('title', 'Admin Dashboard - Sistem Antrian Puskesmas')
|
|
|
|
@section('content')
|
|
<div class="min-h-screen bg-gray-50">
|
|
@include('admin.partials.top-nav')
|
|
|
|
<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-8 animate-fade-in">
|
|
<h1 class="text-3xl md:text-4xl font-bold text-gray-900 mb-2">Admin Dashboard</h1>
|
|
<p class="text-gray-600 text-lg">Kelola sistem antrian Puskesmas</p>
|
|
</div>
|
|
|
|
<!-- Poli Summary -->
|
|
<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 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 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 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>
|
|
|
|
<!-- Quick Actions -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
|
|
<div class="bg-white rounded-2xl shadow-xl p-6">
|
|
<div class="flex items-center mb-4">
|
|
<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="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
|
</svg>
|
|
</div>
|
|
<h3 class="text-lg font-semibold text-gray-900 ml-3">Tambah Antrian</h3>
|
|
</div>
|
|
<p class="text-gray-600 mb-4">Bantu pasien yang tidak bisa antri online</p>
|
|
<a href="{{ route('admin.antrian.tambah') }}"
|
|
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>
|
|
</svg>
|
|
</a>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-2xl shadow-xl p-6">
|
|
<div class="flex items-center mb-4">
|
|
<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="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>
|
|
</div>
|
|
<h3 class="text-lg font-semibold text-gray-900 ml-3">Display Antrian</h3>
|
|
</div>
|
|
<p class="text-gray-600 mb-4">Lihat display antrian untuk pasien</p>
|
|
<a href="{{ route('display') }}"
|
|
class="inline-flex items-center px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition duration-200">
|
|
Lihat Display
|
|
<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>
|
|
</svg>
|
|
</a>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-2xl shadow-xl p-6">
|
|
<div class="flex items-center mb-4">
|
|
<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="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>
|
|
</div>
|
|
<h3 class="text-lg font-semibold text-gray-900 ml-3">Laporan</h3>
|
|
</div>
|
|
<p class="text-gray-600 mb-4">Lihat laporan dan statistik antrian</p>
|
|
<a href="{{ route('admin.laporan.index') }}"
|
|
class="inline-flex items-center px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition duration-200">
|
|
Lihat Laporan
|
|
<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>
|
|
</svg>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 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" 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>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Logout Form -->
|
|
<form id="logout-form" method="POST" action="{{ route('logout') }}" class="hidden">
|
|
@csrf
|
|
</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
|