Setup Layout dan Dashboard Template
Di lesson ini, kita akan membuat layout dashboard yang profesional menggunakan Bootstrap 5 dengan sidebar yang modern.
Apa yang akan dibuat?
1. Master Layout: Sidebar fixed dengan menu navigasi
2. Dashboard: Statistik cards dengan update realtime
3. DashboardController: Logic untuk menghitung statistik
4. Toast Notification: Feedback system yang modern
5. Routes: Route untuk dashboard
1. Master Layout: Sidebar fixed dengan menu navigasi
2. Dashboard: Statistik cards dengan update realtime
3. DashboardController: Logic untuk menghitung statistik
4. Toast Notification: Feedback system yang modern
5. Routes: Route untuk dashboard
Struktur Folder View yang Akan Dibuat:
resources/views/ ├── layouts/ │ └── simrs.blade.php ← Master Layout (dibuat di step ini) ├── dashboard.blade.php ← Halaman dashboard ├── master/ ← CRUD Master Data (Section 2) ├── pasien/ ← Data Pasien (Section 3) └── pendaftaran/ ← Pendaftaran (Section 4)
Step 1: Buat Folder layouts
Lokasi: File Explorer / VS Code
Aksi: [BUAT FOLDER BARU]
Cara:
Aksi: [BUAT FOLDER BARU]
Cara:
- Buka folder project di VS Code
- Masuk ke folder
resources/views/ - Klik kanan → New Folder → beri nama
layouts
Step 2: Buat Master Layout
Lokasi File:
Aksi: [BUAT FILE BARU]
Cara: Di folder
resources/views/layouts/simrs.blade.phpAksi: [BUAT FILE BARU]
Cara: Di folder
layouts yang baru dibuat, klik kanan → New File → beri nama simrs.blade.php, lalu PASTE SEMUA kode di bawah ini:
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>@yield('title', 'SIMRS') - {{ config('app.name') }}</title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" rel="stylesheet">
<!-- DataTables CSS -->
<link href="https://cdn.datatables.net/1.13.7/css/dataTables.bootstrap5.min.css" rel="stylesheet">
<style>
:root {
--primary-color: #0d6efd;
--sidebar-width: 260px;
}
body { background: #f4f6f9; }
.sidebar {
width: var(--sidebar-width);
height: 100vh;
background: linear-gradient(180deg, #1e3a5f 0%, #0d2137 100%);
position: fixed;
left: 0;
top: 0;
z-index: 1000;
display: flex;
flex-direction: column;
}
.sidebar .nav-link {
color: rgba(255,255,255,0.7);
padding: 12px 20px;
border-radius: 8px;
margin: 2px 10px;
}
.sidebar .nav-link:hover, .sidebar .nav-link.active {
background: rgba(255,255,255,0.1);
color: #fff;
}
.sidebar .nav-section {
color: rgba(255,255,255,0.4);
font-size: 11px;
text-transform: uppercase;
padding: 15px 20px 5px;
letter-spacing: 1px;
}
.main-content {
margin-left: var(--sidebar-width);
padding: 20px;
}
.navbar-top {
background: #fff;
box-shadow: 0 2px 4px rgba(0,0,0,0.08);
}
.card { border: none; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.stat-card { border-left: 4px solid var(--primary-color); }
</style>
@stack('styles')
</head>
<body>
<!-- Sidebar -->
<nav class="sidebar d-flex flex-column">
<div class="p-3 text-center border-bottom border-secondary">
<h5 class="text-white mb-0"><i class="bi bi-hospital me-2"></i>SIMRS</h5>
<small class="text-white-50">Sistem Informasi RS</small>
</div>
<div class="sidebar-menu py-3 flex-grow-1 overflow-auto">
<ul class="nav flex-column">
<!-- Dashboard -->
<li class="nav-item">
<a href="{{ route('dashboard') }}" class="nav-link {{ request()->routeIs('dashboard') ? 'active' : '' }}">
<i class="bi bi-speedometer2 me-2"></i>Dashboard
</a>
</li>
<!-- Master Data Section -->
<li class="nav-section">Master Data</li>
<li class="nav-item">
<a href="/poliklinik" class="nav-link {{ request()->is('poliklinik*') ? 'active' : '' }}">
<i class="bi bi-building me-2"></i>Poliklinik
</a>
</li>
<li class="nav-item">
<a href="/dokter" class="nav-link {{ request()->is('dokter*') ? 'active' : '' }}">
<i class="bi bi-person-badge me-2"></i>Dokter
</a>
</li>
<li class="nav-item">
<a href="/jadwal-praktek" class="nav-link {{ request()->is('jadwal*') ? 'active' : '' }}">
<i class="bi bi-calendar-week me-2"></i>Jadwal Praktek
</a>
</li>
<li class="nav-item">
<a href="/obat" class="nav-link {{ request()->is('obat*') ? 'active' : '' }}">
<i class="bi bi-capsule me-2"></i>Obat
</a>
</li>
<!-- Pelayanan Section -->
<li class="nav-section">Pelayanan</li>
<li class="nav-item">
<a href="/pasien" class="nav-link {{ request()->is('pasien*') ? 'active' : '' }}">
<i class="bi bi-people me-2"></i>Pasien
</a>
</li>
<li class="nav-item">
<a href="/pendaftaran" class="nav-link {{ request()->is('pendaftaran*') ? 'active' : '' }}">
<i class="bi bi-clipboard-plus me-2"></i>Pendaftaran
</a>
</li>
<li class="nav-item">
<a href="/antrian" class="nav-link {{ request()->is('antrian*') ? 'active' : '' }}">
<i class="bi bi-ticket-perforated me-2"></i>Antrian
</a>
</li>
</ul>
</div>
<!-- User Info at Bottom -->
<div class="p-3 border-top border-secondary">
<div class="d-flex align-items-center text-white">
<i class="bi bi-person-circle me-2" style="font-size: 1.5rem;"></i>
<div class="flex-grow-1">
<small class="d-block">{{ auth()->user()->name ?? 'Guest' }}</small>
<small class="text-white-50">{{ auth()->user()->email ?? '' }}</small>
</div>
</div>
</div>
</nav>
<!-- Main Content -->
<div class="main-content">
<!-- Top Navbar -->
<nav class="navbar navbar-top rounded mb-4 px-3">
<h5 class="mb-0">@yield('title', 'Dashboard')</h5>
<div class="d-flex align-items-center gap-3">
<span class="text-muted small">{{ now()->locale('id')->isoFormat('dddd, D MMMM Y') }}</span>
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit" class="btn btn-outline-danger btn-sm">
<i class="bi bi-box-arrow-right me-1"></i>Logout
</button>
</form>
</div>
</nav>
<!-- Page Content -->
@yield('content')
</div>
<!-- Toast Container -->
<div class="toast-container position-fixed bottom-0 end-0 p-3">
<div id="liveToast" class="toast" role="alert">
<div class="toast-header">
<i class="bi bi-bell me-2"></i>
<strong class="me-auto">Notifikasi</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast"></button>
</div>
<div class="toast-body"></div>
</div>
</div>
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<!-- Bootstrap 5 JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<!-- DataTables JS -->
<script src="https://cdn.datatables.net/1.13.7/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.7/js/dataTables.bootstrap5.min.js"></script>
<script>
// Setup CSRF Token untuk semua AJAX request
$.ajaxSetup({
headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') }
});
// Function untuk menampilkan Toast
function showToast(message, type = 'success') {
const toast = document.getElementById('liveToast');
const toastBody = toast.querySelector('.toast-body');
// Set warna berdasarkan type
toast.classList.remove('bg-success', 'bg-danger', 'bg-warning', 'bg-info', 'text-white');
if (type === 'success') {
toast.classList.add('bg-success', 'text-white');
} else if (type === 'error') {
toast.classList.add('bg-danger', 'text-white');
} else if (type === 'warning') {
toast.classList.add('bg-warning');
} else {
toast.classList.add('bg-info', 'text-white');
}
toastBody.textContent = message;
const bsToast = new bootstrap.Toast(toast);
bsToast.show();
}
// Flash message dari Laravel session
@if(session('success'))
showToast("{{ session('success') }}", 'success');
@endif
@if(session('error'))
showToast("{{ session('error') }}", 'error');
@endif
@if(session('warning'))
showToast("{{ session('warning') }}", 'warning');
@endif
</script>
@stack('scripts')
</body>
</html>
Penjelasan Komponen Layout:
.sidebar |
Sidebar fixed di kiri dengan gradient biru |
.main-content |
Area konten utama dengan margin-left untuk sidebar |
@yield('content') |
Tempat konten halaman di-inject |
showToast() |
Function JS untuk notifikasi popup |
$.ajaxSetup() |
Set CSRF token untuk semua AJAX request |
Step 3: Buat File Dashboard View
Lokasi File:
Aksi: [BUAT FILE BARU]
Cara: Di folder
resources/views/dashboard.blade.phpAksi: [BUAT FILE BARU]
Cara: Di folder
resources/views/, buat file baru bernama dashboard.blade.php, lalu PASTE SEMUA kode di bawah ini:
@extends('layouts.simrs')
@section('title', 'Dashboard')
@section('content')
<!-- Stats Cards -->
<div class="row g-3 mb-4">
<div class="col-md-3">
<div class="card stat-card">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h6 class="text-muted mb-1">Pasien Hari Ini</h6>
<h3 class="mb-0" id="stat-pasien">{{ $stats['pasien_hari_ini'] ?? 0 }}</h3>
</div>
<div class="text-primary opacity-50">
<i class="bi bi-people" style="font-size: 2.5rem;"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card stat-card" style="border-left-color: #198754;">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h6 class="text-muted mb-1">Antrian Menunggu</h6>
<h3 class="mb-0" id="stat-antrian">{{ $stats['antrian_menunggu'] ?? 0 }}</h3>
</div>
<div class="text-success opacity-50">
<i class="bi bi-hourglass-split" style="font-size: 2.5rem;"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card stat-card" style="border-left-color: #ffc107;">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h6 class="text-muted mb-1">Resep Pending</h6>
<h3 class="mb-0" id="stat-resep">{{ $stats['resep_pending'] ?? 0 }}</h3>
</div>
<div class="text-warning opacity-50">
<i class="bi bi-prescription2" style="font-size: 2.5rem;"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card stat-card" style="border-left-color: #0dcaf0;">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h6 class="text-muted mb-1">Pendapatan Hari Ini</h6>
<h5 class="mb-0" id="stat-pendapatan">{{ format_rupiah($stats['pendapatan'] ?? 0) }}</h5>
</div>
<div class="text-info opacity-50">
<i class="bi bi-cash-coin" style="font-size: 2.5rem;"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Info Cards -->
<div class="row g-3">
<div class="col-md-8">
<div class="card">
<div class="card-header bg-white">
<h6 class="mb-0"><i class="bi bi-clock-history me-2"></i>Antrian Saat Ini</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th>No. Antrian</th>
<th>Nama Pasien</th>
<th>Poliklinik</th>
<th>Status</th>
</tr>
</thead>
<tbody>
@forelse($antrians ?? [] as $antrian)
<tr>
<td><span class="badge bg-primary">{{ $antrian->no_antrian }}</span></td>
<td>{{ $antrian->kunjungan->pasien->nama ?? '-' }}</td>
<td>{{ $antrian->poliklinik->nama ?? '-' }}</td>
<td>
@if($antrian->status == 'dipanggil')
<span class="badge bg-success">Dipanggil</span>
@else
<span class="badge bg-warning">Menunggu</span>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="4" class="text-center text-muted py-3">
Tidak ada antrian saat ini
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header bg-white">
<h6 class="mb-0"><i class="bi bi-prescription2 me-2"></i>Resep Terbaru</h6>
</div>
<div class="card-body p-0">
<ul class="list-group list-group-flush">
@forelse($reseps ?? [] as $resep)
<li class="list-group-item d-flex justify-content-between align-items-center">
<div>
<strong>{{ $resep->rekamMedis->kunjungan->pasien->nama ?? '-' }}</strong>
<br><small class="text-muted">{{ $resep->dokter->nama ?? '-' }}</small>
</div>
<span class="badge bg-{{ $resep->status == 'menunggu' ? 'warning' : 'info' }}">
{{ ucfirst($resep->status) }}
</span>
</li>
@empty
<li class="list-group-item text-center text-muted py-3">
Tidak ada resep pending
</li>
@endforelse
</ul>
</div>
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
// AJAX Polling untuk update realtime setiap 10 detik
function refreshDashboard() {
$.get('{{ route("dashboard.stats") }}', function(data) {
$('#stat-pasien').text(data.pasien_hari_ini);
$('#stat-antrian').text(data.antrian_menunggu);
$('#stat-resep').text(data.resep_pending);
$('#stat-pendapatan').text('Rp ' + new Intl.NumberFormat('id-ID').format(data.pendapatan));
});
}
// Refresh setiap 10 detik (10000ms)
setInterval(refreshDashboard, 10000);
</script>
@endpush
Step 4: Buat DashboardController
Lokasi: Terminal / Command Prompt
Aksi: [JALANKAN COMMAND]
Cara: Buka terminal di folder project, jalankan command berikut:
Aksi: [JALANKAN COMMAND]
Cara: Buka terminal di folder project, jalankan command berikut:
# Buat controller untuk Dashboard
php artisan make:controller DashboardController
Lokasi File:
Aksi: [UPDATE FILE]
Cara: Buka file yang baru dibuat, HAPUS SEMUA isinya, lalu PASTE kode di bawah ini:
app/Http/Controllers/DashboardController.phpAksi: [UPDATE FILE]
Cara: Buka file yang baru dibuat, HAPUS SEMUA isinya, lalu PASTE kode di bawah ini:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class DashboardController extends Controller
{
/**
* Tampilkan halaman dashboard dengan statistik
*/
public function index()
{
// Statistik (akan diisi dengan data real di section berikutnya)
$stats = [
'pasien_hari_ini' => 0,
'antrian_menunggu' => 0,
'resep_pending' => 0,
'pendapatan' => 0,
];
// Data antrian (kosong dulu)
$antrians = collect([]);
// Data resep pending (kosong dulu)
$reseps = collect([]);
return view('dashboard', compact('stats', 'antrians', 'reseps'));
}
/**
* API endpoint untuk AJAX polling statistik
* Dipanggil setiap 10 detik oleh JavaScript
*/
public function stats()
{
// Return statistik dalam format JSON
return response()->json([
'pasien_hari_ini' => 0,
'antrian_menunggu' => 0,
'resep_pending' => 0,
'pendapatan' => 0,
]);
}
}
Catatan:
Di tahap ini, statistik masih 0 semua karena kita belum punya tabel dan data.
Statistik akan diupdate di section berikutnya setelah tabel-tabel dibuat.
Di tahap ini, statistik masih 0 semua karena kita belum punya tabel dan data.
Statistik akan diupdate di section berikutnya setelah tabel-tabel dibuat.
Step 5: Tambahkan Routes Dashboard
Lokasi File:
Aksi: [UPDATE FILE]
Cara: Buka file
routes/web.phpAksi: [UPDATE FILE]
Cara: Buka file
routes/web.php, cari route dashboard yang existing, ubah menjadi seperti di bawah:
<?php
use App\Http\Controllers\DashboardController;
use Illuminate\Support\Facades\Route;
// ===== SECTION 1: SETUP =====
Route::get('/', function () {
return redirect()->route('dashboard');
});
Route::middleware(['auth'])->group(function () {
// Dashboard
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
Route::get('/dashboard/stats', [DashboardController::class, 'stats'])->name('dashboard.stats');
});
// ===== END SECTION 1 =====
require __DIR__.'/auth.php';
Penjelasan Routes:
Route::get('/') |
Redirect ke dashboard (halaman utama) |
/dashboard |
Halaman dashboard dengan statistik |
/dashboard/stats |
API endpoint untuk AJAX polling |
middleware(['auth']) |
Hanya user yang sudah login yang bisa akses |
Full Source Code
Files yang Dibuat (4 files):
Kode sudah lengkap di masing-masing Step.
Kode sudah lengkap di masing-masing Step.
| 1 | resources/views/layouts/simrs.blade.php |
[BUAT FILE BARU] | Step 2 |
| 2 | resources/views/dashboard.blade.php |
[BUAT FILE BARU] | Step 3 |
| 3 | app/Http/Controllers/DashboardController.php |
[BUAT FILE BARU] | Step 4 |
| 4 | routes/web.php |
[UPDATE FILE] | Step 5 |
Checkpoint - Test Manual
Langkah Testing:
1. Pastikan Server Berjalan:
Server harus berjalan di
2. Login ke Sistem:
- Buka browser:
- Login dengan akun yang sudah didaftarkan
3. Cek Dashboard:
- Setelah login, Anda akan di-redirect ke dashboard
- Pastikan sidebar muncul di kiri
- Pastikan 4 cards statistik muncul (nilainya masih 0)
- Pastikan tabel antrian dan resep muncul (kosong)
4. Test Toast Notification:
- Buka Console browser (F12 → Console)
- Ketik:
- Toast hijau harus muncul di kanan bawah
5. Test AJAX Polling:
- Buka Network tab di browser DevTools (F12 → Network)
- Tunggu 10 detik
- Harus ada request ke
1. Pastikan Server Berjalan:
php artisan serveServer harus berjalan di
http://localhost:80002. Login ke Sistem:
- Buka browser:
http://localhost:8000/login- Login dengan akun yang sudah didaftarkan
3. Cek Dashboard:
- Setelah login, Anda akan di-redirect ke dashboard
- Pastikan sidebar muncul di kiri
- Pastikan 4 cards statistik muncul (nilainya masih 0)
- Pastikan tabel antrian dan resep muncul (kosong)
4. Test Toast Notification:
- Buka Console browser (F12 → Console)
- Ketik:
showToast('Test berhasil!', 'success')- Toast hijau harus muncul di kanan bawah
5. Test AJAX Polling:
- Buka Network tab di browser DevTools (F12 → Network)
- Tunggu 10 detik
- Harus ada request ke
/dashboard/stats setiap 10 detik
Troubleshooting Error Umum:
Error: "Route [dashboard] not defined"
Solusi: Pastikan routes sudah ditambahkan di
Error: Sidebar tidak muncul
Solusi: Pastikan file layout sudah ada di
Error: "format_rupiah not found"
Solusi: Pastikan Helper sudah terdaftar di composer.json dan jalankan
Error: "Route [dashboard] not defined"
Solusi: Pastikan routes sudah ditambahkan di
routes/web.phpError: Sidebar tidak muncul
Solusi: Pastikan file layout sudah ada di
resources/views/layouts/simrs.blade.phpError: "format_rupiah not found"
Solusi: Pastikan Helper sudah terdaftar di composer.json dan jalankan
composer dump-autoload
Ringkasan
Yang Sudah Dibuat:
1. Master Layout (simrs.blade.php):
- Sidebar fixed dengan menu navigasi
- Top navbar dengan tanggal dan logout
- Toast notification system
- AJAX CSRF setup otomatis
2. Dashboard (dashboard.blade.php):
- 4 statistik cards (pasien, antrian, resep, pendapatan)
- Tabel antrian saat ini
- Daftar resep pending
- AJAX polling untuk update realtime
3. DashboardController:
- Method index() untuk tampilkan halaman
- Method stats() untuk API polling
4. Routes:
- /dashboard (halaman utama)
- /dashboard/stats (API polling)
1. Master Layout (simrs.blade.php):
- Sidebar fixed dengan menu navigasi
- Top navbar dengan tanggal dan logout
- Toast notification system
- AJAX CSRF setup otomatis
2. Dashboard (dashboard.blade.php):
- 4 statistik cards (pasien, antrian, resep, pendapatan)
- Tabel antrian saat ini
- Daftar resep pending
- AJAX polling untuk update realtime
3. DashboardController:
- Method index() untuk tampilkan halaman
- Method stats() untuk API polling
4. Routes:
- /dashboard (halaman utama)
- /dashboard/stats (API polling)
Lesson Berikutnya:
Di Lesson 5: Implementasi Role dan Permission, kita akan:
- Install package Spatie Permission
- Membuat role (Admin, Dokter, Perawat, Apoteker, Kasir)
- Membuat middleware untuk cek akses
Dengan role & permission, setiap user hanya bisa akses menu sesuai haknya.
Di Lesson 5: Implementasi Role dan Permission, kita akan:
- Install package Spatie Permission
- Membuat role (Admin, Dokter, Perawat, Apoteker, Kasir)
- Membuat middleware untuk cek akses
Dengan role & permission, setiap user hanya bisa akses menu sesuai haknya.