first commit

This commit is contained in:
Saufi
2026-06-14 08:52:33 +08:00
commit 359229685d
87 changed files with 12477 additions and 0 deletions

9
resources/css/app.css Normal file
View File

@@ -0,0 +1,9 @@
@import 'tailwindcss';
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
@source '../../storage/framework/views/*.php';
@theme {
--font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
}

1
resources/js/app.js Normal file
View File

@@ -0,0 +1 @@
//

View File

@@ -0,0 +1,26 @@
@extends('layouts.app')
@section('tajuk', 'Log Masuk')
@section('saiz-container', 'container-kecil')
@section('kandungan')
<div class="kad">
<h1>Log Masuk</h1>
<p class="teks-kecil">Untuk admin kaunter dan pengurus DUN sahaja. Petugas tidak perlu log masuk sila scan QR code di lokasi taklimat.</p>
@if ($errors->any())
<div class="makluman makluman-ralat">{{ $errors->first() }}</div>
@endif
<form method="POST" action="{{ route('login.attempt') }}">
@csrf
<label for="email">Emel</label>
<input type="email" id="email" name="email" value="{{ old('email') }}" required autofocus>
<label for="password">Kata Laluan</label>
<input type="password" id="password" name="password" required>
<button type="submit" class="btn btn-penuh">Log Masuk</button>
</form>
</div>
@endsection

View File

@@ -0,0 +1,17 @@
@extends('layouts.app')
@section('tajuk', 'Sesi Ditutup')
@section('saiz-container', 'container-kecil')
@section('kandungan')
<div class="kad teks-tengah">
<h1>Sesi Taklimat Ditutup</h1>
<div class="makluman makluman-amaran">
Pendaftaran kehadiran bagi sesi
<strong>{{ $sesi->nama }}</strong>
({{ $sesi->dun->nama ?? '' }}, {{ $sesi->tarikh->format('d/m/Y') }})
telah ditutup.
</div>
<p class="teks-kecil">Sila hubungi urus setia taklimat untuk bantuan.</p>
</div>
@endsection

View File

@@ -0,0 +1,37 @@
@extends('layouts.app')
@section('tajuk', 'Daftar Kehadiran Taklimat')
@section('saiz-container', 'container-kecil')
@section('kandungan')
<div class="kad">
<h1>Daftar Kehadiran Taklimat</h1>
<table class="slip-butiran">
<tr><th>DUN</th><td>{{ $sesi->dun->code ?? '' }} {{ $sesi->dun->nama ?? '-' }}</td></tr>
<tr><th>Sesi</th><td>{{ $sesi->nama }}</td></tr>
<tr><th>Tarikh</th><td>{{ $sesi->tarikh->format('d/m/Y') }}</td></tr>
@if ($sesi->masa)
<tr><th>Masa</th><td>{{ $sesi->masa }}</td></tr>
@endif
<tr><th>Lokasi</th><td>{{ $sesi->lokasi }}</td></tr>
</table>
<div class="makluman makluman-info">
Sila tunjuk kad pengenalan di kaunter jika diminta untuk pengesahan identiti.
</div>
@if ($errors->has('no_kp'))
<div class="makluman makluman-ralat">{{ $errors->first('no_kp') }}</div>
@endif
<form method="POST" action="{{ route('checkin.submit', $sesi->qr_token) }}">
@csrf
<label for="no_kp">No. Kad Pengenalan</label>
<input type="text" id="no_kp" name="no_kp" value="{{ old('no_kp') }}"
inputmode="numeric" placeholder="cth: 890529015104" required autofocus>
<button type="submit" class="btn btn-penuh">Daftar Kehadiran</button>
</form>
</div>
@endsection

View File

