This commit is contained in:
Saufi
2026-06-03 08:51:22 +08:00
commit a14d43fe34
347 changed files with 38197 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
@extends('layouts.app')
@section('title', 'Semakan Permohonan PPM')
@section('content')
<div class="page-heading">
<div class="eyebrow">PPM</div>
<div class="d-flex justify-content-between flex-column flex-lg-row gap-3">
<div>
<h1 class="h3 mb-1">Semakan Permohonan</h1>
<p class="text-secondary mb-0">Permohonan untuk Pusat Mengundi di bawah jagaan anda sahaja.</p>
</div>
</div>
</div>
<div class="content-panel p-3 p-md-4 mb-4">
<form method="GET" class="row g-2 align-items-end">
<div class="col-12 col-md-4">
<label for="status" class="form-label">Status</label>
<select id="status" name="status" class="form-select">
<option value="">Semua status</option>
@foreach (['submitted', 'under_ppm_review', 'approved', 'assigned', 'rejected'] as $option)
<option value="{{ $option }}" @selected($status === $option)>{{ $option }}</option>
@endforeach
</select>
</div>
<div class="col-12 col-md-auto">
<button type="submit" class="btn btn-primary"><i class="bi bi-funnel me-1"></i>Tapis</button>
</div>
</form>
</div>
<div class="content-panel p-3 p-md-4">
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<x-sort-th column="name" label="Nama" />
<th>Pusat</th>
<th>Jawatan</th>
<x-sort-th column="status" label="Status" />
<x-sort-th column="created_at" label="Tarikh" />
<th class="text-end no-sort">Tindakan</th>
</tr>
</thead>
<tbody>
@forelse ($applications as $application)
<tr>
<td>
<div class="fw-semibold">{{ $application->name }}</div>
<div class="small text-secondary">{{ Illuminate\Support\Str::mask($application->ic_number, '*', 6, 4) }}</div>
</td>
<td>{{ $application->pusatMengundi?->name }}</td>
<td>
{{ $application->approvedPosition?->code ?? $application->requestedPosition?->code }}
<div class="small text-secondary">{{ $application->requestedPosition?->name }}</div>
</td>
<td><span class="badge text-bg-light border">{{ $application->status }}</span></td>
<td>{{ $application->created_at?->format('d/m/Y H:i') }}</td>
<td class="text-end">
<a href="{{ route('ppm.applications.show', $application->public_uuid) }}" class="btn btn-sm btn-outline-primary">Semak</a>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="text-center text-secondary py-4">Tiada permohonan untuk semakan.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
{{ $applications->links() }}
</div>
@endsection

View File

