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,36 @@
@extends('layouts.app')
@section('title', 'Audit Trail')
@section('content')
<form class="mb-3" method="GET">
<div class="input-group" style="max-width:360px;">
<select name="action" class="form-select">
<option value="">Semua Tindakan</option>
@foreach($actions as $a)<option value="{{ $a }}" @selected($action===$a)>{{ $a }}</option>@endforeach
</select>
<button class="btn btn-teal"><i class="bi bi-funnel"></i></button>
</div>
</form>
<div class="card"><div class="card-body">
<div class="table-responsive">
<table class="table table-koipb table-hover align-middle">
<thead><tr><th>Masa</th><th>Pengguna</th><th>Tindakan</th><th>Keterangan</th><th>IP</th></tr></thead>
<tbody>
@forelse($logs as $log)
<tr>
<td><small>{{ $log->created_at->format('d/m/Y H:i:s') }}</small></td>
<td>{{ $log->user?->name ?? 'Sistem' }}</td>
<td><span class="badge bg-dark2">{{ $log->action }}</span></td>
<td>{{ $log->description }}</td>
<td><small class="text-muted">{{ $log->ip_address ?? '-' }}</small></td>
</tr>
@empty
<tr><td colspan="5" class="text-center text-muted py-4">Tiada rekod audit.</td></tr>
@endforelse
</tbody>
</table>
</div>
{{ $logs->links() }}
</div></div>
@endsection

View File

@@ -0,0 +1,40 @@
@extends('layouts.app')
@section('title', 'Kemaskini Anggota')
@section('content')
<div class="row justify-content-center"><div class="col-lg-8">
<div class="card"><div class="card-body">
<form method="POST" action="{{ route('admin.members.update', $member) }}">
@csrf @method('PUT')
<div class="row g-3">
<div class="col-md-6"><label class="form-label">Nama *</label>
<input name="nama" class="form-control @error('nama') is-invalid @enderror" value="{{ old('nama', $member->nama) }}" required>
@error('nama')<div class="invalid-feedback">{{ $message }}</div>@enderror</div>
<div class="col-md-6"><label class="form-label">No Anggota</label>
<input name="no_anggota" class="form-control" value="{{ old('no_anggota', $member->no_anggota) }}"></div>
<div class="col-md-6"><label class="form-label">No Pekerja</label>
<input name="no_pekerja" class="form-control @error('no_pekerja') is-invalid @enderror" value="{{ old('no_pekerja', $member->no_pekerja) }}">
@error('no_pekerja')<div class="invalid-feedback">{{ $message }}</div>@enderror</div>
<div class="col-md-6"><label class="form-label">No KP</label>
<input name="no_kp" class="form-control @error('no_kp') is-invalid @enderror" value="{{ old('no_kp', $member->no_kp) }}">
@error('no_kp')<div class="invalid-feedback">{{ $message }}</div>@enderror</div>
<div class="col-md-6"><label class="form-label">Jabatan</label>
<input name="jabatan" class="form-control" value="{{ old('jabatan', $member->jabatan) }}"></div>
<div class="col-md-6"><label class="form-label">Bahagian</label>
<input name="bahagian" class="form-control" value="{{ old('bahagian', $member->bahagian) }}"></div>
<div class="col-md-6"><label class="form-label">Telefon</label>
<input name="telefon" class="form-control" value="{{ old('telefon', $member->telefon) }}"></div>
<div class="col-md-6"><label class="form-label">Status</label>
<select name="status_aktif" class="form-select">
<option value="1" @selected($member->status_aktif)>Aktif</option>
<option value="0" @selected(!$member->status_aktif)>Tidak Aktif</option>
</select></div>
</div>
<div class="mt-4 d-flex gap-2">
<button class="btn btn-teal"><i class="bi bi-save"></i> Simpan</button>
<a href="{{ route('admin.members.index') }}" class="btn btn-light">Kembali</a>
</div>
</form>
</div></div>
</div></div>
@endsection

View File

