diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index 77d471a..3118736 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers; use App\Models\KehadiranKtm; +use App\Models\SaluranBermasalah; use App\Models\Sppm\Dun; use App\Models\Sppm\PetugasAssignment; use App\Models\Sppm\PusatMengundi; @@ -46,6 +47,11 @@ class DashboardController extends Controller ->get() ->keyBy('assignment_id'); + // Saluran yang ditanda bermasalah (untuk highlight kuning) + $bermasalah = SaluranBermasalah::whereIn('saluran_id', $assignments->pluck('saluran_id')->filter()) + ->get() + ->keyBy('saluran_id'); + // Tapis ikut status selepas kehadiran diketahui if (in_array($request->input('status'), ['hadir', 'belum'], true)) { $mahuHadir = $request->input('status') === 'hadir'; @@ -64,6 +70,7 @@ class DashboardController extends Controller return view('dashboard', [ 'assignments' => $assignments, 'kehadiran' => $kehadiran, + 'bermasalah' => $bermasalah, 'senaraiPusat' => $senaraiPusat, 'senaraiDun' => $senaraiDun, 'dunId' => $dunId, diff --git a/app/Http/Controllers/SaluranBermasalahController.php b/app/Http/Controllers/SaluranBermasalahController.php new file mode 100644 index 0000000..6288202 --- /dev/null +++ b/app/Http/Controllers/SaluranBermasalahController.php @@ -0,0 +1,123 @@ +user(); + $dunId = $user->isSuperadmin() + ? ($request->filled('dun_id') ? $request->integer('dun_id') : null) + : (int) $user->dun_id; + + $senaraiDun = $user->isSuperadmin() + ? Dun::where('is_active', true)->orderBy('code')->get() + : collect(); + + $senaraiSaluran = Saluran::whereHas('pusatMengundi', function ($q) use ($dunId) { + if ($dunId) { + $q->where('dun_id', $dunId); + } + }) + ->when($request->filled('pusat_id'), fn ($q) => $q->where('pusat_mengundi_id', $request->integer('pusat_id'))) + ->with('pusatMengundi') + ->get() + ->sortBy([ + fn ($a, $b) => strcmp($a->pusatMengundi?->nama ?? '', $b->pusatMengundi?->nama ?? ''), + fn ($a, $b) => ($a->nombor_saluran ?? 0) <=> ($b->nombor_saluran ?? 0), + ]) + ->values(); + + // KTM bagi setiap saluran (untuk paparan nama & telefon) + $ktm = PetugasAssignment::ktm() + ->whereIn('saluran_id', $senaraiSaluran->pluck('id')) + ->with('permohonan') + ->get() + ->keyBy('saluran_id'); + + $bermasalah = SaluranBermasalah::whereIn('saluran_id', $senaraiSaluran->pluck('id')) + ->with('dilaporOleh') + ->get() + ->keyBy('saluran_id'); + + $senaraiPusat = PusatMengundi::when($dunId, fn ($q) => $q->where('dun_id', $dunId)) + ->orderBy('nama') + ->get(['id', 'nama']); + + return view('saluran.index', [ + 'senaraiSaluran' => $senaraiSaluran, + 'ktm' => $ktm, + 'bermasalah' => $bermasalah, + 'senaraiPusat' => $senaraiPusat, + 'senaraiDun' => $senaraiDun, + 'dunId' => $dunId, + ]); + } + + /** + * Tanda saluran sebagai bermasalah (dengan catatan pilihan). + */ + public function store(Request $request) + { + $data = $request->validate([ + 'saluran_id' => ['required', 'integer'], + 'catatan' => ['nullable', 'string', 'max:500'], + ]); + + $saluran = Saluran::with('pusatMengundi')->findOrFail($data['saluran_id']); + $this->pastikanDunDibenarkan($request, $saluran); + + SaluranBermasalah::updateOrCreate( + ['saluran_id' => $saluran->id], + [ + 'pusat_mengundi_id' => $saluran->pusat_mengundi_id, + 'dun_id' => $saluran->pusatMengundi->dun_id, + 'catatan' => $data['catatan'] ?? null, + 'dilapor_oleh_user_id' => $request->user()->id, + ], + ); + + return back()->with('status', 'Saluran '.$saluran->nombor_saluran.' ('.$saluran->pusatMengundi->nama.') ditanda BERMASALAH.'); + } + + /** + * Buang tanda bermasalah pada saluran. + */ + public function destroy(Request $request, int $saluranId) + { + $saluran = Saluran::with('pusatMengundi')->findOrFail($saluranId); + $this->pastikanDunDibenarkan($request, $saluran); + + SaluranBermasalah::where('saluran_id', $saluran->id)->delete(); + + return back()->with('status', 'Tanda bermasalah pada Saluran '.$saluran->nombor_saluran.' ('.$saluran->pusatMengundi->nama.') telah dibuang.'); + } + + /** + * Admin DUN hanya boleh urus saluran dalam DUN sendiri. + */ + private function pastikanDunDibenarkan(Request $request, Saluran $saluran): void + { + $user = $request->user(); + + if ($user->isSuperadmin()) { + return; + } + + if ((int) $saluran->pusatMengundi?->dun_id !== (int) $user->dun_id) { + abort(403, 'Saluran ini bukan dalam DUN anda.'); + } + } +} diff --git a/app/Models/SaluranBermasalah.php b/app/Models/SaluranBermasalah.php new file mode 100644 index 0000000..6e1bd3d --- /dev/null +++ b/app/Models/SaluranBermasalah.php @@ -0,0 +1,36 @@ +belongsTo(Saluran::class, 'saluran_id'); + } + + public function pusatMengundi(): BelongsTo + { + return $this->belongsTo(PusatMengundi::class, 'pusat_mengundi_id'); + } + + public function dilaporOleh(): BelongsTo + { + return $this->belongsTo(User::class, 'dilapor_oleh_user_id'); + } +} diff --git a/app/Models/Sppm/PetugasPermohonan.php b/app/Models/Sppm/PetugasPermohonan.php index 3dfd246..72427ee 100644 --- a/app/Models/Sppm/PetugasPermohonan.php +++ b/app/Models/Sppm/PetugasPermohonan.php @@ -30,6 +30,36 @@ class PetugasPermohonan extends Model return $this->belongsTo(Dun::class, 'dun_id'); } + /** + * No. telefon dalam digit sahaja (untuk pautan tel:). + */ + public function telefonDigits(): ?string + { + $digit = preg_replace('/\D/', '', (string) $this->no_telefon); + + return $digit !== '' ? $digit : null; + } + + /** + * Pautan WhatsApp (wa.me) — normalkan ke format antarabangsa Malaysia. + */ + public function whatsappUrl(): ?string + { + $digit = $this->telefonDigits(); + + if ($digit === null) { + return null; + } + + if (str_starts_with($digit, '0')) { + $digit = '6'.$digit; + } elseif (! str_starts_with($digit, '60')) { + $digit = '60'.$digit; + } + + return 'https://wa.me/'.$digit; + } + public function assignments(): HasMany { return $this->hasMany(PetugasAssignment::class, 'permohonan_id'); diff --git a/app/Models/Sppm/Saluran.php b/app/Models/Sppm/Saluran.php index 3d0b3ca..4616827 100644 --- a/app/Models/Sppm/Saluran.php +++ b/app/Models/Sppm/Saluran.php @@ -3,10 +3,22 @@ namespace App\Models\Sppm; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; class Saluran extends Model { protected $connection = 'sppm'; protected $table = 'saluran'; protected $guarded = []; + + public function pusatMengundi(): BelongsTo + { + return $this->belongsTo(PusatMengundi::class, 'pusat_mengundi_id'); + } + + public function assignments(): HasMany + { + return $this->hasMany(PetugasAssignment::class, 'saluran_id'); + } } diff --git a/database/migrations/2026_07_11_000003_create_saluran_bermasalah_table.php b/database/migrations/2026_07_11_000003_create_saluran_bermasalah_table.php new file mode 100644 index 0000000..abd8685 --- /dev/null +++ b/database/migrations/2026_07_11_000003_create_saluran_bermasalah_table.php @@ -0,0 +1,31 @@ +id(); + // Rujukan ke pangkalan data sppm (tiada FK sebenar merentas DB) + $table->unsignedBigInteger('saluran_id')->unique(); + $table->unsignedBigInteger('pusat_mengundi_id')->index(); + $table->unsignedBigInteger('dun_id')->index(); + $table->string('catatan', 500)->nullable(); + $table->foreignId('dilapor_oleh_user_id')->constrained('users'); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('saluran_bermasalah'); + } +}; diff --git a/public/css/app.css b/public/css/app.css index fa90a64..45c71c1 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -192,6 +192,12 @@ table.jadual thead th { table.jadual tbody tr:nth-child(even) { background: #f8f9fb; } +/* Saluran ditanda bermasalah: serlahkan kuning */ +table.jadual tbody tr.baris-bermasalah, +table.jadual tbody tr.baris-bermasalah:nth-child(even) { + background: #fff3cd; +} + .lencana { display: inline-block; padding: 0.15rem 0.6rem; @@ -358,6 +364,10 @@ table.jadual tbody tr:nth-child(even) { background: #f8f9fb; } padding: 0.3rem 0.6rem; background: #fff !important; } + .jadual-responsif tbody tr.baris-bermasalah { + background: #fff3cd !important; + border-color: var(--kuning); + } .jadual-responsif tbody td { display: flex; justify-content: space-between; diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index 2f91a2a..26a5522 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -96,14 +96,31 @@ @php $rekod = $kehadiran->get($a->id); $p = $a->permohonan; + $flag = $a->saluran_id ? $bermasalah->get($a->saluran_id) : null; @endphp -
Tanda saluran yang bermasalah supaya diserlahkan kuning dalam dashboard kehadiran.
+| Bil | +Pusat Mengundi | +Saluran | +KTM | +Status | +Catatan | +Tindakan | +
|---|---|---|---|---|---|---|
| {{ $i + 1 }} | +{{ $s->pusatMengundi?->nama ?? '-' }} | +Saluran {{ $s->nombor_saluran }} | ++ {{ $p?->nama_ic ?? '-' }} + @if ($p?->telefonDigits()) + + @endif + | +
+ @if ($flag)
+ BERMASALAH
+ {{ $flag->created_at->format('d/m/Y h:i A') }}
+ @else
+ OK
+ @endif
+ oleh {{ $flag->dilaporOleh?->name }} |
+ {{ $flag?->catatan ?? '-' }} | ++ @if ($flag) + + @else + + @endif + | +
| Tiada saluran dijumpai. | +||||||