@@ -0,0 +1,265 @@
@extends('layouts.app')
@section('title', 'Semak Permohonan')
@section('content')
<div class="page-heading">
<div class="eyebrow">PPM</div>
<div class="d-flex justify-content-between flex-column flex-lg-row gap-3">
<div>
<h1 class="h3 mb-1">{{ $application->name }}</h1>
<p class="text-secondary mb-0">{{ $application->pusatMengundi?->code }} - {{ $application->pusatMengundi?->name }}</p>
</div>
<a href="{{ route('ppm.applications.index') }}" class="btn btn-outline-secondary align-self-start">
<i class="bi bi-arrow-left me-1"></i>Kembali
</a>
</div>
</div>
<div class="row g-4">
<div class="col-12 col-xl-7">
<div class="content-panel p-3 p-md-4 mb-4">
<div class="d-flex justify-content-between align-items-start gap-3 mb-3">
<h2 class="h5 mb-0">Maklumat Pemohon</h2>
<span class="badge text-bg-light border">{{ $application->status }}</span>
</div>
<dl class="row mb-0">
<dt class="col-sm-4">No. Kad Pengenalan</dt>
<dd class="col-sm-8">{{ $application->ic_number }}</dd>
<dt class="col-sm-4">Telefon</dt>
<dd class="col-sm-8">{{ $application->phone_number }}</dd>
<dt class="col-sm-4">Emel</dt>
<dd class="col-sm-8">{{ $application->email ?: '-' }}</dd>
<dt class="col-sm-4">Alamat</dt>
<dd class="col-sm-8">{{ $application->address ?: '-' }}</dd>
<dt class="col-sm-4">Jawatan Dipohon</dt>
<dd class="col-sm-8">{{ $application->requestedPosition?->code }} - {{ $application->requestedPosition?->name }}</dd>
<dt class="col-sm-4">Jawatan Diluluskan</dt>
<dd class="col-sm-8">
@if ($application->approvedPosition)
{{ $application->approvedPosition->code }} - {{ $application->approvedPosition->name }}
@else
<span class="text-secondary">Belum diluluskan</span>
@endif
</dd>
<dt class="col-sm-4">Bank</dt>
<dd class="col-sm-8">{{ $application->bank_name }} / {{ Illuminate\Support\Str::mask((string) $application->bank_account_number, '*', 4, 4) }}</dd>
<dt class="col-sm-4">KTM Pilihan Pemohon</dt>
<dd class="col-sm-8">
@if ($application->selectedKtmAssignment)
{{ $application->selectedKtmAssignment->user?->name ?? '-' }}
<span class="text-secondary">Saluran {{ $application->selectedKtmAssignment->saluranMengundi?->number ?? '-' }}</span>
@else
-
@endif
</dd>
</dl>
</div>
<div class="content-panel p-3 p-md-4 mb-4">
<h2 class="h5 mb-3">Dokumen</h2>
<div class="table-responsive">
<table class="table table-sm align-middle">
<thead>
<tr>
<th>Jenis</th>
<th>Fail</th>
<th class="text-end">Tindakan</th>
</tr>
</thead>
<tbody>
@forelse ($application->documents as $document)
<tr>
<td>{{ $document->document_type }}</td>
<td>{{ $document->original_name }}</td>
<td class="text-end">
<a href="{{ route('ppm.applications.documents.download', [$application->public_uuid, $document]) }}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-download me-1"></i>Muat turun
</a>
</td>
</tr>
@empty
<tr>
<td colspan="3" class="text-center text-secondary py-3">Tiada dokumen.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
<div class="content-panel p-3 p-md-4">
<h2 class="h5 mb-3">Wakil Agensi</h2>
<div class="row g-3">
<div class="col-12 col-lg-4">
<div class="border rounded p-3 h-100">
<div class="fw-semibold mb-2">Polis Pengiring</div>
@forelse ($application->pusatMengundi?->policeEscorts ?? [] as $police)
<div class="small">{{ $police->name }} <span class="text-secondary">{{ $police->saluranMengundi?->number ? 'Saluran '.$police->saluranMengundi->number : '' }}</span></div>
@empty
<div class="small text-secondary">Tiada rekod.</div>
@endforelse
</div>
</div>
<div class="col-12 col-lg-4">
<div class="border rounded p-3 h-100">
<div class="fw-semibold mb-2">Wakil KKM</div>
@forelse ($application->pusatMengundi?->kkmRepresentatives ?? [] as $kkm)
<div class="small">{{ $kkm->name }}</div>
@empty
<div class="small text-secondary">Tiada rekod.</div>
@endforelse
</div>
</div>
<div class="col-12 col-lg-4">
<div class="border rounded p-3 h-100">
<div class="fw-semibold mb-2">Wakil JKM</div>
@forelse ($application->pusatMengundi?->jkmRepresentatives ?? [] as $jkm)
<div class="small">{{ $jkm->name }}</div>
@empty
<div class="small text-secondary">Tiada rekod.</div>
@endforelse
</div>
</div>
</div>
</div>
</div>
<div class="col-12 col-xl-5">
<div class="content-panel p-3 p-md-4 mb-4">
<h2 class="h5 mb-3">Lulus dan Assign</h2>
@if (in_array($application->status, ['submitted', 'under_ppm_review', 'approved'], true))
<form method="POST" action="{{ route('ppm.applications.approve', $application->public_uuid) }}">
@csrf
<div class="mb-3">
<label for="approved_position_code" class="form-label">Jawatan Diluluskan</label>
<select id="approved_position_code" name="approved_position_code" class="form-select" required>
@foreach ($roles as $role)
<option value="{{ $role->code }}" @selected(old('approved_position_code', $application->approvedPosition?->code ?? $application->requestedPosition?->code) === $role->code)>
{{ $role->code }} - {{ $role->name }}
</option>
@endforeach
</select>
</div>
<div class="mb-3 d-none" id="saluran-section">
<label for="saluran_mengundi_id" class="form-label">Saluran Mengundi untuk KTM</label>
<select id="saluran_mengundi_id" name="saluran_mengundi_id" class="form-select">
<option value="">Pilih Saluran</option>
@foreach ($availableKtmSalurans as $saluran)
<option value="{{ $saluran->id }}" @selected((int) old('saluran_mengundi_id') === $saluran->id)>
Saluran {{ $saluran->number }}{{ $saluran->name ? ' - '.$saluran->name : '' }}
</option>
@endforeach
</select>
</div>
<div class="mb-3 d-none" id="ktm-section">
<label for="ktm_assignment_id" class="form-label">KTM untuk KP/KPDP</label>
<select id="ktm_assignment_id" name="ktm_assignment_id" class="form-select" data-old-value="{{ old('ktm_assignment_id', $application->selected_ktm_assignment_id) }}">
<option value="">Pilih KTM</option>
</select>
</div>
<div class="mb-3">
<label for="note" class="form-label">Catatan</label>
<textarea id="note" name="note" class="form-control" rows="3">{{ old('note') }}</textarea>
</div>
<button type="submit" class="btn btn-primary w-100">
<i class="bi bi-person-check me-1"></i>Lulus dan Assign
</button>
</form>
@else
<p class="text-secondary mb-0">Permohonan ini tidak lagi boleh diluluskan.</p>
@endif
</div>
<div class="content-panel p-3 p-md-4 mb-4">
<h2 class="h5 mb-3">Tolak Permohonan</h2>
@if (in_array($application->status, ['submitted', 'under_ppm_review', 'approved'], true))
<form method="POST" action="{{ route('ppm.applications.reject', $application->public_uuid) }}" data-confirm>
@csrf
<div class="mb-3">
<label for="reason" class="form-label">Sebab Penolakan</label>
<textarea id="reason" name="reason" class="form-control" rows="3" required>{{ old('reason') }}</textarea>
</div>
<button type="submit" class="btn btn-outline-danger w-100">
<i class="bi bi-x-circle me-1"></i>Tolak
</button>
</form>
@else
<p class="text-secondary mb-0">Permohonan ini tidak lagi boleh ditolak.</p>
@endif
</div>
<div class="content-panel p-3 p-md-4">
<h2 class="h5 mb-3">Sejarah Status</h2>
@forelse ($application->statusHistories->sortByDesc('created_at') as $history)
<div class="border-bottom pb-2 mb-2">
<div class="fw-semibold">{{ $history->from_status ?: '-' }} ke {{ $history->to_status }}</div>
<div class="small text-secondary">{{ $history->created_at?->format('d/m/Y H:i') }} oleh {{ $history->changedBy?->name ?? 'Sistem' }}</div>
@if ($history->note)
<div class="small">{{ $history->note }}</div>
@endif
</div>
@empty
<p class="text-secondary mb-0">Tiada sejarah status.</p>
@endforelse
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
const ktmOptionsByRole = @json($ktmOptionsByRole);
const roleSelect = document.getElementById('approved_position_code');
const saluranSection = document.getElementById('saluran-section');
const saluranSelect = document.getElementById('saluran_mengundi_id');
const ktmSection = document.getElementById('ktm-section');
const ktmSelect = document.getElementById('ktm_assignment_id');
const oldKtmValue = ktmSelect.dataset.oldValue;
function refreshAssignmentInputs() {
const roleCode = roleSelect.value;
const isKtm = roleCode === 'KTM';
const requiresKtm = ['KP', 'KPDP'].includes(roleCode);
saluranSection.classList.toggle('d-none', !isKtm);
saluranSelect.required = isKtm;
saluranSelect.disabled = !isKtm;
ktmSection.classList.toggle('d-none', !requiresKtm);
ktmSelect.required = requiresKtm;
ktmSelect.disabled = !requiresKtm;
ktmSelect.innerHTML = '';
if (!requiresKtm) {
ktmSelect.append(new Option('Pilih KTM', ''));
return;
}
const options = ktmOptionsByRole[roleCode] || [];
if (options.length === 0) {
ktmSelect.append(new Option('Tiada KTM dengan kekosongan', ''));
return;
}
ktmSelect.append(new Option('Pilih KTM', ''));
options.forEach((item) => {
const option = new Option(`${item.label} (${item.remaining} kosong)`, item.id);
if (oldKtmValue && oldKtmValue === String(item.id)) {
option.selected = true;
}
ktmSelect.append(option);
});
}
roleSelect.addEventListener('change', refreshAssignmentInputs);
refreshAssignmentInputs();
</script>
@endpush