@@ -0,0 +1,55 @@
@extends('layouts.app')
@section('title', 'Import Anggota')
@section('content')
@php $result = session('import_result') ? \App\Models\ImportLog::find(session('import_result')) : null; @endphp
<div class="row g-3">
<div class="col-lg-5">
<div class="card"><div class="card-body">
<h5 class="fw-bold"><i class="bi bi-upload text-teal"></i> Muat Naik Fail</h5>
<p class="text-muted small">Format CSV atau XLSX. Lajur: <code>no_anggota, no_pekerja, no_kp, nama, jabatan, bahagian, telefon, status_aktif</code>. Hanya <b>nama</b> wajib.</p>
<form method="POST" action="{{ route('admin.members.import.store') }}" enctype="multipart/form-data">
@csrf
<input type="file" name="file" class="form-control mb-2 @error('file') is-invalid @enderror" accept=".csv,.xlsx,.xls" required>
@error('file')<div class="invalid-feedback">{{ $message }}</div>@enderror
<button class="btn btn-teal w-100"><i class="bi bi-cloud-upload"></i> Import Sekarang</button>
</form>
<a href="{{ asset('samples/contoh-anggota.csv') }}" class="btn btn-sm btn-link mt-2"><i class="bi bi-download"></i> Muat turun templat contoh</a>
</div></div>
</div>
<div class="col-lg-7">
@if($result)
<div class="card mb-3 border-start border-4 border-success"><div class="card-body">
<h6 class="fw-bold">Ringkasan Import: {{ $result->filename }}</h6>
<div class="row text-center g-2 my-2">
<div class="col"><div class="stat-card bg-teal py-2"><div class="value fs-4">{{ $result->total_rows }}</div><div class="label">Jumlah Baris</div></div></div>
<div class="col"><div class="stat-card bg-green py-2"><div class="value fs-4">{{ $result->success_count }}</div><div class="label">Berjaya</div></div></div>
<div class="col"><div class="stat-card bg-gold py-2"><div class="value fs-4">{{ $result->duplicate_count }}</div><div class="label">Duplicate</div></div></div>
<div class="col"><div class="stat-card bg-red py-2"><div class="value fs-4">{{ $result->failed_count }}</div><div class="label">Gagal</div></div></div>
</div>
@if($result->errors)
<details><summary class="text-danger">Lihat {{ count($result->errors) }} isu</summary>
<ul class="small mt-2 mb-0">@foreach($result->errors as $e)<li>{{ $e }}</li>@endforeach</ul>
</details>
@endif
</div></div>
@endif
<div class="card"><div class="card-body">
<h6 class="fw-bold"><i class="bi bi-clock-history"></i> Log Import Terkini</h6>
<table class="table table-sm align-middle">
<thead><tr><th>Fail</th><th>Berjaya</th><th>Dup</th><th>Gagal</th><th>Oleh</th><th>Masa</th></tr></thead>
<tbody>
@forelse($logs as $log)
<tr><td>{{ $log->filename }}</td><td class="text-success">{{ $log->success_count }}</td>
<td class="text-warning">{{ $log->duplicate_count }}</td><td class="text-danger">{{ $log->failed_count }}</td>
<td>{{ $log->importedBy?->name ?? '-' }}</td><td><small>{{ $log->created_at->format('d/m H:i') }}</small></td></tr>
@empty
<tr><td colspan="6" class="text-center text-muted">Belum ada import.</td></tr>
@endforelse
</tbody>
</table>
</div></div>
</div>
</div>
@endsection

View File

