Taip nama/no. KP terus mencari tanpa klik butang (debounce 300ms). Tukar pusat mengundi/status juga menapis serta-merta. Jadual dan statistik digantikan melalui fetch; URL dikemas kini (replaceState) supaya refresh dan eksport kekal konsisten. Butang Cari kekal sebagai fallback tanpa JavaScript; pilihan DUN (superadmin) kekal muat semula penuh kerana senarai pusat mengundi berubah. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
211 lines
9.6 KiB
PHP
211 lines
9.6 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('tajuk', 'Dashboard Kehadiran KTM')
|
|
|
|
@section('kandungan')
|
|
<div class="stat-grid" id="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') }}" id="borang-tapis">
|
|
<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;" id="senarai-ktm">
|
|
<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>
|
|
|
|
<script>
|
|
(function () {
|
|
var borang = document.getElementById('borang-tapis');
|
|
var inputCari = document.getElementById('q');
|
|
var pilihPusat = document.getElementById('pusat_id');
|
|
var pilihStatus = document.getElementById('status');
|
|
var timer = null;
|
|
var fetchSemasa = null;
|
|
|
|
// Cari terus semasa menaip (AJAX) — butang Cari kekal sebagai fallback tanpa JS.
|
|
function cariSekarang() {
|
|
var params = new URLSearchParams(new FormData(borang));
|
|
var url = borang.action + (params.toString() ? '?' + params.toString() : '');
|
|
|
|
if (fetchSemasa) {
|
|
fetchSemasa.abort();
|
|
}
|
|
fetchSemasa = new AbortController();
|
|
|
|
document.getElementById('senarai-ktm').style.opacity = '0.5';
|
|
|
|
fetch(url, {
|
|
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
|
signal: fetchSemasa.signal,
|
|
})
|
|
.then(function (r) { return r.text(); })
|
|
.then(function (html) {
|
|
var doc = new DOMParser().parseFromString(html, 'text/html');
|
|
['stat-grid', 'senarai-ktm'].forEach(function (id) {
|
|
var baru = doc.getElementById(id);
|
|
if (baru) {
|
|
document.getElementById(id).replaceWith(baru);
|
|
}
|
|
});
|
|
// Kekalkan URL supaya refresh/eksport/set-semula konsisten
|
|
window.history.replaceState(null, '', url);
|
|
})
|
|
.catch(function (e) {
|
|
if (e.name !== 'AbortError') {
|
|
borang.submit(); // fallback: muat semula penuh
|
|
}
|
|
});
|
|
}
|
|
|
|
function cariLewat() {
|
|
clearTimeout(timer);
|
|
timer = setTimeout(cariSekarang, 300);
|
|
}
|
|
|
|
inputCari.addEventListener('input', cariLewat);
|
|
pilihPusat.addEventListener('change', cariSekarang);
|
|
pilihStatus.addEventListener('change', cariSekarang);
|
|
|
|
// Elak penghantaran borang penuh bila tekan Enter dalam kotak carian
|
|
borang.addEventListener('submit', function (e) {
|
|
e.preventDefault();
|
|
clearTimeout(timer);
|
|
cariSekarang();
|
|
});
|
|
})();
|
|
</script>
|
|
@endsection
|