@@ -0,0 +1,38 @@
@extends('layouts.app')
@section('tajuk', 'Slip Kehadiran')
@section('saiz-container', 'container-kecil')
@section('kandungan')
<div class="kad teks-tengah">
@if ($sudahDaftar)
<div class="makluman makluman-amaran">
Anda telah pun didaftarkan dalam sesi ini. Berikut ialah slip kehadiran anda.
</div>
@else
<div class="makluman makluman-jaya">
Kehadiran anda berjaya direkodkan!
</div>
@endif
<p class="teks-kecil mb-0">Nombor siri anda</p>
<div class="slip-nombor">{{ $kehadiran->nombor_siri_papar }}</div>
<p class="teks-kecil">Sila tunjuk slip ini kepada admin kaunter untuk mengambil borang SPR.</p>
</div>
<div class="kad">
<table class="slip-butiran">
<tr><th>Nama</th><td><strong>{{ $petugas->nama_ic }}</strong></td></tr>
<tr><th>No. KP</th><td>{{ $petugas->no_kp }}</td></tr>
<tr><th>Pusat Mengundi</th><td>{{ $penempatan['pusat_mengundi'] }}</td></tr>
<tr><th>Saluran</th><td>{{ $penempatan['saluran'] }}</td></tr>
<tr><th>Jawatan</th><td>{{ $penempatan['jawatan'] }}</td></tr>
<tr><th>Sesi</th><td>{{ $sesi->nama }} ({{ $sesi->tarikh->format('d/m/Y') }})</td></tr>
<tr><th>Masa Daftar</th><td>{{ $kehadiran->hadir_at->format('d/m/Y h:i A') }}</td></tr>
</table>
<div class="makluman makluman-info mb-0">
Sila tunjuk kad pengenalan jika diminta untuk pengesahan identiti.
</div>
</div>
@endsection

View File

@@ -0,0 +1,69 @@
@extends('layouts.app')
@section('tajuk', 'Kaunter Borang: ' . $sesi->nama)
@section('saiz-container', 'container-kecil')
@section('kandungan')
<div class="kad">
<h1>Kaunter Borang SPR</h1>
<p class="teks-kecil mb-0">
{{ $sesi->dun->code ?? '' }} {{ $sesi->dun->nama ?? '' }} |
{{ $sesi->nama }} ({{ $sesi->tarikh->format('d/m/Y') }})
</p>
</div>
<div class="kad">
<form method="GET" action="{{ route('kaunter.index', $sesi) }}">
<label for="nombor_siri">Nombor Siri Slip Petugas</label>
<input type="text" id="nombor_siri" name="nombor_siri" value="{{ request('nombor_siri') }}"
inputmode="numeric" placeholder="cth: 001" required autofocus>
<button type="submit" class="btn btn-penuh">Semak</button>
</form>
</div>
@if (! empty($ralat))
<div class="makluman makluman-ralat">{{ $ralat }}</div>
@endif
@if (! empty($kehadiran))
<div class="kad">
<div class="teks-tengah">
<p class="teks-kecil mb-0">Nombor siri</p>
<div class="slip-nombor">{{ $kehadiran->nombor_siri_papar }}</div>
</div>
<table class="slip-butiran">
<tr><th>Nama</th><td><strong>{{ $kehadiran->permohonan->nama_ic ?? '-' }}</strong></td></tr>
<tr><th>No. KP</th><td>{{ $kehadiran->permohonan->no_kp ?? $kehadiran->no_kp }}</td></tr>
<tr><th>Pusat Mengundi</th><td>{{ $penempatan['pusat_mengundi'] ?? '-' }}</td></tr>
<tr><th>Saluran</th><td>{{ $penempatan['saluran'] ?? '-' }}</td></tr>
<tr><th>Jawatan</th><td>{{ $penempatan['jawatan'] ?? '-' }}</td></tr>
<tr><th>Masa Daftar</th><td>{{ $kehadiran->hadir_at->format('d/m/Y h:i A') }}</td></tr>
<tr>
<th>Status Borang</th>
<td>
@if ($kehadiran->borang_diambil)
<span class="lencana lencana-hijau">Telah diambil</span>
<div class="teks-kecil">{{ $kehadiran->borang_diambil_at?->format('d/m/Y h:i A') }}</div>
@else
<span class="lencana lencana-merah">Belum diambil</span>
@endif
</td>
</tr>
</table>
@if (! $kehadiran->borang_diambil)
<form method="POST" action="{{ route('kaunter.serah', [$sesi, $kehadiran]) }}">
@csrf
<button type="submit" class="btn btn-hijau btn-penuh">
Sahkan Serahan Borang kepada Petugas
</button>
</form>
@else
<div class="makluman makluman-amaran mb-0">
Borang telah pun diserahkan kepada petugas ini.
</div>
@endif
</div>
@endif
@endsection

View File

