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,26 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\AuditLog;
use Illuminate\Http\Request;
use Illuminate\View\View;
class AuditController extends Controller
{
public function index(Request $request): View
{
$action = $request->query('action');
$logs = AuditLog::with('user')
->when($action, fn ($q) => $q->where('action', $action))
->latest()
->paginate(40)
->withQueryString();
$actions = AuditLog::query()->distinct()->orderBy('action')->pluck('action');
return view('admin.audit.index', compact('logs', 'actions', 'action'));
}
}

View File

@@ -0,0 +1,91 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\AuditLog;
use App\Models\ImportLog;
use App\Models\Member;
use App\Services\MemberImportService;
use App\Support\Spreadsheet;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class MemberController extends Controller
{
public function index(Request $request): View
{
$q = $request->query('q');
$members = Member::query()
->when($q, fn ($query) => $query->search($q))
->withCount(['drawResults as confirmed_wins' => fn ($query) => $query->where('status', \App\Models\DrawResult::STATUS_CONFIRMED)])
->with('attendance')
->orderBy('nama')
->paginate(25)
->withQueryString();
$importLogs = ImportLog::where('type', 'members')->with('importedBy')->latest()->take(5)->get();
return view('admin.members.index', compact('members', 'q', 'importLogs'));
}
public function edit(Member $member): View
{
return view('admin.members.edit', compact('member'));
}
public function update(Request $request, Member $member): RedirectResponse
{
$data = $request->validate([
'no_anggota' => ['nullable', 'string', 'max:255'],
'no_pekerja' => ['nullable', 'string', 'max:255', "unique:members,no_pekerja,{$member->id}"],
'no_kp' => ['nullable', 'string', 'max:255', "unique:members,no_kp,{$member->id}"],
'nama' => ['required', 'string', 'max:255'],
'jabatan' => ['nullable', 'string', 'max:255'],
'bahagian' => ['nullable', 'string', 'max:255'],
'telefon' => ['nullable', 'string', 'max:255'],
'status_aktif' => ['required', 'boolean'],
]);
$member->update($data);
AuditLog::record('member.update', "Kemaskini anggota: {$member->nama}", $member);
return redirect()->route('admin.members.index')->with('success', 'Maklumat anggota dikemaskini.');
}
public function destroy(Member $member): RedirectResponse
{
$nama = $member->nama;
$member->delete();
AuditLog::record('member.delete', "Padam anggota: {$nama}");
return redirect()->route('admin.members.index')->with('success', "Anggota '{$nama}' dipadam.");
}
public function showImport(): View
{
$logs = ImportLog::where('type', 'members')->with('importedBy')->latest()->take(15)->get();
return view('admin.members.import', compact('logs'));
}
public function import(Request $request, MemberImportService $service): RedirectResponse
{
$request->validate([
'file' => ['required', 'file', 'mimes:csv,txt,xlsx,xls', 'max:10240'],
]);
$file = $request->file('file');
$rows = Spreadsheet::read($file->getRealPath(), $file->getClientOriginalExtension());
$log = $service->import($rows, $file->getClientOriginalName(), $request->user()->id);
AuditLog::record('member.import', "Import anggota: {$log->filename}", $log, [
'success' => $log->success_count,
'duplicate' => $log->duplicate_count,
'failed' => $log->failed_count,
]);
return redirect()->route('admin.members.import')->with('import_result', $log->id);
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\AuditLog;
use App\Models\ImportLog;
use App\Models\Prize;
use App\Services\PrizeImportService;
use App\Support\Spreadsheet;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class PrizeController extends Controller
{
public function index(): View
{
$prizes = Prize::query()->ordered()->with('drawResults.member')->paginate(30);
$importLogs = ImportLog::where('type', 'prizes')->with('importedBy')->latest()->take(5)->get();
return view('admin.prizes.index', compact('prizes', 'importLogs'));
}
public function edit(Prize $prize): View
{
return view('admin.prizes.edit', compact('prize'));
}
public function update(Request $request, Prize $prize): RedirectResponse
{
$data = $request->validate([
'kod_hadiah' => ['nullable', 'string', 'max:255'],
'nama_hadiah' => ['required', 'string', 'max:255'],
'kategori' => ['nullable', 'string', 'max:255'],
'nilai_anggaran' => ['nullable', 'numeric', 'min:0'],
'draw_order' => ['required', 'integer', 'min:0'],
]);
$prize->update($data);
AuditLog::record('prize.update', "Kemaskini hadiah: {$prize->nama_hadiah}", $prize);
return redirect()->route('admin.prizes.index')->with('success', 'Maklumat hadiah dikemaskini.');
}
public function destroy(Prize $prize): RedirectResponse
{
if ($prize->status === Prize::STATUS_DISAHKAN) {
return back()->with('error', 'Hadiah yang sudah ada pemenang disahkan tidak boleh dipadam.');
}
$nama = $prize->nama_hadiah;
$prize->delete();
AuditLog::record('prize.delete', "Padam hadiah: {$nama}");
return redirect()->route('admin.prizes.index')->with('success', "Hadiah '{$nama}' dipadam.");
}
public function showImport(): View
{
$logs = ImportLog::where('type', 'prizes')->with('importedBy')->latest()->take(15)->get();
return view('admin.prizes.import', compact('logs'));
}
public function import(Request $request, PrizeImportService $service): RedirectResponse
{
$request->validate([
'file' => ['required', 'file', 'mimes:csv,txt,xlsx,xls', 'max:10240'],
]);
$file = $request->file('file');
$rows = Spreadsheet::read($file->getRealPath(), $file->getClientOriginalExtension());
$log = $service->import($rows, $file->getClientOriginalName(), $request->user()->id);
AuditLog::record('prize.import', "Import hadiah: {$log->filename}", $log, [
'success' => $log->success_count,
'failed' => $log->failed_count,
]);
return redirect()->route('admin.prizes.import')->with('import_result', $log->id);
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\AttendanceRecord;
use App\Models\AuditLog;
use App\Models\DrawResult;
use App\Models\DrawSession;
use App\Models\Prize;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;
class SettingController extends Controller
{
public function index(): View
{
$session = DrawSession::active();
return view('admin.settings.index', compact('session'));
}
public function updateSession(Request $request): RedirectResponse
{
$data = $request->validate([
'nama' => ['required', 'string', 'max:255'],
'return_cancelled_to_pool' => ['required', 'boolean'],
]);
$session = DrawSession::active();
$session->update($data);
AuditLog::record('setting.session', 'Kemaskini tetapan sesi cabutan', $session, $data);
return back()->with('success', 'Tetapan sesi dikemaskini.');
}
/** Reset data kehadiran & cabutan untuk event baru */
public function reset(Request $request): RedirectResponse
{
$request->validate([
'confirm' => ['required', 'in:RESET'],
'scope' => ['required', 'in:all,draw'],
]);
DB::transaction(function () use ($request) {
DrawResult::query()->delete();
Prize::query()->update(['status' => Prize::STATUS_BELUM, 'redraw_count' => 0]);
if ($request->scope === 'all') {
AttendanceRecord::query()->delete();
}
});
AuditLog::record('setting.reset', 'Reset data event', null, ['scope' => $request->scope]);
return back()->with('success', 'Data event berjaya direset.');
}
}