@@ -0,0 +1,49 @@
@extends('layouts.app')
@section('title', 'Urus Anggota')
@section('content')
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
<form class="d-flex gap-2" method="GET" style="max-width:420px;">
<input type="text" name="q" value="{{ $q }}" class="form-control" placeholder="Cari nama / no pekerja / KP...">
<button class="btn btn-teal"><i class="bi bi-search"></i></button>
</form>
<a href="{{ route('admin.members.import') }}" class="btn btn-outline-teal"><i class="bi bi-upload"></i> Import Anggota</a>
</div>
<div class="card">
<div class="card-body">
<div class="table-responsive">
<table class="table table-koipb table-hover align-middle">
<thead><tr>
<th>No Anggota</th><th>Nama</th><th>No Pekerja</th><th>No KP</th><th>Jabatan</th><th>Status</th><th>Hadir</th><th></th>
</tr></thead>
<tbody>
@forelse($members as $m)
<tr>
<td>{{ $m->no_anggota ?? '-' }}</td>
<td class="fw-semibold">{{ $m->nama }}
@if($m->confirmed_wins) <span class="badge bg-gold"><i class="bi bi-trophy"></i> Menang</span> @endif
</td>
<td>{{ $m->no_pekerja ?? '-' }}</td>
<td><small class="text-muted">{{ $m->maskedKp() ?? '-' }}</small></td>
<td>{{ $m->jabatan ?? '-' }}</td>
<td>@if($m->status_aktif)<span class="pill pill-disahkan">Aktif</span>@else<span class="pill pill-redraw">Tidak Aktif</span>@endif</td>
<td>@if($m->attendance)<span class="badge badge-hadir">Hadir</span>@else<span class="badge badge-belum">Belum</span>@endif</td>
<td class="text-end">
<a href="{{ route('admin.members.edit', $m) }}" class="btn btn-sm btn-outline-secondary"><i class="bi bi-pencil"></i></a>
<form action="{{ route('admin.members.destroy', $m) }}" method="POST" class="d-inline" onsubmit="return confirm('Padam anggota {{ $m->nama }}?')">
@csrf @method('DELETE')
<button class="btn btn-sm btn-outline-danger"><i class="bi bi-trash"></i></button>
</form>
</td>
</tr>
@empty
<tr><td colspan="8" class="text-center text-muted py-4">Tiada anggota. Sila import senarai anggota.</td></tr>
@endforelse
</tbody>
</table>
</div>
{{ $members->links() }}
</div>
</div>
@endsection

View File

@@ -0,0 +1,29 @@
@extends('layouts.app')
@section('title', 'Kemaskini Hadiah')
@section('content')
<div class="row justify-content-center"><div class="col-lg-7">
<div class="card"><div class="card-body">
<form method="POST" action="{{ route('admin.prizes.update', $prize) }}">
@csrf @method('PUT')
<div class="row g-3">
<div class="col-md-8"><label class="form-label">Nama Hadiah *</label>
<input name="nama_hadiah" class="form-control @error('nama_hadiah') is-invalid @enderror" value="{{ old('nama_hadiah', $prize->nama_hadiah) }}" required>
@error('nama_hadiah')<div class="invalid-feedback">{{ $message }}</div>@enderror</div>
<div class="col-md-4"><label class="form-label">Susunan Cabutan *</label>
<input type="number" name="draw_order" class="form-control" value="{{ old('draw_order', $prize->draw_order) }}" required></div>
<div class="col-md-4"><label class="form-label">Kod</label>
<input name="kod_hadiah" class="form-control" value="{{ old('kod_hadiah', $prize->kod_hadiah) }}"></div>
<div class="col-md-4"><label class="form-label">Kategori</label>
<input name="kategori" class="form-control" value="{{ old('kategori', $prize->kategori) }}"></div>
<div class="col-md-4"><label class="form-label">Nilai Anggaran (RM)</label>
<input type="number" step="0.01" name="nilai_anggaran" class="form-control" value="{{ old('nilai_anggaran', $prize->nilai_anggaran) }}"></div>
</div>
<div class="mt-4 d-flex gap-2">
<button class="btn btn-teal"><i class="bi bi-save"></i> Simpan</button>
<a href="{{ route('admin.prizes.index') }}" class="btn btn-light">Kembali</a>
</div>
</form>
</div></div>
</div></div>
@endsection

View File