@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="ms">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@yield('tajuk', config('app.name'))</title>
<link rel="stylesheet" href="{{ asset('css/app.css') }}">
</head>
<body>
<header class="topbar">
<a class="jenama" href="{{ auth()->check() ? route('sesi.index') : url('/') }}">
Kehadiran Taklimat SPR 2026
</a>
@auth
<div class="pengguna">
<span>{{ auth()->user()->name }} ({{ auth()->user()->role === 'admin' ? 'Admin' : 'Pengurus DUN' }})</span>
<form method="POST" action="{{ route('logout') }}" style="margin:0">
@csrf
<button type="submit" class="btn btn-kecil btn-merah">Log Keluar</button>
</form>
</div>
@endauth
</header>
<main class="container @yield('saiz-container')">
@if (session('status'))
<div class="makluman makluman-jaya">{{ session('status') }}</div>
@endif
@yield('kandungan')
</main>
</body>
</html>

View File

@@ -0,0 +1,130 @@
@extends('layouts.app')
@section('tajuk', 'Senarai Kehadiran: ' . $sesi->nama)
@section('kandungan')
<div class="kad">
<h1 class="mb-0">Senarai Kehadiran Taklimat</h1>
<p class="teks-kecil mb-0">
{{ $sesi->dun->code ?? '' }} {{ $sesi->dun->nama ?? '' }} |
{{ $sesi->nama }} ({{ $sesi->tarikh->format('d/m/Y') }}) | {{ $sesi->lokasi }}
</p>
</div>
<div class="stat-grid">
<div class="stat">
<div class="nilai">{{ $hadir->count() + $tidakHadir->count() }}</div>
<div class="label">Jumlah Petugas</div>
</div>
<div class="stat">
<div class="nilai" style="color:var(--hijau)">{{ $hadir->count() }}</div>
<div class="label">Hadir</div>
</div>
<div class="stat">
<div class="nilai" style="color:var(--merah)">{{ $tidakHadir->count() }}</div>
<div class="label">Tidak / Belum Hadir</div>
</div>
</div>
<div class="kad" style="overflow-x:auto; padding:0;">
<table class="jadual">
<thead>
<tr>
<th colspan="8" style="background:var(--hijau)">HADIR ({{ $hadir->count() }})</th>
</tr>
<tr>
<th>Siri</th>
<th>Nama</th>
<th>No. Telefon</th>
<th>Emel</th>
<th>Pusat Mengundi</th>
<th>Saluran</th>
<th>Jawatan</th>
<th>Borang</th>
</tr>
</thead>
<tbody>
@forelse ($hadir as $p)
@php
$penempatan = $p->butiranPenempatan();
$rekod = $kehadiran->get($p->id);
@endphp
<tr>
<td><strong>{{ $rekod?->nombor_siri_papar }}</strong></td>
<td>{{ $p->nama_ic }}</td>
<td>{{ $p->no_telefon ?? '-' }}</td>
<td>{{ $p->emel ?? '-' }}</td>
<td>{{ $penempatan['pusat_mengundi'] }}</td>
<td>{{ $penempatan['saluran'] }}</td>
<td>{{ $penempatan['jawatan'] }}</td>
<td>
@if ($rekod?->borang_diambil)
<span class="lencana lencana-hijau">Diambil</span>
@else
<span class="lencana lencana-kelabu">Belum</span>
@endif
</td>
</tr>
@empty
<tr><td colspan="8" class="teks-tengah">Tiada petugas hadir lagi.</td></tr>
@endforelse
</tbody>
</table>
</div>
<div class="kad" style="overflow-x:auto; padding:0;">
<table class="jadual">
<thead>
<tr>
<th colspan="7" style="background:var(--merah)">TIDAK / BELUM HADIR ({{ $tidakHadir->count() }})</th>
</tr>
<tr>
<th>Nama</th>
<th>No. Telefon</th>
<th>Emel</th>
<th>Pusat Mengundi</th>
<th>Saluran</th>
<th>Jawatan</th>
<th>Hubungi</th>
</tr>
</thead>
<tbody>
@forelse ($tidakHadir as $p)
@php
$penempatan = $p->butiranPenempatan();
$telefon = preg_replace('/[^0-9]/', '', $p->no_telefon ?? '');
$waNombor = $telefon !== '' ? (str_starts_with($telefon, '60') ? $telefon : '6' . $telefon) : '';
@endphp
<tr>
<td>{{ $p->nama_ic }}</td>
<td>{{ $p->no_telefon ?? '-' }}</td>
<td>{{ $p->emel ?? '-' }}</td>
<td>{{ $penempatan['pusat_mengundi'] }}</td>
<td>{{ $penempatan['saluran'] }}</td>
<td>{{ $penempatan['jawatan'] }}</td>
<td>
@if ($telefon !== '')
<div class="baris-tindakan">
<a class="btn btn-kecil btn-hijau" target="_blank" rel="noopener"
href="https://wa.me/{{ $waNombor }}?text={{ urlencode('Salam, ini peringatan taklimat petugas SPR bagi ' . ($sesi->dun->nama ?? '') . ' pada ' . $sesi->tarikh->format('d/m/Y') . ' di ' . $sesi->lokasi . '. Mohon hadir, terima kasih.') }}">
WhatsApp
</a>
<a class="btn btn-kecil" href="tel:{{ $telefon }}">Call</a>
</div>
@else
<span class="teks-kecil">Tiada no. telefon</span>
@endif
</td>
</tr>
@empty
<tr><td colspan="7" class="teks-tengah">Semua petugas telah hadir.</td></tr>
@endforelse
</tbody>
</table>
</div>
<div class="baris-tindakan tiada-cetak">
<a href="{{ route('sesi.show', $sesi) }}" class="btn btn-luar">Kembali ke Sesi</a>
<button onclick="window.print()" class="btn">Cetak Senarai</button>
</div>
@endsection