View File

@@ -0,0 +1,45 @@
@extends('layouts.app')
@section('title', 'Kehadiran PPM')
@section('content')
<div class="page-heading">
<div class="eyebrow">PPM</div>
<h1 class="h3 mb-1">Rekod Kehadiran</h1>
<p class="text-secondary mb-0">Pilih Pusat Mengundi jagaan untuk merekod kehadiran hari mengundi.</p>
</div>
<div class="row g-3">
@forelse ($pusats as $pusat)
@php($summary = $summaryService->totalsForPusat($pusat))
<div class="col-12 col-lg-6">
<div class="content-panel p-3 p-md-4 h-100">
<div class="d-flex justify-content-between gap-3">
<div>
<h2 class="h5 mb-1">{{ $pusat->code }} - {{ $pusat->name }}</h2>
<div class="text-secondary small">{{ $pusat->election?->name }}</div>
</div>
<span class="badge {{ $pusat->election?->settings?->is_attendance_active ? 'text-bg-success' : 'text-bg-secondary' }}">
{{ $pusat->election?->settings?->is_attendance_active ? 'Aktif' : 'Belum aktif' }}
</span>
</div>
<div class="row g-2 mt-3">
<div class="col-6 col-md-3"><div class="small text-secondary">Staf</div><div class="fw-bold">{{ $summary['total_staff'] }}</div></div>
<div class="col-6 col-md-3"><div class="small text-secondary">Hadir</div><div class="fw-bold text-success">{{ $summary['present'] }}</div></div>
<div class="col-6 col-md-3"><div class="small text-secondary">Tidak hadir</div><div class="fw-bold text-danger">{{ $summary['absent'] }}</div></div>
<div class="col-6 col-md-3"><div class="small text-secondary">Belum rekod</div><div class="fw-bold text-warning">{{ $summary['not_recorded'] }}</div></div>
</div>
<div class="mt-3">
<a class="btn btn-primary" href="{{ route('ppm.attendance.show', $pusat) }}">
<i class="bi bi-clipboard-check me-1"></i>Rekod Kehadiran
</a>
</div>
</div>
</div>
@empty
<div class="col-12">
<div class="content-panel p-4 text-center text-secondary">Tiada Pusat Mengundi ditetapkan kepada akaun PPM ini.</div>
</div>
@endforelse
</div>
@endsection

