Aplikasi kehadiran KTM ambil peti undi (SPR 2026)
Sistem untuk admin DUN merekod waktu KTM (Ketua Tempat Mengundi) hadir mengambil peti undi. Data KTM dibaca daripada DB sppm (spr2026) melalui connection kedua — hanya penempatan berjawatan KTM yang dipaparkan. - Dashboard: senarai KTM ikut pusat mengundi (asc) & saluran (asc), status BELUM HADIR (merah) / HADIR (hijau) + tarikh & masa - Carian nama/no. KP, tapisan pusat mengundi & status - Tanda hadir / batal dengan kawalan DUN (admin terhad ke DUN sendiri) - Eksport Excel (worksheet Hadir & Belum Hadir) guna SimpleXlsx - Peranan: superadmin (urus pengguna, semua DUN) & admin DUN - Docker: nginx + php-fpm + queue + scheduler + webhook deploy, port 127.0.0.1:8009, TZ Asia/Kuala_Lumpur di semua container - Zon waktu aplikasi: Asia/Kuala_Lumpur (APP_TIMEZONE) Berasaskan scaffold spr2026_taklimat. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
25
public/.htaccess
Normal file
25
public/.htaccess
Normal file
@@ -0,0 +1,25 @@
|
||||
<IfModule mod_rewrite.c>
|
||||
<IfModule mod_negotiation.c>
|
||||
Options -MultiViews -Indexes
|
||||
</IfModule>
|
||||
|
||||
RewriteEngine On
|
||||
|
||||
# Handle Authorization Header
|
||||
RewriteCond %{HTTP:Authorization} .
|
||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||
|
||||
# Handle X-XSRF-Token Header
|
||||
RewriteCond %{HTTP:x-xsrf-token} .
|
||||
RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}]
|
||||
|
||||
# Redirect Trailing Slashes If Not A Folder...
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_URI} (.+)/$
|
||||
RewriteRule ^ %1 [L,R=301]
|
||||
|
||||
# Send Requests To Front Controller...
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^ index.php [L]
|
||||
</IfModule>
|
||||
381
public/css/app.css
Normal file
381
public/css/app.css
Normal file
@@ -0,0 +1,381 @@
|
||||
:root {
|
||||
--biru: #1c3f94;
|
||||
--biru-gelap: #142e6e;
|
||||
--merah: #cc2229;
|
||||
--kelabu: #f4f5f7;
|
||||
--kelabu-border: #d9dde3;
|
||||
--teks: #1f2430;
|
||||
--hijau: #1a7f37;
|
||||
--kuning: #b58900;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
|
||||
background: var(--kelabu);
|
||||
color: var(--teks);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
a { color: var(--biru); }
|
||||
|
||||
.topbar {
|
||||
background: var(--biru);
|
||||
color: #fff;
|
||||
padding: 0.75rem 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.topbar .jenama {
|
||||
font-weight: 700;
|
||||
font-size: 1.05rem;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.topbar .pengguna {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.topbar .pengguna a {
|
||||
color: #fff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 960px;
|
||||
margin: 1.25rem auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.container-kecil { max-width: 480px; }
|
||||
|
||||
.kad {
|
||||
background: #fff;
|
||||
border: 1px solid var(--kelabu-border);
|
||||
border-radius: 10px;
|
||||
padding: 1.25rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.kad h1, .kad h2 { margin-top: 0; }
|
||||
|
||||
h1 { font-size: 1.35rem; }
|
||||
h2 { font-size: 1.15rem; }
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="email"],
|
||||
input[type="password"],
|
||||
input[type="date"],
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 0.65rem 0.75rem;
|
||||
border: 1px solid var(--kelabu-border);
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 0.9rem;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
input:focus, select:focus {
|
||||
outline: 2px solid var(--biru);
|
||||
border-color: var(--biru);
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 0.6rem 1.2rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
background: var(--biru);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn:hover { background: var(--biru-gelap); }
|
||||
.btn-penuh { width: 100%; display: block; }
|
||||
.btn-hijau { background: var(--hijau); }
|
||||
.btn-hijau:hover { background: #14632b; }
|
||||
.btn-merah { background: var(--merah); }
|
||||
.btn-merah:hover { background: #a31b21; }
|
||||
.btn-kecil { padding: 0.35rem 0.75rem; font-size: 0.85rem; }
|
||||
.btn-luar {
|
||||
background: #fff;
|
||||
color: var(--biru);
|
||||
border: 1px solid var(--biru);
|
||||
}
|
||||
.btn-luar:hover { background: #eef1f9; color: var(--biru); }
|
||||
|
||||
.makluman {
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.makluman-info { background: #e8f0fe; border: 1px solid #b3c7f2; color: #173a7a; }
|
||||
.makluman-jaya { background: #e6f4ea; border: 1px solid #a8d5b5; color: #14632b; }
|
||||
.makluman-ralat { background: #fdecea; border: 1px solid #f5b5b1; color: #8c1d18; }
|
||||
.makluman-amaran { background: #fff8e1; border: 1px solid #ecd591; color: #7a5d00; }
|
||||
|
||||
.slip-nombor {
|
||||
font-size: 4.5rem;
|
||||
font-weight: 900;
|
||||
text-align: center;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--biru);
|
||||
margin: 0.5rem 0;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.slip-butiran {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.slip-butiran th,
|
||||
.slip-butiran td {
|
||||
text-align: left;
|
||||
padding: 0.5rem 0.6rem;
|
||||
border-bottom: 1px solid var(--kelabu-border);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.slip-butiran th {
|
||||
width: 38%;
|
||||
color: #5a6172;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
table.jadual {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: #fff;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
table.jadual th,
|
||||
table.jadual td {
|
||||
padding: 0.55rem 0.6rem;
|
||||
border-bottom: 1px solid var(--kelabu-border);
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
table.jadual thead th {
|
||||
background: var(--biru);
|
||||
color: #fff;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
table.jadual tbody tr:nth-child(even) { background: #f8f9fb; }
|
||||
|
||||
.lencana {
|
||||
display: inline-block;
|
||||
padding: 0.15rem 0.6rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.lencana-hijau { background: #e6f4ea; color: var(--hijau); }
|
||||
.lencana-merah { background: #fdecea; color: var(--merah); }
|
||||
.lencana-kelabu { background: #ececec; color: #555; }
|
||||
.lencana-biru { background: #e8f0fe; color: var(--biru); }
|
||||
.lencana-kuning { background: #fbf0d9; color: var(--kuning); }
|
||||
|
||||
.baris-tindakan {
|
||||
display: flex;
|
||||
gap: 0.4rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tab-bar {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tab-bar a {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
background: #fff;
|
||||
border: 1px solid var(--kelabu-border);
|
||||
color: var(--teks);
|
||||
}
|
||||
|
||||
.tab-bar a.aktif {
|
||||
background: var(--biru);
|
||||
border-color: var(--biru);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.qr-wrap {
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.qr-wrap svg {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.stat-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.stat {
|
||||
background: #fff;
|
||||
border: 1px solid var(--kelabu-border);
|
||||
border-radius: 10px;
|
||||
padding: 0.9rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat .nilai {
|
||||
font-size: 1.8rem;
|
||||
font-weight: 800;
|
||||
color: var(--biru);
|
||||
}
|
||||
|
||||
.stat .label { font-size: 0.85rem; color: #5a6172; }
|
||||
|
||||
.teks-tengah { text-align: center; }
|
||||
.teks-kecil { font-size: 0.85rem; color: #5a6172; }
|
||||
.mb-0 { margin-bottom: 0; }
|
||||
.mt-1 { margin-top: 1rem; }
|
||||
|
||||
.ralat-medan { color: var(--merah); font-size: 0.88rem; margin: -0.5rem 0 0.9rem; }
|
||||
|
||||
/* ===== Jadual: kepala (tajuk + carian), badan, kaki (info + paging) ===== */
|
||||
.jadual-kepala {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
padding: 1rem 1.25rem;
|
||||
}
|
||||
|
||||
.tajuk-jadual {
|
||||
margin: 0;
|
||||
font-size: 1.05rem;
|
||||
border-left: 4px solid var(--biru);
|
||||
padding-left: 0.6rem;
|
||||
}
|
||||
.tajuk-jadual.hijau { border-color: var(--hijau); color: var(--hijau); }
|
||||
.tajuk-jadual.merah { border-color: var(--merah); color: var(--merah); }
|
||||
.tajuk-jadual.kuning { border-color: var(--kuning); color: var(--kuning); }
|
||||
|
||||
.jadual-kepala .cari-input {
|
||||
width: auto;
|
||||
flex: 1 1 220px;
|
||||
max-width: 360px;
|
||||
margin: 0;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.jadual-bekas { overflow-x: auto; }
|
||||
|
||||
.jadual-kaki {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
padding: 0.75rem 1.25rem;
|
||||
border-top: 1px solid var(--kelabu-border);
|
||||
}
|
||||
|
||||
.paging { display: flex; gap: 0.3rem; flex-wrap: wrap; }
|
||||
|
||||
.paging-btn {
|
||||
min-width: 2.2rem;
|
||||
padding: 0.35rem 0.6rem;
|
||||
border: 1px solid var(--kelabu-border);
|
||||
background: #fff;
|
||||
color: var(--teks);
|
||||
border-radius: 6px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
.paging-btn:hover:not(:disabled) { background: var(--kelabu); }
|
||||
.paging-btn.aktif { background: var(--biru); border-color: var(--biru); color: #fff; }
|
||||
.paging-btn:disabled { opacity: 0.45; cursor: default; }
|
||||
|
||||
@media print {
|
||||
.topbar, .tiada-cetak { display: none !important; }
|
||||
body { background: #fff; }
|
||||
.kad { border: none; }
|
||||
/* Cetak semua baris tanpa mengira halaman semasa */
|
||||
tr.baris-data { display: table-row !important; }
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.slip-nombor { font-size: 3.5rem; }
|
||||
table.jadual { font-size: 0.85rem; }
|
||||
|
||||
.jadual-kepala, .jadual-kaki { padding: 0.8rem; }
|
||||
.jadual-kepala .cari-input { max-width: none; }
|
||||
|
||||
/* Jadual responsif: setiap baris jadi kad bertindan dgn label */
|
||||
.jadual-responsif thead { display: none; }
|
||||
.jadual-responsif tbody tr {
|
||||
display: block;
|
||||
border: 1px solid var(--kelabu-border);
|
||||
border-radius: 8px;
|
||||
margin: 0.6rem;
|
||||
padding: 0.3rem 0.6rem;
|
||||
background: #fff !important;
|
||||
}
|
||||
.jadual-responsif tbody td {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
gap: 1rem;
|
||||
border: none;
|
||||
padding: 0.35rem 0;
|
||||
text-align: right;
|
||||
}
|
||||
.jadual-responsif tbody td::before {
|
||||
content: attr(data-label);
|
||||
font-weight: 600;
|
||||
color: #5a6172;
|
||||
text-align: left;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.jadual-responsif tbody td:last-child { padding-bottom: 0.1rem; }
|
||||
.jadual-responsif .baris-tindakan { justify-content: flex-end; }
|
||||
.jadual-responsif .baris-tiada td { justify-content: center; text-align: center; }
|
||||
.jadual-responsif .baris-tiada td::before { content: ""; }
|
||||
}
|
||||
0
public/favicon.ico
Normal file
0
public/favicon.ico
Normal file
20
public/index.php
Normal file
20
public/index.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
define('LARAVEL_START', microtime(true));
|
||||
|
||||
// Determine if the application is in maintenance mode...
|
||||
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
|
||||
require $maintenance;
|
||||
}
|
||||
|
||||
// Register the Composer autoloader...
|
||||
require __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
// Bootstrap Laravel and handle the request...
|
||||
/** @var Application $app */
|
||||
$app = require_once __DIR__.'/../bootstrap/app.php';
|
||||
|
||||
$app->handleRequest(Request::capture());
|
||||
2
public/robots.txt
Normal file
2
public/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow:
|
||||
Reference in New Issue
Block a user