View File

@@ -0,0 +1,45 @@
@extends('layouts.app')
@section('tajuk', 'Cipta Sesi Taklimat')
@section('saiz-container', 'container-kecil')
@section('kandungan')
<div class="kad">
<h1>Cipta Sesi Taklimat</h1>
<p class="teks-kecil">Hanya satu sesi dibenarkan dalam satu hari bagi setiap DUN.</p>
@if ($errors->any())
<div class="makluman makluman-ralat">{{ $errors->first() }}</div>
@endif
<form method="POST" action="{{ route('sesi.store') }}">
@csrf
<label for="dun_id">DUN</label>
<select id="dun_id" name="dun_id" required>
<option value=""> Pilih DUN </option>
@foreach ($senaraiDun as $dun)
<option value="{{ $dun->id }}" @selected(old('dun_id') == $dun->id)>
{{ $dun->code }} {{ $dun->nama }}
</option>
@endforeach
</select>
<label for="tarikh">Tarikh</label>
<input type="date" id="tarikh" name="tarikh" value="{{ old('tarikh', now()->toDateString()) }}" required>
<label for="nama">Nama Sesi</label>
<input type="text" id="nama" name="nama" value="{{ old('nama') }}"
placeholder="cth: Taklimat Petugas Sesi 1" required>
<label for="lokasi">Lokasi</label>
<input type="text" id="lokasi" name="lokasi" value="{{ old('lokasi') }}"
placeholder="cth: Dewan Serbaguna Gelang Patah" required>
<label for="masa">Masa (pilihan)</label>
<input type="text" id="masa" name="masa" value="{{ old('masa') }}"
placeholder="cth: 9:00 pagi - 12:00 tengah hari">
<button type="submit" class="btn btn-penuh">Cipta Sesi</button>
</form>
</div>
@endsection

View File

@@ -0,0 +1,58 @@
@extends('layouts.app')
@section('tajuk', 'Senarai Sesi Taklimat')
@section('kandungan')
<div class="kad">
<div style="display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:0.5rem;">
<h1 class="mb-0">Sesi Taklimat</h1>
<a href="{{ route('sesi.create') }}" class="btn">+ Cipta Sesi</a>
</div>
</div>
@if ($sesi->isEmpty())
<div class="kad teks-tengah">
<p>Tiada sesi taklimat lagi. Cipta sesi pertama anda.</p>
</div>
@else
<div class="kad" style="overflow-x:auto; padding:0;">
<table class="jadual">
<thead>
<tr>
<th>Tarikh</th>
<th>DUN</th>
<th>Sesi</th>
<th>Lokasi</th>
<th>Hadir</th>
<th>Status</th>
<th>Tindakan</th>
</tr>
</thead>
<tbody>
@foreach ($sesi as $s)
<tr>
<td>{{ $s->tarikh->format('d/m/Y') }}</td>
<td>{{ $s->dun->code ?? '' }} {{ $s->dun->nama ?? '-' }}</td>
<td>{{ $s->nama }}</td>
<td>{{ $s->lokasi }}</td>
<td>{{ $s->kehadiran_count }}</td>
<td>
@if ($s->is_active)
<span class="lencana lencana-hijau">Aktif</span>
@else
<span class="lencana lencana-kelabu">Ditutup</span>
@endif
</td>
<td>
<div class="baris-tindakan">
<a href="{{ route('sesi.show', $s) }}" class="btn btn-kecil">Urus</a>
<a href="{{ route('pengurus.senarai', $s) }}" class="btn btn-kecil btn-luar">Senarai</a>
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endif
@endsection

