Merge branch 'feature/add-dividen-display' into 'main'
DONE: add data injection, update ui on login See merge request erahn/voting!15
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class UpdateVoterPelaburanFromJson extends Command
|
||||
{
|
||||
protected $signature = 'voter:update-pelaburan-from-json
|
||||
{--file=database/seeds/inject/list-pelabur.json : Relative path from project root}
|
||||
{--election_id= : Override election_id (default: latest in voter table)}
|
||||
{--dry-run : Show what would change without updating}';
|
||||
|
||||
protected $description = 'Update voter pelaburan (dividen) from list-pelabur.json for latest election only';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$fileOpt = (string) $this->option('file');
|
||||
$filePath = $this->resolvePath($fileOpt);
|
||||
|
||||
if (!is_file($filePath)) {
|
||||
$this->error("❌ JSON file not found: {$filePath}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
$electionId = $this->option('election_id');
|
||||
$electionId = $electionId !== null && $electionId !== '' ? (int) $electionId : (int) DB::table('voter')->max('election_id');
|
||||
|
||||
if ($electionId <= 0) {
|
||||
$this->error("❌ Cannot determine latest election_id (got {$electionId}).");
|
||||
return 1;
|
||||
}
|
||||
|
||||
$json = file_get_contents($filePath);
|
||||
$rows = json_decode($json, true);
|
||||
|
||||
if (!is_array($rows)) {
|
||||
$this->error("❌ Invalid JSON: expected array of rows.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
$map = [];
|
||||
foreach ($rows as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$noAnggota = isset($row['no_anggota']) ? trim((string) $row['no_anggota']) : '';
|
||||
if ($noAnggota === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$pelaburanRaw = $row['pelaburan'] ?? 0;
|
||||
$map[$noAnggota] = $this->cleanNumber($pelaburanRaw);
|
||||
}
|
||||
|
||||
if (count($map) === 0) {
|
||||
$this->error("❌ No usable rows found in JSON (missing no_anggota?).");
|
||||
return 1;
|
||||
}
|
||||
|
||||
$dryRun = (bool) $this->option('dry-run');
|
||||
|
||||
$this->info("Target election_id: {$electionId}");
|
||||
$this->info("JSON members loaded: " . count($map));
|
||||
$this->info($dryRun ? "Mode: DRY RUN (no DB updates)" : "Mode: UPDATE");
|
||||
|
||||
$totalInElection = (int) DB::table('voter')->where('election_id', $electionId)->count();
|
||||
$this->info("Voters in election: {$totalInElection}");
|
||||
|
||||
$updated = 0;
|
||||
$matched = 0;
|
||||
$missingInJson = 0;
|
||||
$unchanged = 0;
|
||||
|
||||
$bar = $this->output->createProgressBar(max(1, $totalInElection));
|
||||
$bar->start();
|
||||
|
||||
DB::table('voter')
|
||||
->select(['id', 'no_anggota', 'pelaburan'])
|
||||
->where('election_id', $electionId)
|
||||
->orderBy('id')
|
||||
->chunkById(500, function ($chunk) use ($map, $dryRun, &$updated, &$matched, &$missingInJson, &$unchanged, $bar) {
|
||||
foreach ($chunk as $voter) {
|
||||
$noAnggota = $voter->no_anggota !== null ? trim((string) $voter->no_anggota) : '';
|
||||
|
||||
if ($noAnggota === '' || !array_key_exists($noAnggota, $map)) {
|
||||
$missingInJson++;
|
||||
$bar->advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
$matched++;
|
||||
|
||||
$newPelaburan = $map[$noAnggota];
|
||||
$oldPelaburan = $voter->pelaburan !== null ? (float) $voter->pelaburan : 0.0;
|
||||
|
||||
if (abs($oldPelaburan - $newPelaburan) < 0.00001) {
|
||||
$unchanged++;
|
||||
$bar->advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$dryRun) {
|
||||
DB::table('voter')
|
||||
->where('id', $voter->id)
|
||||
->update([
|
||||
'pelaburan' => $newPelaburan,
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
$updated++;
|
||||
$bar->advance();
|
||||
}
|
||||
}, 'id');
|
||||
|
||||
$bar->finish();
|
||||
$this->line('');
|
||||
|
||||
$this->info("✅ Matched in JSON: {$matched}");
|
||||
$this->info("✅ Updated: {$updated}" . ($dryRun ? " (would update)" : ""));
|
||||
$this->info("✅ Unchanged: {$unchanged}");
|
||||
$this->info("⚠️ Missing in JSON: {$missingInJson}");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function resolvePath(string $path): string
|
||||
{
|
||||
// absolute path
|
||||
if (strpos($path, DIRECTORY_SEPARATOR) === 0) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
return base_path($path);
|
||||
}
|
||||
|
||||
private function cleanNumber($value): float
|
||||
{
|
||||
if ($value === null) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$value = (string) $value;
|
||||
$value = str_replace(['RM', ',', ' '], '', $value);
|
||||
|
||||
if (preg_match('/^\((.*)\)$/', $value, $matches)) {
|
||||
$value = $matches[1];
|
||||
}
|
||||
|
||||
$value = preg_replace('/[^0-9\.\-]/', '', $value);
|
||||
|
||||
return is_numeric($value) ? (float) $value : 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ class Kernel extends ConsoleKernel
|
||||
protected $commands = [
|
||||
//
|
||||
Commands\ImportCSV::class,
|
||||
Commands\UpdateVoterPelaburanFromJson::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,947 @@
|
||||
[
|
||||
{
|
||||
"no_anggota": "7",
|
||||
"name": "Wan Zulkifly Bin Wan Mohamed",
|
||||
"pelaburan": "32,910.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "12",
|
||||
"name": "Azmadi Bin Omar",
|
||||
"pelaburan": "56,474.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "13",
|
||||
"name": "Rusmawati Binti Mamat",
|
||||
"pelaburan": "59,945.90"
|
||||
},
|
||||
{
|
||||
"no_anggota": "25",
|
||||
"name": "Rakiah Binti Yaacob",
|
||||
"pelaburan": "242.40"
|
||||
},
|
||||
{
|
||||
"no_anggota": "44",
|
||||
"name": "Nor Aisah Binti Rameli",
|
||||
"pelaburan": "18,594.80"
|
||||
},
|
||||
{
|
||||
"no_anggota": "47",
|
||||
"name": "Faridah Binti Mamat",
|
||||
"pelaburan": "8,445.10"
|
||||
},
|
||||
{
|
||||
"no_anggota": "48",
|
||||
"name": "Razaki Bin Che Omar",
|
||||
"pelaburan": "18,000.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "52",
|
||||
"name": "Tuan Razak Bin Tuan Muda",
|
||||
"pelaburan": "13,100.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "56",
|
||||
"name": "Aminuddin Bin Md Nor",
|
||||
"pelaburan": "32,543.10"
|
||||
},
|
||||
{
|
||||
"no_anggota": "97",
|
||||
"name": "ZAILILAH BINTI HASSAN",
|
||||
"pelaburan": "3,139.80"
|
||||
},
|
||||
{
|
||||
"no_anggota": "112",
|
||||
"name": "Narwati Binti Lupin",
|
||||
"pelaburan": "7,560.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "133",
|
||||
"name": "Mohd. Ezanee Bin Mohd. Noor",
|
||||
"pelaburan": "27,000.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "135",
|
||||
"name": "Mastura Binti Muhammad",
|
||||
"pelaburan": "960.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "151",
|
||||
"name": "Hassan Shukri Bin Hussein",
|
||||
"pelaburan": "69,288.90"
|
||||
},
|
||||
{
|
||||
"no_anggota": "170",
|
||||
"name": "Azly Bin Othman",
|
||||
"pelaburan": "219,955.90"
|
||||
},
|
||||
{
|
||||
"no_anggota": "186",
|
||||
"name": "Razaki Bin Mamat",
|
||||
"pelaburan": "1,176.90"
|
||||
},
|
||||
{
|
||||
"no_anggota": "188",
|
||||
"name": "Roshafiza Bin Mamat",
|
||||
"pelaburan": "6,043.50"
|
||||
},
|
||||
{
|
||||
"no_anggota": "199",
|
||||
"name": "Fatimah Binti Mat Zin",
|
||||
"pelaburan": "1,443.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "200",
|
||||
"name": "Mohd Saharuddin Bin Ab Rahman",
|
||||
"pelaburan": "8,936.70"
|
||||
},
|
||||
{
|
||||
"no_anggota": "221",
|
||||
"name": "Mohd Bin Yaacob",
|
||||
"pelaburan": "739.80"
|
||||
},
|
||||
{
|
||||
"no_anggota": "224",
|
||||
"name": "Mohd Sabri Bin Abdullah",
|
||||
"pelaburan": "28,356.50"
|
||||
},
|
||||
{
|
||||
"no_anggota": "235",
|
||||
"name": "Anis Binti Ismail",
|
||||
"pelaburan": "29,335.90"
|
||||
},
|
||||
{
|
||||
"no_anggota": "240",
|
||||
"name": "Hafeizah Binti Ab Kadir",
|
||||
"pelaburan": "2,407.50"
|
||||
},
|
||||
{
|
||||
"no_anggota": "243",
|
||||
"name": "Mohd Faizal Bin Mohamad",
|
||||
"pelaburan": "3,763.20"
|
||||
},
|
||||
{
|
||||
"no_anggota": "252",
|
||||
"name": "Azirin Bin Riffin",
|
||||
"pelaburan": "205.10"
|
||||
},
|
||||
{
|
||||
"no_anggota": "255",
|
||||
"name": "Kamrul Hisan Bin Mohd Kamson",
|
||||
"pelaburan": "39,572.50"
|
||||
},
|
||||
{
|
||||
"no_anggota": "260",
|
||||
"name": "Asmah Laily Binti Hussein",
|
||||
"pelaburan": "62,615.70"
|
||||
},
|
||||
{
|
||||
"no_anggota": "264",
|
||||
"name": "W.M. Azhari Bin Wan Abdullah",
|
||||
"pelaburan": "55,708.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "278",
|
||||
"name": "Norly Binti Deraman @ Ibrahim",
|
||||
"pelaburan": "35,136.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "284",
|
||||
"name": "Zailani Bin Yaacob",
|
||||
"pelaburan": "6,021.60"
|
||||
},
|
||||
{
|
||||
"no_anggota": "288",
|
||||
"name": "Amiriza Bin Hamzah",
|
||||
"pelaburan": "21,697.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "290",
|
||||
"name": "A'sri Bin Che Ali",
|
||||
"pelaburan": "46,554.50"
|
||||
},
|
||||
{
|
||||
"no_anggota": "292",
|
||||
"name": "Faridah Binti Che Ali",
|
||||
"pelaburan": "360.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "295",
|
||||
"name": "Abdul Rahman Bin Ali",
|
||||
"pelaburan": "4,243.30"
|
||||
},
|
||||
{
|
||||
"no_anggota": "320",
|
||||
"name": "Nik Abdullah Bin Nik Muhammad",
|
||||
"pelaburan": "12,000.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "321",
|
||||
"name": "Ramilan Bin Abdul Rahman",
|
||||
"pelaburan": "140,412.20"
|
||||
},
|
||||
{
|
||||
"no_anggota": "329",
|
||||
"name": "Roselina Binti Mohamad Mahiddin",
|
||||
"pelaburan": "20,000.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "345",
|
||||
"name": "Azizah Binti Kamarul Zaman",
|
||||
"pelaburan": "182,985.30"
|
||||
},
|
||||
{
|
||||
"no_anggota": "346",
|
||||
"name": "Norazani Binti Husain",
|
||||
"pelaburan": "12,036.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "354",
|
||||
"name": "Mohd Nor Bin Osman",
|
||||
"pelaburan": "60,240.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "358",
|
||||
"name": "Mohd Khalid Bin Abdul Rahman",
|
||||
"pelaburan": "6,390.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "365",
|
||||
"name": "Nor Azlimah Binti Ramli",
|
||||
"pelaburan": "17,334.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "367",
|
||||
"name": "Zaid Bin Zakaria",
|
||||
"pelaburan": "12,360.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "368",
|
||||
"name": "Mohd Azrul Faizal Bin Abu Bakar",
|
||||
"pelaburan": "39.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "376",
|
||||
"name": "Azimat Bin Mamat",
|
||||
"pelaburan": "32,345.40"
|
||||
},
|
||||
{
|
||||
"no_anggota": "386",
|
||||
"name": "Mohd Nor Ikhwan Bin Shahimi",
|
||||
"pelaburan": "3,618.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "391",
|
||||
"name": "Ahmad Syahmi Bin Ahmad Izani",
|
||||
"pelaburan": "15,000.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "392",
|
||||
"name": "Nik Nabihah Binti Nik Yusoff",
|
||||
"pelaburan": "206.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "396",
|
||||
"name": "Mohd Zani Bin Md Zain",
|
||||
"pelaburan": "277.60"
|
||||
},
|
||||
{
|
||||
"no_anggota": "397",
|
||||
"name": "Hafizul Rahman Bin Mohd Fauzi",
|
||||
"pelaburan": "8,494.60"
|
||||
},
|
||||
{
|
||||
"no_anggota": "403",
|
||||
"name": "Muhamad Shaharil Bin Sidik",
|
||||
"pelaburan": "4,308.50"
|
||||
},
|
||||
{
|
||||
"no_anggota": "405",
|
||||
"name": "Nurhaida Binti Abd Samad",
|
||||
"pelaburan": "263.60"
|
||||
},
|
||||
{
|
||||
"no_anggota": "416",
|
||||
"name": "Muhamad Farkiami Bin Ab Rahim",
|
||||
"pelaburan": "207.80"
|
||||
},
|
||||
{
|
||||
"no_anggota": "421",
|
||||
"name": "Mohd Faizul Azaha Bin Umanah",
|
||||
"pelaburan": "90.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "431",
|
||||
"name": "Azurawaty Binti Ahmad",
|
||||
"pelaburan": "6.80"
|
||||
},
|
||||
{
|
||||
"no_anggota": "440",
|
||||
"name": "Nur Rashihah Binti Salleh",
|
||||
"pelaburan": "3,360.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "448",
|
||||
"name": "Nik Fadhlinee Binti Nik Marwan",
|
||||
"pelaburan": "4,851.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "450",
|
||||
"name": "Roslina Binti Mohd Ramli",
|
||||
"pelaburan": "2,454.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "452",
|
||||
"name": "Wan Ramlah Binti Wan Deraman",
|
||||
"pelaburan": "1,206.60"
|
||||
},
|
||||
{
|
||||
"no_anggota": "454",
|
||||
"name": "Mohd Roslan Bin Mohd Ali",
|
||||
"pelaburan": "20.10"
|
||||
},
|
||||
{
|
||||
"no_anggota": "458",
|
||||
"name": "Zanjabila Binti Mohd Saleh",
|
||||
"pelaburan": "188.80"
|
||||
},
|
||||
{
|
||||
"no_anggota": "461",
|
||||
"name": "MARIYANI BINTI MUDA",
|
||||
"pelaburan": "12,317.20"
|
||||
},
|
||||
{
|
||||
"no_anggota": "462",
|
||||
"name": "Abdullah Hakim Bin Ab. Alim",
|
||||
"pelaburan": "1,349.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "463",
|
||||
"name": "Nor Fishah Binti Mustaffa",
|
||||
"pelaburan": "1,200.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "469",
|
||||
"name": "Shahilla Binti Che Razali",
|
||||
"pelaburan": "6,302.60"
|
||||
},
|
||||
{
|
||||
"no_anggota": "470",
|
||||
"name": "Nur Idayu Binti Mat Nasir",
|
||||
"pelaburan": "4,719.90"
|
||||
},
|
||||
{
|
||||
"no_anggota": "475",
|
||||
"name": "Suziana Binti Yusof",
|
||||
"pelaburan": "2,375.10"
|
||||
},
|
||||
{
|
||||
"no_anggota": "476",
|
||||
"name": "Rozalmi Bin Razali",
|
||||
"pelaburan": "71.90"
|
||||
},
|
||||
{
|
||||
"no_anggota": "481",
|
||||
"name": "Mohamed Azmi Bin Mahmood",
|
||||
"pelaburan": "1,219.30"
|
||||
},
|
||||
{
|
||||
"no_anggota": "482",
|
||||
"name": "Abdull Razak Bin Omar",
|
||||
"pelaburan": "8,436.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "483",
|
||||
"name": "Mohd Ekhwan Bin Zakaria",
|
||||
"pelaburan": "77,990.30"
|
||||
},
|
||||
{
|
||||
"no_anggota": "493",
|
||||
"name": "Farah Awanis Binti Edrus",
|
||||
"pelaburan": "270.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "495",
|
||||
"name": "Asma Binti Ahmad",
|
||||
"pelaburan": "1,207.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "497",
|
||||
"name": "Sadeed Muhdhar Bin Mohd Zain",
|
||||
"pelaburan": "322.40"
|
||||
},
|
||||
{
|
||||
"no_anggota": "499",
|
||||
"name": "Nurul Aini Binti Harun",
|
||||
"pelaburan": "600.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "503",
|
||||
"name": "Wan Mohd Razis Bin Wan Suhaimi",
|
||||
"pelaburan": "14,300.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "504",
|
||||
"name": "Wan Ruslaile Binti Wan Mohd Zain",
|
||||
"pelaburan": "24,192.20"
|
||||
},
|
||||
{
|
||||
"no_anggota": "505",
|
||||
"name": "Suhairah Binti Suliman",
|
||||
"pelaburan": "3,625.60"
|
||||
},
|
||||
{
|
||||
"no_anggota": "506",
|
||||
"name": "Nik Lokman Bin Nik Man",
|
||||
"pelaburan": "1,582.50"
|
||||
},
|
||||
{
|
||||
"no_anggota": "507",
|
||||
"name": "Mohd Shahrul Ridzuan Bin Abdullah",
|
||||
"pelaburan": "16,574.90"
|
||||
},
|
||||
{
|
||||
"no_anggota": "510",
|
||||
"name": "Wan Mohd Ridhuan Bin Wan Mat Ludin",
|
||||
"pelaburan": "410.90"
|
||||
},
|
||||
{
|
||||
"no_anggota": "511",
|
||||
"name": "Mohamad Faiz Bin Mohamed Asri",
|
||||
"pelaburan": "3,048.50"
|
||||
},
|
||||
{
|
||||
"no_anggota": "515",
|
||||
"name": "Othman Bin Daud",
|
||||
"pelaburan": "3,756.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "517",
|
||||
"name": "Mohd Farid Bin Ibrahim",
|
||||
"pelaburan": "18,506.60"
|
||||
},
|
||||
{
|
||||
"no_anggota": "520",
|
||||
"name": "Siti Zaleha Binti Harun",
|
||||
"pelaburan": "459.90"
|
||||
},
|
||||
{
|
||||
"no_anggota": "522",
|
||||
"name": "Mohd Faizul Bin Hussain",
|
||||
"pelaburan": "1.20"
|
||||
},
|
||||
{
|
||||
"no_anggota": "525",
|
||||
"name": "Fatin Fauzana Binti Mohd Ramli",
|
||||
"pelaburan": "1,990.10"
|
||||
},
|
||||
{
|
||||
"no_anggota": "527",
|
||||
"name": "Nor Aziah Binti Ahmad",
|
||||
"pelaburan": "4,724.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "546",
|
||||
"name": "Nur Adnin Binti Mohamed",
|
||||
"pelaburan": "2,040.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "549",
|
||||
"name": "Siti Raihan Binti Mat San",
|
||||
"pelaburan": "5,035.90"
|
||||
},
|
||||
{
|
||||
"no_anggota": "555",
|
||||
"name": "Mohd Redzuan Bin Shaarani",
|
||||
"pelaburan": "600.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "566",
|
||||
"name": "Nur Farahiah Binti Mat Jusoh",
|
||||
"pelaburan": "1,200.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "573",
|
||||
"name": "Che Rusli Bin Muhammad",
|
||||
"pelaburan": "68.50"
|
||||
},
|
||||
{
|
||||
"no_anggota": "581",
|
||||
"name": "Mohd Azizi Bin Ab Rashid",
|
||||
"pelaburan": "40,450.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "583",
|
||||
"name": "Mohd Hafiz Bin Mohammad",
|
||||
"pelaburan": "6,768.30"
|
||||
},
|
||||
{
|
||||
"no_anggota": "586",
|
||||
"name": "Muzani Bin Ibrahim",
|
||||
"pelaburan": "3,050.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "588",
|
||||
"name": "Wan Azmeer Bin Wan Mahmood",
|
||||
"pelaburan": "15,592.70"
|
||||
},
|
||||
{
|
||||
"no_anggota": "590",
|
||||
"name": "KHAIRUL ANUAR BIN YAACOB",
|
||||
"pelaburan": "135.60"
|
||||
},
|
||||
{
|
||||
"no_anggota": "591",
|
||||
"name": "Azhan Bin Mat",
|
||||
"pelaburan": "15,780.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "593",
|
||||
"name": "Nasrul Hafez Bin Mat Nasir",
|
||||
"pelaburan": "10,992.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "598",
|
||||
"name": "Nur Izzati Binti Zulkifli",
|
||||
"pelaburan": "1,568.50"
|
||||
},
|
||||
{
|
||||
"no_anggota": "599",
|
||||
"name": "Muhamad Hafiz Bin Zakaria",
|
||||
"pelaburan": "240.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "601",
|
||||
"name": "Mohamad Ridzwan Bin Mohamad",
|
||||
"pelaburan": "3,000.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "602",
|
||||
"name": "Mohd Firdaus Bin Mahamood",
|
||||
"pelaburan": "4,196.20"
|
||||
},
|
||||
{
|
||||
"no_anggota": "607",
|
||||
"name": "Ismail Bin Zakaria",
|
||||
"pelaburan": "0.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "608",
|
||||
"name": "Mohd Shahmin Bin Sa'ari",
|
||||
"pelaburan": "7,800.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "609",
|
||||
"name": "Wan Mohammad Armani Bin Wan Sharifudin",
|
||||
"pelaburan": "2,864.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "612",
|
||||
"name": "Aidamarni Binti Salleh",
|
||||
"pelaburan": "2,642.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "616",
|
||||
"name": "Nurhusin Bin Apan",
|
||||
"pelaburan": "70.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "619",
|
||||
"name": "MUHAMMAD NAFIS BIN ZAINUDIN",
|
||||
"pelaburan": "24,613.40"
|
||||
},
|
||||
{
|
||||
"no_anggota": "621",
|
||||
"name": "Mohd Hafizul Fikri Bin Sidi Mohamed Noor",
|
||||
"pelaburan": "1,356.20"
|
||||
},
|
||||
{
|
||||
"no_anggota": "622",
|
||||
"name": "Ahmad Zulkhairi Bin Zulkifli",
|
||||
"pelaburan": "0.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "625",
|
||||
"name": "Mohd Firdaus Bin Mohammad",
|
||||
"pelaburan": "3,745.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "627",
|
||||
"name": "Mohd Ayub Bin Suhaimi",
|
||||
"pelaburan": "22,846.80"
|
||||
},
|
||||
{
|
||||
"no_anggota": "638",
|
||||
"name": "NURUL NISHA BT MUHAMMAD",
|
||||
"pelaburan": "1,356.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "639",
|
||||
"name": "RIDZAN NURLINA BINTI MOHAMED NASIR",
|
||||
"pelaburan": "6,071.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "644",
|
||||
"name": "ABDUL HAKIM AHMAD BIN AB'NASHIR",
|
||||
"pelaburan": "34,388.80"
|
||||
},
|
||||
{
|
||||
"no_anggota": "646",
|
||||
"name": "MOHD SYAHMIE SYAWAL BIN MOHD BAKRI",
|
||||
"pelaburan": "1,517.90"
|
||||
},
|
||||
{
|
||||
"no_anggota": "653",
|
||||
"name": "MUHAMMAD 'IZZUDDIN BIN MOKHTAR",
|
||||
"pelaburan": "2,400.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "654",
|
||||
"name": "MOHAMAD SHAKIR BIN ABDUL MANAN",
|
||||
"pelaburan": "7.70"
|
||||
},
|
||||
{
|
||||
"no_anggota": "655",
|
||||
"name": "NUR AIN FARINA PANAI BINTI ABDULLAH",
|
||||
"pelaburan": "161.20"
|
||||
},
|
||||
{
|
||||
"no_anggota": "659",
|
||||
"name": "RAJA NOR AIDAH BINTI RAJA AHMAD",
|
||||
"pelaburan": "9,128.50"
|
||||
},
|
||||
{
|
||||
"no_anggota": "660",
|
||||
"name": "NOOR SUHAILA BINTI IBRAHIM",
|
||||
"pelaburan": "930.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "661",
|
||||
"name": "MOHAMMAD FARIZ BIN MUSTAPHA",
|
||||
"pelaburan": "210.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "665",
|
||||
"name": "ASMARIHAN BINTI AB GHANI",
|
||||
"pelaburan": "659.50"
|
||||
},
|
||||
{
|
||||
"no_anggota": "667",
|
||||
"name": "HADAFI BIN MOHD ZUKIPELI",
|
||||
"pelaburan": "600.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "672",
|
||||
"name": "MOHD HAYYUL ILTIZAM BIN OTHMAN",
|
||||
"pelaburan": "600.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "674",
|
||||
"name": "RIDHUAN BIN ABDULLAH",
|
||||
"pelaburan": "13,001.40"
|
||||
},
|
||||
{
|
||||
"no_anggota": "687",
|
||||
"name": "MUHAMAD HAFIZ SHAR'E BIN HANAFI",
|
||||
"pelaburan": "29.80"
|
||||
},
|
||||
{
|
||||
"no_anggota": "691",
|
||||
"name": "ROHAYA BINTI MUHAMAD",
|
||||
"pelaburan": "11,008.80"
|
||||
},
|
||||
{
|
||||
"no_anggota": "693",
|
||||
"name": "MUHAMMAD IZWAN BIN IBRAHIM",
|
||||
"pelaburan": "143,140.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "694",
|
||||
"name": "NURWIDAD BINTI AZLY",
|
||||
"pelaburan": "25,370.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "697",
|
||||
"name": "MAHARI BUKHARI BIN MAT",
|
||||
"pelaburan": "9,319.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "700",
|
||||
"name": "MOHAMAD AZUHAIDIE BIN AHMAD",
|
||||
"pelaburan": "2,770.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "702",
|
||||
"name": "MOHD ZULKIFLIE BIN MOHD ZAIN",
|
||||
"pelaburan": "45.30"
|
||||
},
|
||||
{
|
||||
"no_anggota": "703",
|
||||
"name": "IRMA SYUHADA BT IBRAHIM",
|
||||
"pelaburan": "360.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "704",
|
||||
"name": "SYUKRI BIN ARIFFIN",
|
||||
"pelaburan": "355.60"
|
||||
},
|
||||
{
|
||||
"no_anggota": "710",
|
||||
"name": "MOHD FAHMI BIN MAT LUDIN",
|
||||
"pelaburan": "0.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "712",
|
||||
"name": "MUHAMMAD SHAKIL BIN HAMIDIN",
|
||||
"pelaburan": "240.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "721",
|
||||
"name": "WAN MANITA BT HAMAD@ WAN MOHD",
|
||||
"pelaburan": "6,150.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "724",
|
||||
"name": "LUQMAN HAKIM BIN MOHD YAHAYA",
|
||||
"pelaburan": "1,680.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "725",
|
||||
"name": "MUHAMMAD ASYWAM BIN A'SRI",
|
||||
"pelaburan": "40,704.60"
|
||||
},
|
||||
{
|
||||
"no_anggota": "726",
|
||||
"name": "NIK NOR SHAHIRAH BT NIK YUSOFF",
|
||||
"pelaburan": "20,268.60"
|
||||
},
|
||||
{
|
||||
"no_anggota": "730",
|
||||
"name": "MOHD HISYAM KHAIREE BIN MOHD YUSNI",
|
||||
"pelaburan": "3,403.60"
|
||||
},
|
||||
{
|
||||
"no_anggota": "739",
|
||||
"name": "WAN NURUL AMIRA BINTI WAN HARON",
|
||||
"pelaburan": "240.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "742",
|
||||
"name": "NUR LIANA SYAFIKAH BINTI SALAMUN",
|
||||
"pelaburan": "8,500.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "756",
|
||||
"name": "MUHAMMAD KHAIRUL AMIRIN BIN MOHD ROSLI",
|
||||
"pelaburan": "5,006.90"
|
||||
},
|
||||
{
|
||||
"no_anggota": "757",
|
||||
"name": "NUR FAKHIRA BINTI AHMAD KAMIL",
|
||||
"pelaburan": "3,390.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "758",
|
||||
"name": "MUHAMMAD HAZIM BIN MOHD ASRI",
|
||||
"pelaburan": "54,604.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "765",
|
||||
"name": "NORIZAN BINTI AWANG",
|
||||
"pelaburan": "255.60"
|
||||
},
|
||||
{
|
||||
"no_anggota": "770",
|
||||
"name": "WAN NOR HISHAM BIN WAN JAAFAR",
|
||||
"pelaburan": "8,537.50"
|
||||
},
|
||||
{
|
||||
"no_anggota": "773",
|
||||
"name": "MUHAMMAD HAMIRUL BIN HALID",
|
||||
"pelaburan": "124.60"
|
||||
},
|
||||
{
|
||||
"no_anggota": "783",
|
||||
"name": "WAN AHMAD KAMAL BIN MOHD NOOR",
|
||||
"pelaburan": "294.70"
|
||||
},
|
||||
{
|
||||
"no_anggota": "785",
|
||||
"name": "ABDUL MUHAIMIN BIN AHMAD",
|
||||
"pelaburan": "13.40"
|
||||
},
|
||||
{
|
||||
"no_anggota": "788",
|
||||
"name": "NUR ALISSYA BT MOHD HAZMAN",
|
||||
"pelaburan": "36,480.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "792",
|
||||
"name": "NIK MOHD HAZIM AQMAL BIN NIK AZIM AMIRAH",
|
||||
"pelaburan": "7,912.60"
|
||||
},
|
||||
{
|
||||
"no_anggota": "794",
|
||||
"name": "DATO' DR WAN ZAWAWI BIN WAN ISMAIL",
|
||||
"pelaburan": "3,800.50"
|
||||
},
|
||||
{
|
||||
"no_anggota": "799",
|
||||
"name": "FATIN NADIA BINTI ZAIDI",
|
||||
"pelaburan": "240.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "803",
|
||||
"name": "ASMADI BIN ABDUL WAHED",
|
||||
"pelaburan": "18,170.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "804",
|
||||
"name": "AHMAD TARZIDIE BIN AB. MANAF",
|
||||
"pelaburan": "960.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "806",
|
||||
"name": "NURUL ALIA BINTI MOHAMAD KAMAL",
|
||||
"pelaburan": "2,742.60"
|
||||
},
|
||||
{
|
||||
"no_anggota": "807",
|
||||
"name": "JUNAIDI BIN BAKRI",
|
||||
"pelaburan": "3,001.70"
|
||||
},
|
||||
{
|
||||
"no_anggota": "809",
|
||||
"name": "ASHIKIN BINTI ABDULLAH",
|
||||
"pelaburan": "2,520.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "815",
|
||||
"name": "MAHNUN BINTI HUSSIN",
|
||||
"pelaburan": "7,140.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "817",
|
||||
"name": "NIK MOHD NASIRUDDIN BIN NIK COB",
|
||||
"pelaburan": "720.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "819",
|
||||
"name": "AB AZIZ BIN YUNUS",
|
||||
"pelaburan": "252,768.30"
|
||||
},
|
||||
{
|
||||
"no_anggota": "825",
|
||||
"name": "ABD MUHAIMIN BIN MOHD TURIDI",
|
||||
"pelaburan": "645.60"
|
||||
},
|
||||
{
|
||||
"no_anggota": "828",
|
||||
"name": "MOHD FADZLI BIN MOHD NASRAH",
|
||||
"pelaburan": "18,602.60"
|
||||
},
|
||||
{
|
||||
"no_anggota": "838",
|
||||
"name": "MOHD FARIS BIN RUSLEE",
|
||||
"pelaburan": "24,000.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "839",
|
||||
"name": "WAN NUR FATIN NATASYA BT W M AZHARI",
|
||||
"pelaburan": "24,000.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "842",
|
||||
"name": "MUHAMAD SHAUQI BIN SHAHAPUDIN",
|
||||
"pelaburan": "14,966.10"
|
||||
},
|
||||
{
|
||||
"no_anggota": "847",
|
||||
"name": "NUR AIN SOFIA BINTI ROSLI",
|
||||
"pelaburan": "270.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "854",
|
||||
"name": "NUR FARAHIM BINTI MOHD OMAR",
|
||||
"pelaburan": "120,000.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "857",
|
||||
"name": "NUR IZZAH IWANI BINTI MOHD RAZDI",
|
||||
"pelaburan": "4,527.10"
|
||||
},
|
||||
{
|
||||
"no_anggota": "858",
|
||||
"name": "MOHAMMAD IZFAN BIN MAT IZANI",
|
||||
"pelaburan": "10,340.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "859",
|
||||
"name": "TUAN NOR DIANA BINTI TUAN AZHAN",
|
||||
"pelaburan": "1,889.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "860",
|
||||
"name": "NOR INTAN MARLINI BINTI NOR AZLAN",
|
||||
"pelaburan": "282.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "862",
|
||||
"name": "NUR IZZATI HUSNA BINTI ABD RAHMAN",
|
||||
"pelaburan": "752.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "863",
|
||||
"name": "NORAZERA BINTI ADIN",
|
||||
"pelaburan": "1,200.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "868",
|
||||
"name": "NUR AMIRAH BINTI ZULKIFLI",
|
||||
"pelaburan": "425.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "871",
|
||||
"name": "MUHAMMAD HAFIZI WAJDI BIN HASSAN",
|
||||
"pelaburan": "3,658.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "872",
|
||||
"name": "MUHAMMAD HAFIFI BIN ABD RAHMAN",
|
||||
"pelaburan": "379.50"
|
||||
},
|
||||
{
|
||||
"no_anggota": "882",
|
||||
"name": "FATIHA BINTI ABDULLAH",
|
||||
"pelaburan": "24,100.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "890",
|
||||
"name": "AMIRUL HISHAM AIMI BIN CHE AMRAN",
|
||||
"pelaburan": "9,641.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "894",
|
||||
"name": "AHMAD NAZRI BIN ISMAIL",
|
||||
"pelaburan": "25,450.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "917",
|
||||
"name": "MUHAMAD MUSLIM BIN MADIRAN",
|
||||
"pelaburan": "191.90"
|
||||
},
|
||||
{
|
||||
"no_anggota": "920",
|
||||
"name": "FARAHAIN FATIN BINTI MOHD FAUZI",
|
||||
"pelaburan": "270.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "950",
|
||||
"name": "DATO` ZAMRI BIN ISMAIL",
|
||||
"pelaburan": "1,500.00"
|
||||
},
|
||||
{
|
||||
"no_anggota": "958",
|
||||
"name": "AHMAD SUHAIL IRFAN BIN ZAMRI",
|
||||
"pelaburan": "184.50"
|
||||
}
|
||||
]
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -141,4 +141,6 @@ You can watch the System Demo [here](https://youtu.be/dsEoONiovdA).
|
||||
|
||||
```bash
|
||||
php artisan vendor:publish --provider="Spatie\Activitylog\ActivitylogServiceProvider" --tag=migrations
|
||||
```
|
||||
```
|
||||
|
||||
formula kiraan = ((Bulan masuk/12) * jumlah pelaburan) * 0.12
|
||||
@@ -392,7 +392,8 @@ export default {
|
||||
{ icon: "fa-barcode", label: "No. Anggota", value: u.no_anggota || "—" },
|
||||
{ icon: "fa-building", label: "Unit", value: u.unit || "—" },
|
||||
{ icon: "fa-line-chart", label: "Saham", value: "RM " + fmt(u.saham) },
|
||||
{ icon: "fa-money", label: "Yuran", value: "RM " + fmt(u.yuran) }
|
||||
{ icon: "fa-money", label: "Yuran", value: "RM " + fmt(u.yuran) },
|
||||
{ icon: "fa-percent", label: "Dividen Tahun 2025", value: "RM " + fmt(u.pelaburan) }
|
||||
];
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,204 +1,251 @@
|
||||
<template>
|
||||
<div class="d-flex justify-content-center align-items-center min-vh-100 bg-gradient">
|
||||
<div class="card shadow-lg rounded-lg border-primary" style="width: 100%; max-width: 400px; padding: 30px;">
|
||||
<div class="text-center mb-4">
|
||||
<img class="logo-img" src="/images/MyKoPKB-logo.png" alt="Logo" style="max-width: 250px; height: auto;">
|
||||
<h4 class="mt-3">Selamat Datang ke Portal MyKoPKB</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form @submit.prevent="login" id="login_form">
|
||||
<div class="form-group">
|
||||
<label for="no_kp">Sila Masukkan No. Kad Pengenalan</label>
|
||||
<input
|
||||
type="text"
|
||||
id="no_kp"
|
||||
name="no_kp"
|
||||
placeholder="Masukkan No. Kad Pengenalan Tanpa Dash (-)"
|
||||
class="form-control"
|
||||
required
|
||||
/>
|
||||
<div class="login-shell">
|
||||
<div class="login-card">
|
||||
<div class="login-header">
|
||||
<img class="login-logo" src="/images/MyKoPKB-logo.png" alt="MyKoPKB" />
|
||||
<h1 class="login-title">Selamat Datang</h1>
|
||||
<p class="login-subtitle">Log masuk untuk meneruskan ke Portal MyKoPKB</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-block"
|
||||
:disabled="loading"
|
||||
>
|
||||
<span v-if="loading" class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||
Masuk
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form @submit.prevent="login" id="login_form" class="login-form" novalidate>
|
||||
<div class="form-group mb-3">
|
||||
<label class="login-label" for="no_kp">No. Kad Pengenalan</label>
|
||||
<div class="login-field">
|
||||
<span class="login-field-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" focusable="false">
|
||||
<path
|
||||
d="M4 7.5A3.5 3.5 0 0 1 7.5 4h9A3.5 3.5 0 0 1 20 7.5v9A3.5 3.5 0 0 1 16.5 20h-9A3.5 3.5 0 0 1 4 16.5v-9Zm3.5-1.5A1.5 1.5 0 0 0 6 7.5v9A1.5 1.5 0 0 0 7.5 18h9a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 16.5 6h-9Zm1.5 3h6v2H9V9Zm0 4h10v2H9v-2Z" />
|
||||
</svg>
|
||||
</span>
|
||||
<input v-model.trim="no_kp" @input="onNoKpInput" :disabled="loading" type="text" id="no_kp"
|
||||
name="no_kp" class="form-control login-input" placeholder="Contoh: 901010101010"
|
||||
autocomplete="off" inputmode="numeric" enterkeyhint="go" required />
|
||||
</div>
|
||||
<small class="login-help">Masukkan tanpa dash (-) atau jarak.</small>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn login-btn" :disabled="loading || !no_kp">
|
||||
<span v-if="loading" class="spinner-border spinner-border-sm mr-2" role="status"
|
||||
aria-hidden="true"></span>
|
||||
<span>{{ loading ? 'Sedang log masuk…' : 'Masuk' }}</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false
|
||||
};
|
||||
return {
|
||||
loading: false,
|
||||
no_kp: ''
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.util.isAdminPortalSession()) {
|
||||
this.$router.replace(this.util.getAdminEntryRoute());
|
||||
}
|
||||
if (this.util.isAdminPortalSession()) {
|
||||
this.$router.replace(this.util.getAdminEntryRoute());
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
login() {
|
||||
this.startLoading();
|
||||
const noKp = document.getElementById('no_kp').value.trim();
|
||||
axios
|
||||
.post(config.API + 'voter/login', {
|
||||
no_kp: noKp,
|
||||
send_otp: false
|
||||
})
|
||||
.then(response => {
|
||||
this.stopLoading();
|
||||
if (this.util.showResult(response, 'success')) {
|
||||
this.$router.push({
|
||||
name: 'Voter Verify',
|
||||
params: {
|
||||
'nokp': response.data.nokp,
|
||||
'notel': response.data.notel,
|
||||
'debug_otp': response.data.debug_otp || null
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
this.stopLoading();
|
||||
this.util.showResult(error, 'error');
|
||||
});
|
||||
},
|
||||
startLoading() {
|
||||
this.loading = true;
|
||||
},
|
||||
stopLoading() {
|
||||
this.loading = false;
|
||||
}
|
||||
onNoKpInput() {
|
||||
// keep input forgiving: strip dash/spaces and non-digits
|
||||
this.no_kp = (this.no_kp || '').replace(/[\s-]+/g, '').replace(/[^\d]/g, '');
|
||||
},
|
||||
login() {
|
||||
this.startLoading();
|
||||
const noKp = (this.no_kp || '').trim();
|
||||
axios
|
||||
.post(config.API + 'voter/login', {
|
||||
no_kp: noKp,
|
||||
send_otp: false
|
||||
})
|
||||
.then(response => {
|
||||
this.stopLoading();
|
||||
if (this.util.showResult(response, 'success')) {
|
||||
this.$router.push({
|
||||
name: 'Voter Verify',
|
||||
params: {
|
||||
'nokp': response.data.nokp,
|
||||
'notel': response.data.notel,
|
||||
'debug_otp': response.data.debug_otp || null
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
this.stopLoading();
|
||||
this.util.showResult(error, 'error');
|
||||
});
|
||||
},
|
||||
startLoading() {
|
||||
this.loading = true;
|
||||
},
|
||||
stopLoading() {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
body {
|
||||
background-color: #f8f9fa; /* Light grey background */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.bg-gradient {
|
||||
background: linear-gradient(to bottom, #6c757d, #343a40); /* Gradient background from grey to dark */
|
||||
}
|
||||
|
||||
.d-flex {
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-shell {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.justify-content-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.align-items-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.min-vh-100 {
|
||||
min-height: 100vh; /* Ensure full screen height */
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #ffffff; /* White background for card */
|
||||
border: 2px solid #007bff; /* Blue border */
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.logo-img {
|
||||
max-width: 250px; /* Increased the size of the logo */
|
||||
justify-content: center;
|
||||
padding: 32px 16px;
|
||||
background: #f6f7fb;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
background: #ffffff;
|
||||
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||
border-radius: 20px;
|
||||
box-shadow:
|
||||
0 18px 55px rgba(2, 6, 23, 0.12),
|
||||
0 2px 10px rgba(2, 6, 23, 0.06);
|
||||
padding: 28px 24px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0 0 auto 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, #22c55e 0%, #3b82f6 55%, #a855f7 100%);
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 16px;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.login-logo {
|
||||
display: block;
|
||||
margin: 0 auto 10px;
|
||||
max-width: 240px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #5cb85c;
|
||||
border: none;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
padding: 12px 0;
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #4cae4c;
|
||||
}
|
||||
|
||||
/* Add this to make the placeholder move and animate */
|
||||
.form-control {
|
||||
border-radius: 30px;
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-control::placeholder {
|
||||
color: #6c757d;
|
||||
transition: all 0.3s ease; /* Smooth transition for moving placeholder */
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%); /* Vertically center the placeholder */
|
||||
}
|
||||
|
||||
.form-control:focus::placeholder {
|
||||
color: #ff0000; /* Change color on focus */
|
||||
left: 10px; /* Move it left */
|
||||
font-size: 12px; /* Shrink the font size */
|
||||
top: 5px; /* Move it up */
|
||||
transform: translateY(-50%) translateX(-10px); /* Move it to the left */
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: #007bff; /* Highlight the border on focus */
|
||||
box-shadow: 0 0 8px rgba(0, 123, 255, 0.6); /* Add a shadow effect */
|
||||
}
|
||||
|
||||
|
||||
h4 {
|
||||
.login-title {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.card {
|
||||
width: 90%;
|
||||
padding: 20px;
|
||||
font-weight: 900;
|
||||
letter-spacing: -0.02em;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.login-subtitle {
|
||||
margin: 6px 0 0;
|
||||
font-size: 14px;
|
||||
color: rgba(15, 23, 42, 0.72);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.login-label {
|
||||
font-weight: 700;
|
||||
font-size: 13px;
|
||||
color: rgba(15, 23, 42, 0.85);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.login-field {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.login-field-icon {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: rgba(15, 23, 42, 0.55);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.login-field-icon svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
fill: currentColor;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.login-input {
|
||||
border-radius: 14px;
|
||||
padding: 12px 14px 12px 42px;
|
||||
font-size: 16px;
|
||||
border: 1px solid rgba(15, 23, 42, 0.14);
|
||||
transition: box-shadow 160ms ease, border-color 160ms ease, transform 160ms ease;
|
||||
}
|
||||
|
||||
.login-input:focus {
|
||||
border-color: rgba(59, 130, 246, 0.55);
|
||||
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.14);
|
||||
}
|
||||
|
||||
.login-help {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
color: rgba(15, 23, 42, 0.6);
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 14px;
|
||||
padding: 13px 14px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0.01em;
|
||||
color: #ffffff;
|
||||
background: linear-gradient(180deg, #2563eb 0%, #1d4ed8 100%);
|
||||
border: 0;
|
||||
box-shadow: 0 12px 24px rgba(29, 78, 216, 0.22);
|
||||
transition: transform 160ms ease, box-shadow 160ms ease, filter 160ms ease;
|
||||
}
|
||||
|
||||
.login-btn:hover {
|
||||
filter: brightness(1.03);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 16px 34px rgba(29, 78, 216, 0.26);
|
||||
}
|
||||
|
||||
.login-btn:disabled {
|
||||
opacity: 0.72;
|
||||
filter: saturate(0.9);
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.login-shell {
|
||||
align-items: center;
|
||||
padding: 20px 14px;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
max-width: none;
|
||||
border-radius: 20px;
|
||||
padding: 22px 18px 18px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 20px;
|
||||
|
||||
.login-logo {
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.logo-img {
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
font-size: 14px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -1,55 +1,64 @@
|
||||
<template>
|
||||
<div class="col-md-5 col-md-offset-3">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<a @click="$router.go(-1)">Kembali</a>
|
||||
<h4 class="text-center">Kod Verifikasi!</h4>
|
||||
<div class="verify-shell">
|
||||
<div class="verify-card">
|
||||
<div class="verify-card__accent" aria-hidden="true"></div>
|
||||
|
||||
<div class="verify-header">
|
||||
<button type="button" class="verify-back" @click="$router.go(-1)">
|
||||
<span class="verify-back__icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" focusable="false">
|
||||
<path
|
||||
d="M15.5 19.5a1 1 0 0 1-.7-.3l-7-7a1 1 0 0 1 0-1.4l7-7a1 1 0 1 1 1.4 1.4L9.9 11.5l6.3 6.3a1 1 0 0 1-.7 1.7Z" />
|
||||
</svg>
|
||||
</span>
|
||||
Kembali
|
||||
</button>
|
||||
|
||||
<h1 class="verify-title">Kod Verifikasi</h1>
|
||||
<p class="verify-subtitle">
|
||||
<template v-if="!otpSent">
|
||||
Sahkan nombor telefon anda. Kod akan dihantar ke nombor berakhir
|
||||
<span class="verify-phone">+6 •••• •••• {{ phoneLastFour }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
Masukkan kod pengesahan yang dihantar ke nombor berakhir
|
||||
<span class="verify-phone">+6 •••• •••• {{ phoneLastFour }}</span>
|
||||
</template>
|
||||
</p>
|
||||
<p class="verify-note">
|
||||
Jika nombor ini tidak betul atau anda tidak menerima OTP, sila pergi ke kaunter IT.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="panel-body">
|
||||
<form @submit.prevent="login" id="login_form">
|
||||
<div class="m-4">
|
||||
<template v-if="!otpSent">
|
||||
<span>Sila sahkan nombor telefon anda. Kod akan dihantar ke nombor berakhir:</span>
|
||||
<br><b>+6 •••• •••• {{ phoneLastFour }}</b>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span>Sila masukkan kod pengesahan yang dihantar ke nombor berakhir:</span>
|
||||
<br><b>+6 •••• •••• {{ phoneLastFour }}</b>
|
||||
</template>
|
||||
<p class="text-muted small" style="margin-top:12px;margin-bottom:0;">
|
||||
Jika nombor ini tidak betul atau anda tidak menerima OTP, sila pergi ke kaunter IT.
|
||||
</p>
|
||||
</div>
|
||||
<form @submit.prevent="login" id="login_form" class="verify-form" novalidate>
|
||||
<div v-if="otpSent && showDebugOtp" class="verify-debug">
|
||||
OTP local development: <b>{{ debugOtp }}</b>
|
||||
</div>
|
||||
|
||||
<div v-if="otpSent && showDebugOtp" class="alert alert-info" style="margin-bottom:20px;">
|
||||
OTP local development: <b>{{ debugOtp }}</b>
|
||||
</div>
|
||||
<div v-if="!otpSent" class="verify-actions">
|
||||
<button type="button" class="btn verify-btn" :disabled="otpSending" @click="requestOtp">
|
||||
<span v-if="otpSending" class="spinner-border spinner-border-sm mr-2" role="status"
|
||||
aria-hidden="true"></span>
|
||||
Hantar OTP
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="!otpSent" class="form-group" style="margin-top: 20px;">
|
||||
<button type="button" class="btn btn-primary form-control" :disabled="otpSending" @click="requestOtp">
|
||||
<span v-if="otpSending" class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||
Hantar OTP ke nombor ini
|
||||
</button>
|
||||
</div>
|
||||
<div v-show="otpSent" class="verify-otp">
|
||||
<v-otp-input ref="otpInput" input-classes="otp-input" separator="-" :num-inputs="6"
|
||||
:should-auto-focus="true" :is-input-num="true" @on-change="handleOnChange"
|
||||
@on-complete="handleOnComplete" />
|
||||
</div>
|
||||
|
||||
<div v-show="otpSent" class="justify-content-center d-flex"
|
||||
style="margin-top:20px;margin-bottom:20px;display: flex;flex-direction: row;justify-content: center;align-items: center;">
|
||||
<v-otp-input ref="otpInput" input-classes="otp-input" separator="-" :num-inputs="6"
|
||||
:should-auto-focus="true" :is-input-num="true" @on-change="handleOnChange"
|
||||
@on-complete="handleOnComplete" />
|
||||
</div>
|
||||
<div v-if="otpSent" class="verify-links">
|
||||
<button type="button" class="verify-link" @click="resendVerify">Hantar kembali OTP</button>
|
||||
</div>
|
||||
|
||||
<div v-if="otpSent" style="margin-bottom:20px;">
|
||||
<a @click="resendVerify">Hantar Kembali OTP!</a>
|
||||
</div>
|
||||
|
||||
<div v-if="otpSent" class="form-group">
|
||||
<input ref="submitbtn" type="submit" class="btn btn-primary form-control" value="Sahkan & Teruskan"
|
||||
disabled />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div v-if="otpSent" class="verify-actions">
|
||||
<button ref="submitbtn" type="submit" class="btn verify-btn verify-btn--primary" disabled>
|
||||
Sahkan & Teruskan
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -167,25 +176,226 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.otp-input {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 5px;
|
||||
margin: 0 10px;
|
||||
font-size: 20px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.3);
|
||||
text-align: center;
|
||||
<style scoped>
|
||||
.verify-shell {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 32px 16px;
|
||||
background: #f6f7fb;
|
||||
}
|
||||
|
||||
.otp-input.error {
|
||||
border: 1px solid red !important;
|
||||
.verify-card {
|
||||
width: 100%;
|
||||
max-width: 520px;
|
||||
background: #ffffff;
|
||||
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||
border-radius: 20px;
|
||||
box-shadow:
|
||||
0 18px 55px rgba(2, 6, 23, 0.12),
|
||||
0 2px 10px rgba(2, 6, 23, 0.06);
|
||||
padding: 22px 22px 18px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.otp-input::-webkit-inner-spin-button,
|
||||
.otp-input::-webkit-outer-spin-button {
|
||||
.verify-card__accent {
|
||||
position: absolute;
|
||||
inset: 0 0 auto 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, #22c55e 0%, #3b82f6 55%, #a855f7 100%);
|
||||
}
|
||||
|
||||
.verify-header {
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.verify-back {
|
||||
appearance: none;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
padding: 8px 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: rgba(15, 23, 42, 0.7);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.verify-back:hover {
|
||||
color: rgba(15, 23, 42, 0.85);
|
||||
}
|
||||
|
||||
.verify-back__icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
display: inline-flex;
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.verify-back__icon svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.verify-title {
|
||||
margin: 6px 0 0;
|
||||
font-size: 22px;
|
||||
font-weight: 900;
|
||||
letter-spacing: -0.02em;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.verify-subtitle {
|
||||
margin: 8px 0 0;
|
||||
font-size: 14px;
|
||||
color: rgba(15, 23, 42, 0.72);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.verify-phone {
|
||||
display: inline-block;
|
||||
margin-left: 6px;
|
||||
font-weight: 900;
|
||||
color: rgba(15, 23, 42, 0.88);
|
||||
}
|
||||
|
||||
.verify-note {
|
||||
margin: 10px 0 0;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
color: rgba(15, 23, 42, 0.56);
|
||||
}
|
||||
|
||||
.verify-form {
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.verify-debug {
|
||||
margin: 0 0 14px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(59, 130, 246, 0.2);
|
||||
background: rgba(59, 130, 246, 0.08);
|
||||
color: rgba(15, 23, 42, 0.9);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.verify-otp {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 18px 0 14px;
|
||||
max-width: 360px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
/* v-otp-input renders inputs inside a child component.
|
||||
Use deep selectors so scoped styles apply. */
|
||||
::v-deep .otp-input {
|
||||
width: 40px !important;
|
||||
min-width: 40px !important;
|
||||
max-width: 40px !important;
|
||||
height: 46px !important;
|
||||
padding: 6px !important;
|
||||
margin: 0 6px !important;
|
||||
font-size: 18px !important;
|
||||
font-weight: 900 !important;
|
||||
border-radius: 14px !important;
|
||||
border: 1px solid rgba(15, 23, 42, 0.14) !important;
|
||||
text-align: center !important;
|
||||
box-sizing: border-box !important;
|
||||
transition: box-shadow 160ms ease, border-color 160ms ease, transform 160ms ease;
|
||||
}
|
||||
|
||||
::v-deep .otp-input.error {
|
||||
border-color: rgba(239, 68, 68, 0.9) !important;
|
||||
box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.14) !important;
|
||||
}
|
||||
|
||||
::v-deep .otp-input:focus {
|
||||
outline: none !important;
|
||||
border-color: rgba(59, 130, 246, 0.55) !important;
|
||||
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.14) !important;
|
||||
}
|
||||
|
||||
::v-deep .otp-input::-webkit-inner-spin-button,
|
||||
::v-deep .otp-input::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.verify-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 10px 0 16px;
|
||||
}
|
||||
|
||||
.verify-link {
|
||||
appearance: none;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
padding: 6px 10px;
|
||||
color: rgba(29, 78, 216, 0.92);
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.verify-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.verify-actions {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.verify-btn {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 14px;
|
||||
padding: 13px 14px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0.01em;
|
||||
color: #ffffff;
|
||||
background: linear-gradient(180deg, #2563eb 0%, #1d4ed8 100%);
|
||||
border: 0;
|
||||
box-shadow: 0 12px 24px rgba(29, 78, 216, 0.22);
|
||||
transition: transform 160ms ease, box-shadow 160ms ease, filter 160ms ease;
|
||||
}
|
||||
|
||||
.verify-btn:hover {
|
||||
filter: brightness(1.03);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 16px 34px rgba(29, 78, 216, 0.26);
|
||||
}
|
||||
|
||||
.verify-btn:disabled {
|
||||
opacity: 0.72;
|
||||
filter: saturate(0.9);
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.verify-shell {
|
||||
padding: 20px 14px;
|
||||
}
|
||||
|
||||
.verify-card {
|
||||
padding: 20px 18px 16px;
|
||||
}
|
||||
|
||||
::v-deep .otp-input {
|
||||
width: 38px !important;
|
||||
min-width: 38px !important;
|
||||
max-width: 38px !important;
|
||||
height: 44px !important;
|
||||
margin: 0 5px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user