This commit is contained in:
Saufi
2026-06-24 18:30:00 +08:00
commit c0c3d7c09c
122 changed files with 16321 additions and 0 deletions

View File

@@ -0,0 +1,105 @@
@extends('layouts.app')
@section('title', 'Kaunter Kehadiran')
@section('content')
<div class="row justify-content-center">
<div class="col-lg-9">
<div class="card mb-3">
<div class="card-body counter-search">
<label class="fw-bold mb-2 fs-5"><i class="bi bi-search text-teal"></i> Cari Anggota</label>
<input type="text" id="searchBox" class="form-control" autocomplete="off"
placeholder="Taip nama / no. KP / no. pekerja..." autofocus>
<div class="form-text">Carian automatik. Boleh guna nama, nombor kad pengenalan atau nombor pekerja MBIP.</div>
</div>
</div>
<div id="searchStatus" class="text-center text-muted py-2"></div>
<div id="resultArea"></div>
</div>
</div>
@push('scripts')
<script>
const searchUrl = "{{ route('counter.search') }}";
const storeUrl = "{{ route('counter.store') }}";
let timer = null, lastTerm = '';
const $box = $('#searchBox'), $status = $('#searchStatus'), $area = $('#resultArea');
$box.on('input', function(){
const term = this.value.trim();
clearTimeout(timer);
if (term.length < 2) { $area.empty(); $status.text(''); return; }
timer = setTimeout(() => doSearch(term), 220);
});
function doSearch(term){
lastTerm = term;
$status.html('<span class="spinner-border spinner-border-sm text-teal"></span> Mencari...');
$.getJSON(searchUrl, { q: term })
.done(res => { if (term !== lastTerm) return; render(res); })
.fail(() => $status.html('<span class="text-danger">Ralat carian.</span>'));
}
function render(res){
$area.empty();
if (!res.data || res.data.length === 0){
$status.html('');
$area.html(`<div class="alert alert-danger d-flex align-items-center gap-2 fs-6">
<i class="bi bi-x-octagon-fill fs-3"></i>
<div><b>Rekod tidak dijumpai.</b><br>Individu ini bukan anggota koperasi yang berdaftar.</div></div>`);
return;
}
$status.html(`<small>${res.data.length} rekod dijumpai</small>`);
res.data.forEach(m => $area.append(cardHtml(m)));
}
function cardHtml(m){
const hadirBadge = m.hadir
? `<span class="badge badge-hadir"><i class="bi bi-check-circle"></i> Sudah Hadir</span>`
: `<span class="badge badge-belum">Belum Hadir</span>`;
const timeInfo = m.hadir ? `<div class="small text-success mt-1"><i class="bi bi-clock"></i> Masa hadir: ${m.attended_at ?? '-'}</div>` : '';
const aktif = m.status_aktif ? '' : `<span class="badge bg-warning text-dark ms-1">Tidak Aktif</span>`;
const btn = m.hadir
? `<button class="btn btn-success" disabled><i class="bi bi-check2-all"></i> Telah Hadir</button>`
: `<button class="btn btn-teal btn-lg px-4" onclick="recordAttend(${m.id}, this)"><i class="bi bi-person-check"></i> Rekod Hadir</button>`;
return `<div class="card result-card mb-2 ${m.hadir ? 'hadir':''}" id="m-${m.id}">
<div class="card-body d-flex flex-wrap justify-content-between align-items-center gap-3">
<div>
<div class="fs-5 fw-bold">${escapeHtml(m.nama)} ${aktif}</div>
<div class="text-muted small">
<i class="bi bi-person-badge"></i> ${m.no_pekerja ?? '-'} &nbsp;·&nbsp;
<i class="bi bi-credit-card-2-front"></i> ${m.masked_kp ?? '-'} &nbsp;·&nbsp;
<i class="bi bi-building"></i> ${escapeHtml(m.jabatan ?? '-')} / ${escapeHtml(m.bahagian ?? '-')}
</div>
<div class="mt-1">${hadirBadge}</div>
${timeInfo}
</div>
<div class="text-end" id="act-${m.id}">${btn}</div>
</div></div>`;
}
function recordAttend(id, btn){
$(btn).prop('disabled', true).html('<span class="spinner-border spinner-border-sm"></span> Menyimpan...');
$.post(storeUrl, { member_id: id })
.done(res => {
const m = res.member;
$(`#m-${id}`).addClass('hadir');
$(`#act-${id}`).html('<button class="btn btn-success" disabled><i class="bi bi-check2-all"></i> Telah Hadir</button>');
if (res.status === 'ok'){
Swal.fire({ icon:'success', title:'Kehadiran Direkod!', text: res.message, timer: 1600, showConfirmButton:false });
} else {
Swal.fire({ icon:'info', title:'Sudah Hadir', text: res.message });
}
})
.fail(xhr => {
$(btn).prop('disabled', false).html('<i class="bi bi-person-check"></i> Rekod Hadir');
Swal.fire({ icon:'error', title:'Ralat', text: (xhr.responseJSON?.message) || 'Gagal merekod kehadiran.' });
});
}
function escapeHtml(s){ return (s ?? '').toString().replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c])); }
</script>
@endpush
@endsection