Aplikasi kehadiran KTM ambil peti undi (SPR 2026)

Sistem untuk admin DUN merekod waktu KTM (Ketua Tempat Mengundi) hadir
mengambil peti undi. Data KTM dibaca daripada DB sppm (spr2026) melalui
connection kedua — hanya penempatan berjawatan KTM yang dipaparkan.

- Dashboard: senarai KTM ikut pusat mengundi (asc) & saluran (asc),
  status BELUM HADIR (merah) / HADIR (hijau) + tarikh & masa
- Carian nama/no. KP, tapisan pusat mengundi & status
- Tanda hadir / batal dengan kawalan DUN (admin terhad ke DUN sendiri)
- Eksport Excel (worksheet Hadir & Belum Hadir) guna SimpleXlsx
- Peranan: superadmin (urus pengguna, semua DUN) & admin DUN
- Docker: nginx + php-fpm + queue + scheduler + webhook deploy,
  port 127.0.0.1:8009, TZ Asia/Kuala_Lumpur di semua container
- Zon waktu aplikasi: Asia/Kuala_Lumpur (APP_TIMEZONE)

Berasaskan scaffold spr2026_taklimat.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Saufi
2026-07-11 02:40:00 +08:00
commit dfd22ff2af
95 changed files with 14681 additions and 0 deletions

View File

@@ -0,0 +1,148 @@
@extends('layouts.app')
@section('tajuk', 'Dashboard Kehadiran KTM')
@section('kandungan')
<div class="stat-grid">
<div class="stat">
<div class="nilai">{{ $jumlahSemua }}</div>
<div class="label">Jumlah KTM</div>
</div>
<div class="stat">
<div class="nilai" style="color: var(--hijau)">{{ $jumlahHadir }}</div>
<div class="label">Hadir</div>
</div>
<div class="stat">
<div class="nilai" style="color: var(--merah)">{{ $jumlahBelum }}</div>
<div class="label">Belum Hadir</div>
</div>
</div>
<div class="kad">
<form method="GET" action="{{ route('dashboard') }}">
<div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(180px, 1fr)); gap:0 0.75rem;">
@if (auth()->user()->isSuperadmin())
<div>
<label for="dun_id">DUN</label>
<select id="dun_id" name="dun_id" onchange="this.form.submit()">
<option value=""> Semua DUN </option>
@foreach ($senaraiDun as $dun)
<option value="{{ $dun->id }}" @selected($dunId == $dun->id)>
{{ $dun->code }} {{ $dun->nama }}
</option>
@endforeach
</select>
</div>
@endif
<div>
<label for="q">Cari Nama / No. KP</label>
<input type="text" id="q" name="q" value="{{ request('q') }}" placeholder="Nama KTM atau No. KP">
</div>
<div>
<label for="pusat_id">Pusat Mengundi</label>
<select id="pusat_id" name="pusat_id">
<option value=""> Semua Pusat </option>
@foreach ($senaraiPusat as $pusat)
<option value="{{ $pusat->id }}" @selected(request('pusat_id') == $pusat->id)>
{{ $pusat->nama }}
</option>
@endforeach
</select>
</div>
<div>
<label for="status">Status</label>
<select id="status" name="status">
<option value=""> Semua Status </option>
<option value="belum" @selected(request('status') === 'belum')>Belum Hadir</option>
<option value="hadir" @selected(request('status') === 'hadir')>Hadir</option>
</select>
</div>
</div>
<div class="baris-tindakan">
<button type="submit" class="btn">Cari / Tapis</button>
<a href="{{ route('dashboard') }}" class="btn btn-luar">Set Semula</a>
<a href="{{ route('kehadiran.eksport', array_filter(['dun_id' => auth()->user()->isSuperadmin() ? $dunId : null])) }}"
class="btn btn-hijau"> Muat Turun Excel</a>
</div>
</form>
</div>
<div class="kad" style="padding:0;">
<div class="jadual-kepala">
<h2 class="tajuk-jadual mb-0">Senarai KTM (Ketua Tempat Mengundi)</h2>
</div>
<div class="jadual-bekas">
<table class="jadual jadual-responsif">
<thead>
<tr>
<th>Bil</th>
<th>Pusat Mengundi</th>
<th>Saluran</th>
<th>Nama</th>
<th>No. KP</th>
<th>No. Telefon</th>
<th>Jawatan</th>
<th>Status</th>
<th>Tindakan</th>
</tr>
</thead>
<tbody>
@forelse ($assignments as $i => $a)
@php
$rekod = $kehadiran->get($a->id);
$p = $a->permohonan;
@endphp
<tr class="baris-data">
<td data-label="Bil">{{ $i + 1 }}</td>
<td data-label="Pusat Mengundi">{{ $a->pusatMengundi?->nama ?? '-' }}</td>
<td data-label="Saluran">{{ $a->saluran ? 'Saluran ' . $a->saluran->nombor_saluran : '-' }}</td>
<td data-label="Nama"><strong>{{ $p?->nama_ic ?? '-' }}</strong></td>
<td data-label="No. KP">{{ $p?->no_kp ?? '-' }}</td>
<td data-label="No. Telefon">{{ $p?->no_telefon ?? '-' }}</td>
<td data-label="Jawatan">{{ $a->jawatan?->nama ?? '-' }}</td>
<td data-label="Status">
@if ($rekod)
<span class="lencana lencana-hijau">HADIR</span>
<div class="teks-kecil">{{ $rekod->hadir_at->format('d/m/Y h:i A') }}</div>
@else
<span class="lencana lencana-merah">BELUM HADIR</span>
@endif
</td>
<td data-label="Tindakan">
<div class="baris-tindakan">
@if ($rekod)
<form method="POST" action="{{ route('kehadiran.batal', $a->id) }}"
onsubmit="return confirm('Batalkan rekod kehadiran {{ $p?->nama_ic }}?');">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-kecil btn-luar">Batal</button>
</form>
@else
<form method="POST" action="{{ route('kehadiran.hadir', $a->id) }}"
onsubmit="return confirm('Tandakan {{ $p?->nama_ic }} sebagai HADIR mengambil peti undi?');">
@csrf
<button type="submit" class="btn btn-kecil btn-hijau">Tanda Hadir</button>
</form>
@endif
</div>
</td>
</tr>
@empty
<tr class="baris-tiada">
<td colspan="9" class="teks-tengah">Tiada KTM dijumpai untuk tapisan ini.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="jadual-kaki">
<span class="teks-kecil">{{ $assignments->count() }} rekod dipaparkan</span>
</div>
</div>
@endsection