View File

@@ -0,0 +1,92 @@
@extends('layouts.app')
@section('title', 'Rekod Kehadiran')
@section('content')
<div class="page-heading">
<div class="eyebrow">PPM</div>
<div class="d-flex justify-content-between flex-column flex-lg-row gap-3">
<div>
<h1 class="h3 mb-1">Rekod Kehadiran</h1>
<p class="text-secondary mb-0">{{ $pusat->code }} - {{ $pusat->name }}</p>
</div>
<div><a class="btn btn-outline-secondary" href="{{ route('ppm.attendance.index') }}">Kembali</a></div>
</div>
</div>
<div class="row g-3 mb-4">
@foreach ([
['label' => 'Staf', 'value' => $summary['total_staff'], 'tone' => 'primary'],
['label' => 'Hadir', 'value' => $summary['present'], 'tone' => 'success'],
['label' => 'Tidak hadir', 'value' => $summary['absent'], 'tone' => 'danger'],
['label' => 'Belum rekod', 'value' => $summary['not_recorded'], 'tone' => 'warning'],
] as $card)
<div class="col-6 col-xl-3">
<div class="stat-card h-100">
<div class="text-secondary small">{{ $card['label'] }}</div>
<div class="fs-3 fw-bold mt-2 text-{{ $card['tone'] }}">{{ $card['value'] }}</div>
</div>
</div>
@endforeach
</div>
@if (! $pusat->election?->settings?->is_attendance_active)
<div class="alert alert-warning">
Modul kehadiran belum diaktifkan untuk pilihanraya ini.
</div>
@endif
<form method="POST" action="{{ route('ppm.attendance.store', $pusat) }}">
@csrf
<div class="content-panel p-0 overflow-hidden">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead>
<tr>
<th>Staf</th>
<th>Jawatan</th>
<th>Saluran</th>
<th>Status</th>
<th>Masa Check-in</th>
<th>Catatan</th>
</tr>
</thead>
<tbody>
@forelse ($assignments as $assignment)
@php($attendance = $assignment->attendance)
<tr>
<td>
<div class="fw-semibold">{{ $assignment->application?->name ?: $assignment->user?->name ?: 'Tanpa nama' }}</div>
<div class="small text-secondary">{{ $assignment->application?->ic_number ?: $assignment->user?->email ?: '-' }}</div>
</td>
<td>{{ $assignment->position?->code }}</td>
<td>{{ $assignment->saluranMengundi?->number ?: '-' }}</td>
<td style="min-width: 160px;">
<select class="form-select form-select-sm" name="attendance[{{ $assignment->id }}][status]">
@foreach (['not_recorded' => 'Belum rekod', 'present' => 'Hadir', 'absent' => 'Tidak hadir'] as $value => $label)
<option value="{{ $value }}" @selected(old("attendance.{$assignment->id}.status", $attendance?->status ?? 'not_recorded') === $value)>{{ $label }}</option>
@endforeach
</select>
</td>
<td style="min-width: 190px;">
<input class="form-control form-control-sm" type="datetime-local" name="attendance[{{ $assignment->id }}][check_in_time]" value="{{ old("attendance.{$assignment->id}.check_in_time", $attendance?->check_in_time?->format('Y-m-d\TH:i')) }}">
</td>
<td style="min-width: 220px;">
<input class="form-control form-control-sm" name="attendance[{{ $assignment->id }}][note]" value="{{ old("attendance.{$assignment->id}.note", $attendance?->note) }}">
</td>
</tr>
@empty
<tr><td colspan="6" class="text-center text-secondary py-4">Tiada staf aktif untuk Pusat Mengundi ini.</td></tr>
@endforelse
</tbody>
</table>
</div>
</div>
<div class="d-flex justify-content-end mt-3">
<button class="btn btn-primary" type="submit" @disabled(! $pusat->election?->settings?->is_attendance_active)>
<i class="bi bi-save me-1"></i>Simpan Kehadiran
</button>
</div>
</form>
@endsection