Compare commits
3 Commits
dfd22ff2af
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
322687c1d1 | ||
|
|
57c20353c7 | ||
|
|
cd5cfb49a9 |
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\KehadiranKtm;
|
use App\Models\KehadiranKtm;
|
||||||
|
use App\Models\SaluranBermasalah;
|
||||||
use App\Models\Sppm\Dun;
|
use App\Models\Sppm\Dun;
|
||||||
use App\Models\Sppm\PetugasAssignment;
|
use App\Models\Sppm\PetugasAssignment;
|
||||||
use App\Models\Sppm\PusatMengundi;
|
use App\Models\Sppm\PusatMengundi;
|
||||||
@@ -46,6 +47,11 @@ class DashboardController extends Controller
|
|||||||
->get()
|
->get()
|
||||||
->keyBy('assignment_id');
|
->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
|
// Tapis ikut status selepas kehadiran diketahui
|
||||||
if (in_array($request->input('status'), ['hadir', 'belum'], true)) {
|
if (in_array($request->input('status'), ['hadir', 'belum'], true)) {
|
||||||
$mahuHadir = $request->input('status') === 'hadir';
|
$mahuHadir = $request->input('status') === 'hadir';
|
||||||
@@ -64,6 +70,7 @@ class DashboardController extends Controller
|
|||||||
return view('dashboard', [
|
return view('dashboard', [
|
||||||
'assignments' => $assignments,
|
'assignments' => $assignments,
|
||||||
'kehadiran' => $kehadiran,
|
'kehadiran' => $kehadiran,
|
||||||
|
'bermasalah' => $bermasalah,
|
||||||
'senaraiPusat' => $senaraiPusat,
|
'senaraiPusat' => $senaraiPusat,
|
||||||
'senaraiDun' => $senaraiDun,
|
'senaraiDun' => $senaraiDun,
|
||||||
'dunId' => $dunId,
|
'dunId' => $dunId,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\KehadiranKtm;
|
||||||
use App\Models\Sppm\Dun;
|
use App\Models\Sppm\Dun;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@@ -65,4 +66,21 @@ class PenggunaController extends Controller
|
|||||||
|
|
||||||
return redirect()->route('pengguna.index')->with('status', 'Pengguna baharu berjaya didaftarkan.');
|
return redirect()->route('pengguna.index')->with('status', 'Pengguna baharu berjaya didaftarkan.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function destroy(Request $request, User $pengguna)
|
||||||
|
{
|
||||||
|
if ($pengguna->id === $request->user()->id) {
|
||||||
|
return back()->withErrors(['pengguna' => 'Anda tidak boleh memadam akaun anda sendiri.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (KehadiranKtm::where('dicatat_oleh_user_id', $pengguna->id)->exists()) {
|
||||||
|
return back()->withErrors([
|
||||||
|
'pengguna' => "Pengguna {$pengguna->name} tidak boleh dipadam kerana telah mencatat rekod kehadiran.",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pengguna->delete();
|
||||||
|
|
||||||
|
return redirect()->route('pengguna.index')->with('status', "Pengguna {$pengguna->name} telah dipadam.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
123
app/Http/Controllers/SaluranBermasalahController.php
Normal file
123
app/Http/Controllers/SaluranBermasalahController.php
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\SaluranBermasalah;
|
||||||
|
use App\Models\Sppm\Dun;
|
||||||
|
use App\Models\Sppm\PetugasAssignment;
|
||||||
|
use App\Models\Sppm\PusatMengundi;
|
||||||
|
use App\Models\Sppm\Saluran;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class SaluranBermasalahController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Senarai semua saluran dalam DUN berserta status bermasalah.
|
||||||
|
* Admin boleh tanda / buang tanda saluran bermasalah dari sini.
|
||||||
|
*/
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$user = $request->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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
app/Models/SaluranBermasalah.php
Normal file
36
app/Models/SaluranBermasalah.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Models\Sppm\PusatMengundi;
|
||||||
|
use App\Models\Sppm\Saluran;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class SaluranBermasalah extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'saluran_bermasalah';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'saluran_id',
|
||||||
|
'pusat_mengundi_id',
|
||||||
|
'dun_id',
|
||||||
|
'catatan',
|
||||||
|
'dilapor_oleh_user_id',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function saluran(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->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');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,36 @@ class PetugasPermohonan extends Model
|
|||||||
return $this->belongsTo(Dun::class, 'dun_id');
|
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
|
public function assignments(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(PetugasAssignment::class, 'permohonan_id');
|
return $this->hasMany(PetugasAssignment::class, 'permohonan_id');
|
||||||
|
|||||||
@@ -3,10 +3,22 @@
|
|||||||
namespace App\Models\Sppm;
|
namespace App\Models\Sppm;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
class Saluran extends Model
|
class Saluran extends Model
|
||||||
{
|
{
|
||||||
protected $connection = 'sppm';
|
protected $connection = 'sppm';
|
||||||
protected $table = 'saluran';
|
protected $table = 'saluran';
|
||||||
protected $guarded = [];
|
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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Saluran yang ditanda bermasalah oleh admin DUN. Satu rekod = satu
|
||||||
|
* saluran (sppm.saluran) yang sedang bermasalah.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('saluran_bermasalah', function (Blueprint $table) {
|
||||||
|
$table->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');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -192,6 +192,12 @@ table.jadual thead th {
|
|||||||
|
|
||||||
table.jadual tbody tr:nth-child(even) { background: #f8f9fb; }
|
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 {
|
.lencana {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0.15rem 0.6rem;
|
padding: 0.15rem 0.6rem;
|
||||||
@@ -358,6 +364,10 @@ table.jadual tbody tr:nth-child(even) { background: #f8f9fb; }
|
|||||||
padding: 0.3rem 0.6rem;
|
padding: 0.3rem 0.6rem;
|
||||||
background: #fff !important;
|
background: #fff !important;
|
||||||
}
|
}
|
||||||
|
.jadual-responsif tbody tr.baris-bermasalah {
|
||||||
|
background: #fff3cd !important;
|
||||||
|
border-color: var(--kuning);
|
||||||
|
}
|
||||||
.jadual-responsif tbody td {
|
.jadual-responsif tbody td {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
@section('tajuk', 'Dashboard Kehadiran KTM')
|
@section('tajuk', 'Dashboard Kehadiran KTM')
|
||||||
|
|
||||||
@section('kandungan')
|
@section('kandungan')
|
||||||
<div class="stat-grid">
|
<div class="stat-grid" id="stat-grid">
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="nilai">{{ $jumlahSemua }}</div>
|
<div class="nilai">{{ $jumlahSemua }}</div>
|
||||||
<div class="label">Jumlah KTM</div>
|
<div class="label">Jumlah KTM</div>
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="kad">
|
<div class="kad">
|
||||||
<form method="GET" action="{{ route('dashboard') }}">
|
<form method="GET" action="{{ route('dashboard') }}" id="borang-tapis">
|
||||||
<div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(180px, 1fr)); gap:0 0.75rem;">
|
<div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(180px, 1fr)); gap:0 0.75rem;">
|
||||||
@if (auth()->user()->isSuperadmin())
|
@if (auth()->user()->isSuperadmin())
|
||||||
<div>
|
<div>
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="kad" style="padding:0;">
|
<div class="kad" style="padding:0;" id="senarai-ktm">
|
||||||
<div class="jadual-kepala">
|
<div class="jadual-kepala">
|
||||||
<h2 class="tajuk-jadual mb-0">Senarai KTM (Ketua Tempat Mengundi)</h2>
|
<h2 class="tajuk-jadual mb-0">Senarai KTM (Ketua Tempat Mengundi)</h2>
|
||||||
</div>
|
</div>
|
||||||
@@ -96,14 +96,31 @@
|
|||||||
@php
|
@php
|
||||||
$rekod = $kehadiran->get($a->id);
|
$rekod = $kehadiran->get($a->id);
|
||||||
$p = $a->permohonan;
|
$p = $a->permohonan;
|
||||||
|
$flag = $a->saluran_id ? $bermasalah->get($a->saluran_id) : null;
|
||||||
@endphp
|
@endphp
|
||||||
<tr class="baris-data">
|
<tr class="baris-data {{ $flag ? 'baris-bermasalah' : '' }}">
|
||||||
<td data-label="Bil">{{ $i + 1 }}</td>
|
<td data-label="Bil">{{ $i + 1 }}</td>
|
||||||
<td data-label="Pusat Mengundi">{{ $a->pusatMengundi?->nama ?? '-' }}</td>
|
<td data-label="Pusat Mengundi">{{ $a->pusatMengundi?->nama ?? '-' }}</td>
|
||||||
<td data-label="Saluran">{{ $a->saluran ? 'Saluran ' . $a->saluran->nombor_saluran : '-' }}</td>
|
<td data-label="Saluran">
|
||||||
|
{{ $a->saluran ? 'Saluran ' . $a->saluran->nombor_saluran : '-' }}
|
||||||
|
@if ($flag)
|
||||||
|
<div><span class="lencana lencana-kuning" @if($flag->catatan) title="{{ $flag->catatan }}" @endif>BERMASALAH</span></div>
|
||||||
|
@if ($flag->catatan)
|
||||||
|
<div class="teks-kecil">{{ $flag->catatan }}</div>
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
<td data-label="Nama"><strong>{{ $p?->nama_ic ?? '-' }}</strong></td>
|
<td data-label="Nama"><strong>{{ $p?->nama_ic ?? '-' }}</strong></td>
|
||||||
<td data-label="No. KP">{{ $p?->no_kp ?? '-' }}</td>
|
<td data-label="No. KP">{{ $p?->no_kp ?? '-' }}</td>
|
||||||
<td data-label="No. Telefon">{{ $p?->no_telefon ?? '-' }}</td>
|
<td data-label="No. Telefon">
|
||||||
|
{{ $p?->no_telefon ?? '-' }}
|
||||||
|
@if ($p?->telefonDigits())
|
||||||
|
<div class="baris-tindakan" style="margin-top:0.25rem">
|
||||||
|
<a href="tel:{{ $p->telefonDigits() }}" class="btn btn-kecil btn-luar">📞 Call</a>
|
||||||
|
<a href="{{ $p->whatsappUrl() }}" target="_blank" rel="noopener" class="btn btn-kecil btn-hijau">WhatsApp</a>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
<td data-label="Jawatan">{{ $a->jawatan?->nama ?? '-' }}</td>
|
<td data-label="Jawatan">{{ $a->jawatan?->nama ?? '-' }}</td>
|
||||||
<td data-label="Status">
|
<td data-label="Status">
|
||||||
@if ($rekod)
|
@if ($rekod)
|
||||||
@@ -145,4 +162,66 @@
|
|||||||
<span class="teks-kecil">{{ $assignments->count() }} rekod dipaparkan</span>
|
<span class="teks-kecil">{{ $assignments->count() }} rekod dipaparkan</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
var borang = document.getElementById('borang-tapis');
|
||||||
|
var inputCari = document.getElementById('q');
|
||||||
|
var pilihPusat = document.getElementById('pusat_id');
|
||||||
|
var pilihStatus = document.getElementById('status');
|
||||||
|
var timer = null;
|
||||||
|
var fetchSemasa = null;
|
||||||
|
|
||||||
|
// Cari terus semasa menaip (AJAX) — butang Cari kekal sebagai fallback tanpa JS.
|
||||||
|
function cariSekarang() {
|
||||||
|
var params = new URLSearchParams(new FormData(borang));
|
||||||
|
var url = borang.action + (params.toString() ? '?' + params.toString() : '');
|
||||||
|
|
||||||
|
if (fetchSemasa) {
|
||||||
|
fetchSemasa.abort();
|
||||||
|
}
|
||||||
|
fetchSemasa = new AbortController();
|
||||||
|
|
||||||
|
document.getElementById('senarai-ktm').style.opacity = '0.5';
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||||
|
signal: fetchSemasa.signal,
|
||||||
|
})
|
||||||
|
.then(function (r) { return r.text(); })
|
||||||
|
.then(function (html) {
|
||||||
|
var doc = new DOMParser().parseFromString(html, 'text/html');
|
||||||
|
['stat-grid', 'senarai-ktm'].forEach(function (id) {
|
||||||
|
var baru = doc.getElementById(id);
|
||||||
|
if (baru) {
|
||||||
|
document.getElementById(id).replaceWith(baru);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Kekalkan URL supaya refresh/eksport/set-semula konsisten
|
||||||
|
window.history.replaceState(null, '', url);
|
||||||
|
})
|
||||||
|
.catch(function (e) {
|
||||||
|
if (e.name !== 'AbortError') {
|
||||||
|
borang.submit(); // fallback: muat semula penuh
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function cariLewat() {
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = setTimeout(cariSekarang, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
inputCari.addEventListener('input', cariLewat);
|
||||||
|
pilihPusat.addEventListener('change', cariSekarang);
|
||||||
|
pilihStatus.addEventListener('change', cariSekarang);
|
||||||
|
|
||||||
|
// Elak penghantaran borang penuh bila tekan Enter dalam kotak carian
|
||||||
|
borang.addEventListener('submit', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
clearTimeout(timer);
|
||||||
|
cariSekarang();
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
@endphp
|
@endphp
|
||||||
<div class="pengguna">
|
<div class="pengguna">
|
||||||
<a href="{{ route('dashboard') }}">Dashboard</a>
|
<a href="{{ route('dashboard') }}">Dashboard</a>
|
||||||
|
<a href="{{ route('saluran.index') }}">Saluran Bermasalah</a>
|
||||||
@if (auth()->user()->isSuperadmin())
|
@if (auth()->user()->isSuperadmin())
|
||||||
<a href="{{ route('pengguna.index') }}">Pengguna</a>
|
<a href="{{ route('pengguna.index') }}">Pengguna</a>
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@@ -10,6 +10,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if ($errors->any())
|
||||||
|
<div class="makluman makluman-ralat">{{ $errors->first() }}</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
@if ($pengguna->isEmpty())
|
@if ($pengguna->isEmpty())
|
||||||
<div class="kad teks-tengah">
|
<div class="kad teks-tengah">
|
||||||
<p>Tiada pengguna lagi.</p>
|
<p>Tiada pengguna lagi.</p>
|
||||||
@@ -23,6 +27,7 @@
|
|||||||
<th>E-mel</th>
|
<th>E-mel</th>
|
||||||
<th>Peranan</th>
|
<th>Peranan</th>
|
||||||
<th>DUN</th>
|
<th>DUN</th>
|
||||||
|
<th>Tindakan</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -38,6 +43,18 @@
|
|||||||
} }}
|
} }}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ $p->dun ? $p->dun->code . ' — ' . $p->dun->nama : '-' }}</td>
|
<td>{{ $p->dun ? $p->dun->code . ' — ' . $p->dun->nama : '-' }}</td>
|
||||||
|
<td>
|
||||||
|
@if ($p->id === auth()->id())
|
||||||
|
<span class="teks-kecil">(akaun anda)</span>
|
||||||
|
@else
|
||||||
|
<form method="POST" action="{{ route('pengguna.destroy', $p) }}" style="margin:0"
|
||||||
|
onsubmit="return confirm('Padam pengguna {{ $p->name }}? Tindakan ini tidak boleh dikembalikan.');">
|
||||||
|
@csrf
|
||||||
|
@method('DELETE')
|
||||||
|
<button type="submit" class="btn btn-kecil btn-merah">Padam</button>
|
||||||
|
</form>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
@endforeach
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
124
resources/views/saluran/index.blade.php
Normal file
124
resources/views/saluran/index.blade.php
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('tajuk', 'Saluran Bermasalah')
|
||||||
|
|
||||||
|
@section('kandungan')
|
||||||
|
<div class="kad">
|
||||||
|
<div style="display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:0.5rem;">
|
||||||
|
<h1 class="mb-0">Saluran Bermasalah</h1>
|
||||||
|
<span class="lencana lencana-kuning">{{ $bermasalah->count() }} saluran bermasalah</span>
|
||||||
|
</div>
|
||||||
|
<p class="teks-kecil mb-0">Tanda saluran yang bermasalah supaya diserlahkan kuning dalam dashboard kehadiran.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if ($errors->any())
|
||||||
|
<div class="makluman makluman-ralat">{{ $errors->first() }}</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="kad">
|
||||||
|
<form method="GET" action="{{ route('saluran.index') }}">
|
||||||
|
<div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(200px, 1fr)); gap:0 0.75rem;">
|
||||||
|
@if (auth()->user()->isSuperadmin())
|
||||||
|
<div>
|
||||||
|
<label for="dun_id">DUN</label>
|
||||||
|
<select id="dun_id" name="dun_id" onchange="this.form.submit()">
|
||||||
|
<option value="">— Semua DUN —</option>
|
||||||
|
@foreach ($senaraiDun as $dun)
|
||||||
|
<option value="{{ $dun->id }}" @selected($dunId == $dun->id)>
|
||||||
|
{{ $dun->code }} — {{ $dun->nama }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="pusat_id">Pusat Mengundi</label>
|
||||||
|
<select id="pusat_id" name="pusat_id" onchange="this.form.submit()">
|
||||||
|
<option value="">— Semua Pusat —</option>
|
||||||
|
@foreach ($senaraiPusat as $pusat)
|
||||||
|
<option value="{{ $pusat->id }}" @selected(request('pusat_id') == $pusat->id)>
|
||||||
|
{{ $pusat->nama }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="kad" style="padding:0;">
|
||||||
|
<div class="jadual-bekas">
|
||||||
|
<table class="jadual jadual-responsif">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Bil</th>
|
||||||
|
<th>Pusat Mengundi</th>
|
||||||
|
<th>Saluran</th>
|
||||||
|
<th>KTM</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Catatan</th>
|
||||||
|
<th>Tindakan</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@forelse ($senaraiSaluran as $i => $s)
|
||||||
|
@php
|
||||||
|
$flag = $bermasalah->get($s->id);
|
||||||
|
$p = $ktm->get($s->id)?->permohonan;
|
||||||
|
@endphp
|
||||||
|
<tr class="baris-data {{ $flag ? 'baris-bermasalah' : '' }}">
|
||||||
|
<td data-label="Bil">{{ $i + 1 }}</td>
|
||||||
|
<td data-label="Pusat Mengundi">{{ $s->pusatMengundi?->nama ?? '-' }}</td>
|
||||||
|
<td data-label="Saluran"><strong>Saluran {{ $s->nombor_saluran }}</strong></td>
|
||||||
|
<td data-label="KTM">
|
||||||
|
{{ $p?->nama_ic ?? '-' }}
|
||||||
|
@if ($p?->telefonDigits())
|
||||||
|
<div class="baris-tindakan" style="margin-top:0.25rem">
|
||||||
|
<a href="tel:{{ $p->telefonDigits() }}" class="btn btn-kecil btn-luar">📞 Call</a>
|
||||||
|
<a href="{{ $p->whatsappUrl() }}" target="_blank" rel="noopener" class="btn btn-kecil btn-hijau">WhatsApp</a>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td data-label="Status">
|
||||||
|
@if ($flag)
|
||||||
|
<span class="lencana lencana-kuning">BERMASALAH</span>
|
||||||
|
<div class="teks-kecil">{{ $flag->created_at->format('d/m/Y h:i A') }}<br>oleh {{ $flag->dilaporOleh?->name }}</div>
|
||||||
|
@else
|
||||||
|
<span class="lencana lencana-hijau">OK</span>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td data-label="Catatan">{{ $flag?->catatan ?? '-' }}</td>
|
||||||
|
<td data-label="Tindakan">
|
||||||
|
@if ($flag)
|
||||||
|
<form method="POST" action="{{ route('saluran.destroy', $s->id) }}" style="margin:0"
|
||||||
|
onsubmit="return confirm('Buang tanda bermasalah pada Saluran {{ $s->nombor_saluran }} ({{ $s->pusatMengundi?->nama }})?');">
|
||||||
|
@csrf
|
||||||
|
@method('DELETE')
|
||||||
|
<button type="submit" class="btn btn-kecil btn-luar">Buang Tanda</button>
|
||||||
|
</form>
|
||||||
|
@else
|
||||||
|
<form method="POST" action="{{ route('saluran.store') }}" style="margin:0; display:flex; gap:0.4rem; flex-wrap:wrap;">
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="saluran_id" value="{{ $s->id }}">
|
||||||
|
<input type="text" name="catatan" placeholder="Catatan (pilihan)" maxlength="500"
|
||||||
|
style="margin:0; padding:0.35rem 0.6rem; font-size:0.85rem; width:auto; flex:1 1 140px;">
|
||||||
|
<button type="submit" class="btn btn-kecil" style="background: var(--kuning);">Tanda Bermasalah</button>
|
||||||
|
</form>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr class="baris-tiada">
|
||||||
|
<td colspan="7" class="teks-tengah">Tiada saluran dijumpai.</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="jadual-kaki">
|
||||||
|
<span class="teks-kecil">{{ $senaraiSaluran->count() }} saluran dipaparkan</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
@@ -7,6 +7,7 @@ use App\Http\Controllers\KehadiranController;
|
|||||||
use App\Http\Controllers\PasswordResetController;
|
use App\Http\Controllers\PasswordResetController;
|
||||||
use App\Http\Controllers\PenggunaController;
|
use App\Http\Controllers\PenggunaController;
|
||||||
use App\Http\Controllers\ProfilController;
|
use App\Http\Controllers\ProfilController;
|
||||||
|
use App\Http\Controllers\SaluranBermasalahController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::redirect('/', '/login');
|
Route::redirect('/', '/login');
|
||||||
@@ -37,6 +38,11 @@ Route::middleware(['auth'])->group(function () {
|
|||||||
|
|
||||||
// Muat turun senarai kehadiran (Excel)
|
// Muat turun senarai kehadiran (Excel)
|
||||||
Route::get('/eksport-kehadiran', EksportKehadiranController::class)->name('kehadiran.eksport');
|
Route::get('/eksport-kehadiran', EksportKehadiranController::class)->name('kehadiran.eksport');
|
||||||
|
|
||||||
|
// Flag saluran bermasalah
|
||||||
|
Route::get('/saluran-bermasalah', [SaluranBermasalahController::class, 'index'])->name('saluran.index');
|
||||||
|
Route::post('/saluran-bermasalah', [SaluranBermasalahController::class, 'store'])->name('saluran.store');
|
||||||
|
Route::delete('/saluran-bermasalah/{saluran}', [SaluranBermasalahController::class, 'destroy'])->name('saluran.destroy');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::middleware(['role:superadmin'])->group(function () {
|
Route::middleware(['role:superadmin'])->group(function () {
|
||||||
@@ -44,5 +50,6 @@ Route::middleware(['auth'])->group(function () {
|
|||||||
Route::get('/pengguna', [PenggunaController::class, 'index'])->name('pengguna.index');
|
Route::get('/pengguna', [PenggunaController::class, 'index'])->name('pengguna.index');
|
||||||
Route::get('/pengguna/cipta', [PenggunaController::class, 'create'])->name('pengguna.create');
|
Route::get('/pengguna/cipta', [PenggunaController::class, 'create'])->name('pengguna.create');
|
||||||
Route::post('/pengguna', [PenggunaController::class, 'store'])->name('pengguna.store');
|
Route::post('/pengguna', [PenggunaController::class, 'store'])->name('pengguna.store');
|
||||||
|
Route::delete('/pengguna/{pengguna}', [PenggunaController::class, 'destroy'])->name('pengguna.destroy');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user