@@ -0,0 +1,51 @@
@extends('layouts.app')
@section('title', 'Import Hadiah')
@section('content')
@php $result = session('import_result') ? \App\Models\ImportLog::find(session('import_result')) : null; @endphp
<div class="row g-3">
<div class="col-lg-5">
<div class="card"><div class="card-body">
<h5 class="fw-bold"><i class="bi bi-upload text-teal"></i> Muat Naik Fail</h5>
<p class="text-muted small">Format CSV atau XLSX. Lajur: <code>kod_hadiah, nama_hadiah, kategori, nilai_anggaran, susunan_cabutan, kuantiti</code>.
Jika <b>kuantiti &gt; 1</b>, sistem akan jana beberapa item berasingan (cth Hamper #1 … #5).</p>
<form method="POST" action="{{ route('admin.prizes.import.store') }}" enctype="multipart/form-data">
@csrf
<input type="file" name="file" class="form-control mb-2 @error('file') is-invalid @enderror" accept=".csv,.xlsx,.xls" required>
@error('file')<div class="invalid-feedback">{{ $message }}</div>@enderror
<button class="btn btn-teal w-100"><i class="bi bi-cloud-upload"></i> Import Sekarang</button>
</form>
<a href="{{ asset('samples/contoh-hadiah.csv') }}" class="btn btn-sm btn-link mt-2"><i class="bi bi-download"></i> Muat turun templat contoh</a>
</div></div>
</div>
<div class="col-lg-7">
@if($result)
<div class="card mb-3 border-start border-4 border-success"><div class="card-body">
<h6 class="fw-bold">Ringkasan Import: {{ $result->filename }}</h6>
<div class="row text-center g-2 my-2">
<div class="col"><div class="stat-card bg-teal py-2"><div class="value fs-4">{{ $result->total_rows }}</div><div class="label">Baris</div></div></div>
<div class="col"><div class="stat-card bg-green py-2"><div class="value fs-4">{{ $result->success_count }}</div><div class="label">Item Dijana</div></div></div>
<div class="col"><div class="stat-card bg-red py-2"><div class="value fs-4">{{ $result->failed_count }}</div><div class="label">Gagal</div></div></div>
</div>
@if($result->errors)
<details><summary class="text-danger">Lihat {{ count($result->errors) }} isu</summary>
<ul class="small mt-2 mb-0">@foreach($result->errors as $e)<li>{{ $e }}</li>@endforeach</ul></details>
@endif
</div></div>
@endif
<div class="card"><div class="card-body">
<h6 class="fw-bold"><i class="bi bi-clock-history"></i> Log Import Terkini</h6>
<table class="table table-sm align-middle">
<thead><tr><th>Fail</th><th>Item</th><th>Gagal</th><th>Oleh</th><th>Masa</th></tr></thead>
<tbody>
@forelse($logs as $log)
<tr><td>{{ $log->filename }}</td><td class="text-success">{{ $log->success_count }}</td>
<td class="text-danger">{{ $log->failed_count }}</td><td>{{ $log->importedBy?->name ?? '-' }}</td>
<td><small>{{ $log->created_at->format('d/m H:i') }}</small></td></tr>
@empty<tr><td colspan="5" class="text-center text-muted">Belum ada import.</td></tr>@endforelse
</tbody>
</table>
</div></div>
</div>
</div>
@endsection

View File

@@ -0,0 +1,42 @@
@extends('layouts.app')
@section('title', 'Urus Hadiah')
@section('content')
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="text-muted">Susunan cabutan mengikut <code>draw_order</code>.</div>
<a href="{{ route('admin.prizes.import') }}" class="btn btn-outline-teal"><i class="bi bi-upload"></i> Import Hadiah</a>
</div>
<div class="card"><div class="card-body">
<div class="table-responsive">
<table class="table table-koipb table-hover align-middle">
<thead><tr><th>Susunan</th><th>Kod</th><th>Hadiah</th><th>Kategori</th><th>Nilai (RM)</th><th>Status</th><th>Pemenang</th><th></th></tr></thead>
<tbody>
@forelse($prizes as $p)
@php $pill = ['belum_dicabut'=>'pill-belum','sedang_dicabut'=>'pill-sedang','disahkan'=>'pill-disahkan','redraw_required'=>'pill-redraw'][$p->status]; @endphp
<tr>
<td><span class="badge bg-dark2">#{{ $p->draw_order }}</span></td>
<td>{{ $p->kod_hadiah ?? '-' }}</td>
<td class="fw-semibold">{{ $p->nama_hadiah }}</td>
<td>{{ $p->kategori ?? '-' }}</td>
<td>{{ $p->nilai_anggaran ? number_format($p->nilai_anggaran, 2) : '-' }}</td>
<td><span class="pill {{ $pill }}">{{ $p->statusLabel() }}</span>
@if($p->redraw_count) <small class="text-muted">({{ $p->redraw_count }}x redraw)</small>@endif</td>
<td>{{ $p->drawResults->firstWhere('status','confirmed')?->member?->nama ?? '-' }}</td>
<td class="text-end">
<a href="{{ route('admin.prizes.edit', $p) }}" class="btn btn-sm btn-outline-secondary"><i class="bi bi-pencil"></i></a>
<form action="{{ route('admin.prizes.destroy', $p) }}" method="POST" class="d-inline" onsubmit="return confirm('Padam hadiah ini?')">
@csrf @method('DELETE')
<button class="btn btn-sm btn-outline-danger" @disabled($p->status==='disahkan')><i class="bi bi-trash"></i></button>
</form>
</td>
</tr>
@empty
<tr><td colspan="8" class="text-center text-muted py-4">Tiada hadiah. Sila import senarai hadiah.</td></tr>
@endforelse
</tbody>
</table>
</div>
{{ $prizes->links() }}
</div></div>
@endsection

View File

@@ -0,0 +1,40 @@
@extends('layouts.app')
@section('title', 'Tetapan & Reset')
@section('content')
<div class="row g-3">
<div class="col-lg-6">
<div class="card"><div class="card-body">
<h5 class="fw-bold"><i class="bi bi-gear text-teal"></i> Tetapan Sesi Cabutan</h5>
<form method="POST" action="{{ route('admin.settings.session') }}">
@csrf @method('PUT')
<div class="mb-3"><label class="form-label">Nama Sesi / Event</label>
<input name="nama" class="form-control" value="{{ old('nama', $session->nama) }}" required></div>
<div class="mb-3"><label class="form-label">Peserta dibatalkan (tiada di dewan)</label>
<select name="return_cancelled_to_pool" class="form-select">
<option value="1" @selected($session->return_cancelled_to_pool)>Masukkan semula ke pool cabutan (default)</option>
<option value="0" @selected(!$session->return_cancelled_to_pool)>Keluarkan terus dari pool</option>
</select></div>
<button class="btn btn-teal"><i class="bi bi-save"></i> Simpan Tetapan</button>
</form>
</div></div>
</div>
<div class="col-lg-6">
<div class="card border-danger"><div class="card-body">
<h5 class="fw-bold text-danger"><i class="bi bi-exclamation-triangle"></i> Reset Data Event</h5>
<p class="text-muted small">Untuk mula event baharu. Tindakan ini tidak boleh diundur.</p>
<form method="POST" action="{{ route('admin.settings.reset') }}" id="resetForm">
@csrf
<div class="mb-3"><label class="form-label">Skop Reset</label>
<select name="scope" class="form-select">
<option value="draw">Reset cabutan sahaja (kekalkan kehadiran)</option>
<option value="all">Reset semua (kehadiran + cabutan)</option>
</select></div>
<div class="mb-3"><label class="form-label">Taip <code>RESET</code> untuk sahkan</label>
<input name="confirm" class="form-control" placeholder="RESET" autocomplete="off" required></div>
<button class="btn btn-danger" onclick="return confirm('Anda pasti mahu reset data event? Tindakan ini kekal.')"><i class="bi bi-trash"></i> Reset Sekarang</button>
</form>
</div></div>
</div>
</div>
@endsection