View File

@@ -0,0 +1,27 @@
@extends('layouts.app')
@section('tajuk', 'QR Code: ' . $sesi->nama)
@section('kandungan')
<div class="kad teks-tengah">
<h1>Daftar Kehadiran Taklimat</h1>
<h2>{{ $sesi->dun->code ?? '' }} {{ $sesi->dun->nama ?? '' }}</h2>
<p>
<strong>{{ $sesi->nama }}</strong><br>
{{ $sesi->tarikh->format('d/m/Y') }}@if ($sesi->masa) | {{ $sesi->masa }}@endif<br>
{{ $sesi->lokasi }}
</p>
<div class="qr-wrap">
{!! QrCode::size(420)->margin(1)->generate($checkinUrl) !!}
</div>
<p><strong>Scan QR code ini dan masukkan No. Kad Pengenalan anda.</strong></p>
<p class="teks-kecil">{{ $checkinUrl }}</p>
<div class="baris-tindakan tiada-cetak" style="justify-content:center">
<button onclick="window.print()" class="btn">Cetak</button>
<a href="{{ route('sesi.show', $sesi) }}" class="btn btn-luar">Kembali</a>
</div>
</div>
@endsection

View File

@@ -0,0 +1,60 @@
@extends('layouts.app')
@section('tajuk', 'Urus Sesi: ' . $sesi->nama)
@section('kandungan')
<div class="kad">
<h1>{{ $sesi->nama }}</h1>
<table class="slip-butiran">
<tr><th>DUN</th><td>{{ $sesi->dun->code ?? '' }} {{ $sesi->dun->nama ?? '-' }}</td></tr>
<tr><th>Tarikh</th><td>{{ $sesi->tarikh->format('d/m/Y') }}</td></tr>
@if ($sesi->masa)
<tr><th>Masa</th><td>{{ $sesi->masa }}</td></tr>
@endif
<tr><th>Lokasi</th><td>{{ $sesi->lokasi }}</td></tr>
<tr>
<th>Status</th>
<td>
@if ($sesi->is_active)
<span class="lencana lencana-hijau">Aktif</span>
@else
<span class="lencana lencana-kelabu">Ditutup</span>
@endif
</td>
</tr>
<tr>
<th>Pautan Daftar</th>
<td><a href="{{ $checkinUrl }}" target="_blank">{{ $checkinUrl }}</a></td>
</tr>
</table>
<div class="baris-tindakan">
<a href="{{ route('sesi.qr', $sesi) }}" class="btn">Papar / Cetak QR Code</a>
<a href="{{ route('kaunter.index', $sesi) }}" class="btn btn-hijau">Kaunter Borang</a>
<a href="{{ route('pengurus.senarai', $sesi) }}" class="btn btn-luar">Senarai Kehadiran</a>
<form method="POST" action="{{ route('sesi.toggle', $sesi) }}" style="margin:0">
@csrf
<button type="submit" class="btn {{ $sesi->is_active ? 'btn-merah' : 'btn-hijau' }}">
{{ $sesi->is_active ? 'Tutup Pendaftaran' : 'Buka Pendaftaran' }}
</button>
</form>
</div>
</div>
<div class="stat-grid">
<div class="stat">
<div class="nilai">{{ $sesi->kehadiran_count }}</div>
<div class="label">Telah Daftar Hadir</div>
</div>
<div class="stat">
<div class="nilai">{{ $sesi->borang_diambil_count }}</div>
<div class="label">Telah Ambil Borang</div>
</div>
</div>
<div class="kad qr-wrap tiada-cetak">
<h2>QR Code Pendaftaran</h2>
{!! QrCode::size(220)->margin(1)->generate($checkinUrl) !!}
<p class="teks-kecil">Petugas scan QR ini untuk daftar kehadiran.</p>
</div>
@endsection

File diff suppressed because one or more lines are too long