Update Fitur Certificate Training

This commit is contained in:
mikael-zakaria 2026-03-18 11:10:05 +07:00
parent 038e5e74db
commit 799edf9588
10 changed files with 1494 additions and 637 deletions

View File

@ -161,6 +161,8 @@ $routes->get('/activities/getproduct/(:num)', 'Activities::getproduct/$1/$2/$3')
$routes->get('/activities/getvendor/(:num)', 'Activities::getvendor/$1');
$routes->get('/activities/getconsumable/(:num)', 'Activities::getconsumable/$1');
$routes->get('/activities/getcontact/(:num)', 'Activities::getcontact/$1');
$routes->get('/activities/getsitecontact/(:num)', 'Activities::getsitecontact/$1');
$routes->get('/activities/getsitecontact/(:num)/(:num)', 'Activities::getsitecontact/$1/$2');
$routes->get('/activities/newtextarea', 'Activities::newtextarea');
$routes->get('/activities/activitiesproduct/(:num)', 'Activities::activitiesproduct/$1');
$routes->get('/activities/dummy', 'Activities::dummy');
@ -212,23 +214,24 @@ $routes->group('certificates', function($routes) {
// Untuk Index Tiap Menu
$routes->get('maintenance', 'Certificates::maintenanceIndex'); // OK
$routes->get('installation', 'Certificates::installationIndex'); // OK
// $routes->get('training', 'Certificates::trainingIndex'); // OK
$routes->get('training', 'Certificates::trainingIndex'); // OK
// $routes->get('calibration', 'Certificates::calibrateIndex'); // OK
// Untuk Get API
$routes->get('api/getindexmaintenance', 'Certificates::getDataIndexMaintenance'); // OK
$routes->get('api/getindexinstallation', 'Certificates::getDataIndexInstallation'); // OK
// $routes->get('api/getindextraining', 'Certificates::getDataIndexTraining'); // OK
$routes->get('api/getindextraining', 'Certificates::getDataIndexTraining'); // OK
// $routes->get('api/getindexcalibrate', 'Certificates::getDataIndexCalibrate');
// Untuk Get Modal Data
$routes->post('api/showmaintenance', 'Certificates::showDataMaintenance'); // OK
$routes->post('api/showinstallation', 'Certificates::showDataInstallation'); // OK
$routes->post('api/showtraining', 'Certificates::showDataTraining'); // OK
// Untuk Preview Cerificate
$routes->get('maintenance/show/(:any)', 'Certificates::createMaintenancePreview/$1'); // OK
$routes->get('installation/show/(:any)', 'Certificates::createInstallationPreview/$1'); // OK
// $routes->get('training/show/(:any)', 'Certificates::createTrainingPreview/$1'); // OK
$routes->get('training/show/(:any)', 'Certificates::createTrainingPreview/$1'); // OK
// $routes->get('calibration/show/(:any)/(:any)', 'Certificates::createCalibratePreview/$1/$2');
$routes->post('api/validatecertificate', 'Certificates::validateCertificate'); // OK

View File

@ -3,6 +3,7 @@
namespace App\Controllers;
use App\Models\CertificateModel;
use App\Models\CertificateTrainingModel;
use App\Models\ActivitiesModel;
use App\Models\ActdetailModel;
use App\Models\InvTransModel;
@ -376,6 +377,14 @@ class Activities extends Controller {
$query = $db->query($sql);
$results = $query->getResultArray();
$data['vendors'] = $results;
// $sql = "select c.* from contacts c
// Left join sitecontact sc on sc.contactid=c.contactid
// where sc.siteid = 2";
$sql = "SELECT contactid, firstname, lastname, initial, title, email_1 FROM contacts";
$query = $db->query($sql);
$results = $query->getResultArray();
$data['contacts'] = $results;
if( $this->request->getVar('siteid') != '' ) { $siteid = $this->request->getVar('siteid'); }
@ -408,7 +417,13 @@ class Activities extends Controller {
'attachment' => $this->request->getVar('attachment'),
'actdetailid' => $this->request->getVar('actdetailid'),
'acttextid' => $this->request->getVar('acttextid'),
'textvalue' => $this->request->getVar('textvalue')
'textvalue' => $this->request->getVar('textvalue'),
];
$data['training_certificates'] = [
'trainingids' => $this->request->getVar('trainingids'),
'trainingnames' => $this->request->getVar('trainingnames'),
'trainingdates' => $this->request->getVar('trainingdates'),
'validatealltraining' => $this->request->getVar('validatealltraining'),
];
$actby = $this->request->getVar('actby');
@ -470,11 +485,17 @@ class Activities extends Controller {
$userid_owner = $data['new_value']['userid_owner'];
$this->createCertificate($actid, $issuedDate, $userid_owner, 'MC');
}
if ($this->request->getVar('installation')) { // Jika Maintenance Dicentang
if ($this->request->getVar('installation')) { // Jika Installation Dicentang
$issuedDate = $data['new_value']['closedate'] ?? null;
$userid_owner = $data['new_value']['userid_owner'];
$this->createCertificate($actid, $issuedDate, $userid_owner, 'IC');
}
if ($this->request->getVar('training')) { // Jika User Training Dicentang
if ($data['training_certificates']['trainingids'] != null) {
$userid_owner = $data['new_value']['userid_owner'];
$this->createCertificateTraining($actid, $userid_owner, $data['training_certificates']);
}
}
} else {
// update edit
@ -550,33 +571,43 @@ class Activities extends Controller {
}
}
// UNTUK CERTIFICATES
$certificateTypes = [
'maintenance' => 'MC',
'installation' => 'IC',
];
$hasAnyAction = false;
// // UNTUK CERTIFICATES
// $certificateTypes = [
// 'maintenance' => 'MC',
// 'installation' => 'IC'
// ];
// $hasAnyAction = false;
// $issuedDate = $data['new_value']['closedate'] ?? null;
// $userid_owner = $data['new_value']['userid_owner'];
// foreach ($certificateTypes as $requestName => $certCode) { //perulangan untuk mengecek semua checkbox
// if ($this->request->getVar($requestName)) {
// $hasAnyAction = true; // Jika checkbox dicentang, tandai bahwa ada aksi
// $this->updateCertificate($actid, $issuedDate, $userid_owner, $certCode);
// }
// }
// if (!$hasAnyAction) { // Jika setelah dicek semua ternyata tidak ada satupun yang dicentang, baru jalankan delete
// $this->deleteCertificate($actid);
// }
$reqMaintenance = $this->request->getPost('maintenance') !== null;
$reqInstallation = $this->request->getPost('installation') !== null;
$reqTraining = $this->request->getPost('training') !== null;
$issuedDate = $data['new_value']['closedate'] ?? null;
$userid_owner = $data['new_value']['userid_owner'];
foreach ($certificateTypes as $requestName => $certCode) { //perulangan untuk mengecek semua checkbox
if ($this->request->getVar($requestName)) {
$hasAnyAction = true; // Jika checkbox dicentang, tandai bahwa ada aksi
$this->updateCertificate($actid, $issuedDate, $userid_owner, $certCode);
}
if ($reqMaintenance) {// MC
$this->updateCertificate($actid, $issuedDate, $userid_owner, 'MC');
} else {
$this->deleteCertificate($actid, 'MC');
}
if (!$hasAnyAction) { // Jika setelah dicek semua ternyata tidak ada satupun yang dicentang, baru jalankan delete
$this->deleteCertificate($actid);
if ($reqInstallation) {// IC
$this->updateCertificate($actid, $issuedDate, $userid_owner, 'IC');
} else {
$this->deleteCertificate($actid, 'IC');
}
if ($reqTraining) {// UTC
$this->updateCertificateTraining($actid, $userid_owner, $data['training_certificates']);
} else {
$this->deleteCertificateTraining(null, $actid);
}
// if ($this->request->getVar('maintenance')) { // Maintenance
// // Maintenance sertifikat create or update
// $issuedDate = $data['new_value']['closedate'] ?? null;
// $userid_owner = $data['new_value']['userid_owner'];
// $this->updateCertificate($actid, $issuedDate, $userid_owner, 'MC');
// } else {
// // Hapus softdelete sertifikat
// $this->deleteCertificate($actid);
// }
}
// act by consumables
@ -1692,7 +1723,7 @@ class Activities extends Controller {
return view('invtrans_index', $data);
}
// Untuk CRUD Sertifikat
// Untuk CRUD Sertifikat Installation dan Maintenance
public function createCertificate ($actid, $issuedDate, $userid_owner, $certificate_type) {
$db = \Config\Database::connect();
@ -1744,6 +1775,7 @@ class Activities extends Controller {
$existingCerts = $certificateModel->withDeleted()
->select('status')
->where('actid', $actid)
->where('cert_type', $certificate_type)
->findAll();
// Variabel penanda apakah data sudah pernah ada di database
@ -1807,22 +1839,25 @@ class Activities extends Controller {
$newCertificate = $certificateModel->find($certid);
$certificateModel->update($certid, ['file_url' => base_url('certificates/number/'.$newCertificate['cert_number'])]);
} else {
// dd($isDataExist);
// Jika data sudah ada, eksekusi UPDATE
// Catatan: Karena bisa ada lebih dari 1 data dengan actid yang sama (karena findAll),
// Update ini akan menimpa semua baris yang punya actid tersebut.
$certificateModel->withDeleted()
->where('actid', $actid)
->where('cert_type', $certificate_type)
->set($certPayload)
->update();
}
}
public function deleteCertificate($actid) {
public function deleteCertificate($actid, $certificate_type) {
$certificateModel = new CertificateModel();
// 1. Cek apakah data sudah ada menggunakan findAll()
$existingCerts = $certificateModel->withDeleted()
->select('status')
->where('actid', $actid)
->where('cert_type', $certificate_type)
->findAll();
// Variabel penanda apakah data sudah pernah ada di database
@ -1837,8 +1872,307 @@ class Activities extends Controller {
return;
}
}
$certificateModel->where('actid', $actid)->delete();
$certificateModel->where('actid', $actid)->where('cert_type', $certificate_type)->delete();
}
public function createCertificateTraining ($actid, $userid_owner, $training_data) {
$db = \Config\Database::connect();
$sql = "SELECT prl.productaliastext as productname
FROM `activities` act
LEFT JOIN products pr ON pr.productid = act.productid
LEFT JOIN productcatalog prc ON prc.catalogid = pr.catalogid
LEFT JOIN productalias prl ON prl.productaliasid = prc.productaliasid
WHERE act.actid = $actid";
$query = $db->query($sql);
$result = $query->getRowArray();
$insertCert = [
'cert_type' => "UTC",
'actid' => $actid,
'user_id' => $userid_owner
];
$certificateModel = new CertificateModel();
$CertificateTrainingModel = new CertificateTrainingModel();
// Apakah Validasi Semuanya Dicentang
if($training_data['validatealltraining'] != null) {
$currentTime = date('Y-m-d H:i:s');
} else {
$currentTime = null;
}
foreach ($training_data['trainingids'] as $index => $id) {
// Ambil data berdasarkan index yang sama
$contact_id = $id;
$name = $training_data['trainingnames'][$index];
$issued_date = $training_data['trainingdates'][$index];
$insertCert['cert_name'] = "UTC_" . ($result['productname'] ?? 'UNKNOWN') . "_" . $name . "_" . $actid;
$insertCert['issued_date'] = $issued_date;
$insertCert['user_validation_at'] = $currentTime;
$certificateModel->insert($insertCert);
$certid = $certificateModel->getInsertID();
$newCertificate = $certificateModel->find($certid);
$certificateModel->update($certid, ['file_url' => base_url('certificates/number/'.$newCertificate['cert_number'])]);
$CertificateTrainingModel->insert(['cert_id' => (int)$certid, 'contact_id' => (int)$contact_id]);
}
}
public function updateCertificateTraining($actid, $userid_owner, $new_training_data) {
$certificateModel = new CertificateModel();
$existingTrainingCerts = $certificateModel->withDeleted() // 1. Ambil data existing dari database
->select('
Certificates.cert_id,
Certificates.cert_name,
Certificates.status,
Certificates_Training.contact_id
')
->join('Certificates_Training', 'Certificates_Training.cert_id = Certificates.cert_id', 'join')
->where('Certificates.actid', $actid)
->where('Certificates.cert_type', 'UTC')
->findAll();
$existingMap = []; // 2. Buat Map untuk data existing
foreach ($existingTrainingCerts as $cert) {
$contactId = is_object($cert) ? $cert->contact_id : $cert['contact_id'];
$existingMap[$contactId] = $cert;
}
// Temp
$dataToInsert = [];
$dataToUpdate = [];
$dataToDelete = [];
$currentTime = date('Y-m-d H:i:s');
// 3. Looping data baru dari form
if (!empty($new_training_data['trainingids']) && is_array($new_training_data['trainingids'])) {
foreach ($new_training_data['trainingids'] as $index => $contactId) {
$trainingName = $new_training_data['trainingnames'][$index] ?? null;
$trainingDate = $new_training_data['trainingdates'][$index] ?? null;
// Cek apakah data sudah ada di database
if (array_key_exists($contactId, $existingMap)) {
$existingData = $existingMap[$contactId];
$certId = is_object($existingData) ? $existingData->cert_id : $existingData['cert_id'];
// if ($new_training_data['validatealltraining'] != null) {
// $certificatesUpdate['user_validation_at'] = $currentTime;
// } else {
// $certificatesUpdate['user_validation_at'] = null;
// }
$certificatesUpdate['cert_id'] = $certId;
// $certificatesUpdate['actid'] = $actid;
$certificatesUpdate['issued_date'] = $trainingDate;
$certificatesUpdate['deleted_at'] = null;
$certificatesTrainingUpdate['cert_id'] = $certId;
$certificatesTrainingUpdate['contact_id'] = $contactId;
$certificatesTrainingUpdate['deleted_at'] = null;
$certificatesUpdates[] = $certificatesUpdate;
$certificatesTrainingUpdates[] = $certificatesTrainingUpdate;
unset($existingMap[$contactId]); // ELIMINASI: Hapus dari map karena data ini dipertahankan
} else {
// Insert Temp
$dataToInsert['trainingids'][] = $contactId;
$dataToInsert['trainingnames'][] = $trainingName;
$dataToInsert['trainingdates'][] = $trainingDate;
}
}
}
// 4. Proses Sisa Data di Map menjadi DELETE
// Apapun yang tersisa di $existingMap berarti tidak ada di $new_training_data
foreach ($existingMap as $contactId => $existingData) {
$certId = is_object($existingData) ? $existingData->cert_id : $existingData['cert_id'];
// Memasukkan cert_id, contact_id, (dan actid jika itu parameter ke-3 yang dimaksud)
$dataToDelete[] = [
'cert_id' => $certId,
'contact_id' => $contactId,
'actid' => $actid
];
}
// Cek hasil klasifikasinya:
// dd(['Insert' => $dataToInsert, 'Update' => $dataToUpdate, 'Delete' => $dataToDelete]);
// OK
if (!empty($dataToInsert)) {
$dataToInsert['validatealltraining'] = $new_training_data['validatealltraining'];
$this->createCertificateTraining($actid, $userid_owner, $dataToInsert);
}
if (!empty($certificatesUpdates)) {
$certificateModel = new CertificateModel();
$CertificateTrainingModel = new CertificateTrainingModel();
$db = \Config\Database::connect();
$db->transStart();
$certificateModel->builder()
->where('cert_type', 'UTC')
->where('status', 'unvalidated')
->updateBatch($certificatesUpdates, 'cert_id');
foreach ($certificatesTrainingUpdates as $trainingUpdate) {
$CertificateTrainingModel->builder()
->where('cert_id', $trainingUpdate['cert_id'])
->where('contact_id', $trainingUpdate['contact_id'])
->set([
'deleted_at' => null // Paksa restore data
])->update();
}
$db->transComplete();
if ($db->transStatus() === false) {
// Handle jika transaksi gagal
log_message('error', 'Gagal melakukan update sertifikat training.');
}
}
// OK
if (!empty($dataToDelete)) {
$this->deleteCertificateTraining($dataToDelete, null);
}
}
public function deleteCertificateTraining($updateToDelete, $deleteActid) {
$certificateModel = new CertificateModel();
$CertificateTrainingModel = new CertificateTrainingModel();
// if (!empty($updateToDelete)) {
// foreach ($updateToDelete as $key => $value) {
// // 1. Cari data sertifikat terlebih dahulu untuk mendapatkan cert_id
// $certificate = $certificateModel
// ->where('actid', $value['actid'])
// ->where('status', 'unvalidated')
// ->where('cert_type', 'UTC')
// ->first(); // Gunakan first() jika Laravel, atau get()->getRowArray() jika CI4
// // 2. Pastikan data ditemukan sebelum melakukan penghapusan
// if ($certificate) {
// // Ambil cert_id dari hasil query (sesuaikan dengan format return object/array framework Anda)
// $certId = $certificate['cert_id']; // atau $certificate->cert_id jika berupa object
// // 3. Hapus data di CertificateTrainingModel menggunakan $certId yang baru didapat
// $CertificateTrainingModel
// ->where('cert_id', $certId)
// ->where('contact_id', $value['contact_id'])
// ->delete();
// // 4. Hapus data di certificateModel
// // Kita cukup gunakan cert_id karena itu sudah spesifik (Primary Key)
// $certificateModel
// ->where('cert_id', $certId)
// ->delete();
// }
// }
// }
if (!empty($updateToDelete)) {
foreach ($updateToDelete as $key => $value) {
$isSuccess = $certificateModel
->where('actid', $value['actid'])
->where('cert_id', $value['cert_id'])
->where('status', 'unvalidated')
->where('cert_type', 'UTC')->delete();
// Cek apakah ada baris yang benar-benar dihapus di database
if ($isSuccess && $certificateModel->affectedRows() > 0) {
$CertificateTrainingModel
->where('cert_id', $value['cert_id'])
->where('contact_id', $value['contact_id'])->delete();
}
}
}
if ($deleteActid != null) {
// 1. Ambil (SELECT) dulu daftar cert_id yang memenuhi syarat sebelum dihapus
$certsToDelete = $certificateModel
->select('cert_id') // Kita hanya butuh kolom cert_id
->where('actid', $deleteActid)
->where('status', 'unvalidated')
->where('cert_type', 'UTC')
->findAll();
// Pastikan ada data yang ditemukan sebelum mengeksekusi delete
if (!empty($certsToDelete)) {
// Ekstrak hasil query menjadi array 1 dimensi berisi kumpulan cert_id
// Contoh hasil: [10, 15, 22]
$certIds = array_column($certsToDelete, 'cert_id');
// Gunakan Transaction agar jika salah satu gagal, semuanya batal (aman)
$db = \Config\Database::connect();
$db->transStart();
// 2. Hapus data relasi di tabel Certificates_Training (Pivot)
// Gunakan whereIn karena kita menghapus banyak cert_id sekaligus
$CertificateTrainingModel
->whereIn('cert_id', $certIds)
->delete();
// 3. Hapus data utama di tabel Certificates
// Kita bisa langsung pakai $certIds yang sudah didapat
$certificateModel
->whereIn('cert_id', $certIds)
->delete();
$db->transComplete();
if ($db->transStatus() === false) {
// Logika jika proses delete gagal
}
}
}
}
// Untuk Training Contact
public function getsitecontact($siteid=null, $actid=null){
$db = \Config\Database::connect();
if($siteid == null) {
$filterquery = '';
} else {
$filterquery = "where sc.siteid=$siteid";
}
$sql = "SELECT sc.siteid, c.contactid, c.firstname, c.lastname, c.title, c.initial, c.email_1 from contacts c
Left join sitecontact sc on sc.contactid=c.contactid
$filterquery";
$query = $db->query($sql);
$results = $query->getResultArray();
$data['sitecontacts'] = $results;
$data['siteid'] = $siteid;
// Get Data Training User
if($actid != null) {
$certificateTrainingModel = new CertificateTrainingModel();
$traininguser = $certificateTrainingModel->select('contacts.contactid, contacts.firstname, contacts.lastname, contacts.title, contacts.initial, contacts.email_1, certificates.issued_date')
->join('certificates', 'certificates.cert_id = certificates_training.cert_id', 'inner')
->join('contacts', 'contacts.contactid = certificates_training.contact_id', 'inner')
->where('certificates.actid', $actid)
->findAll();
$data['traininguser'] = $traininguser;
}
return view('activities_getsitecontacts', $data);
}
}

View File

@ -43,7 +43,7 @@ class Certificates extends BaseController {
')
->join('activities', 'activities.actid = certificates.actid', 'left')
->join('users', 'users.userid = certificates.user_id', 'left')
->where('certificates.cert_type', 'IC');;
->where('certificates.cert_type', 'IC');
// 2. Filter berdasarkan Role
if (in_array($userPosId, [1, 3, 5])) { // Manager & IT: Tidak perlu filter tambahan (lihat semua)
@ -208,7 +208,7 @@ class Certificates extends BaseController {
')
->join('activities', 'activities.actid = certificates.actid', 'left')
->join('users', 'users.userid = certificates.user_id', 'left')
->where('certificates.cert_type', 'MC');;
->where('certificates.cert_type', 'MC');
// 2. Filter berdasarkan Role
if (in_array($userPosId, [1, 3, 5])) { // Manager & IT: Tidak perlu filter tambahan (lihat semua)
@ -350,69 +350,189 @@ class Certificates extends BaseController {
// Untuk Sertifikat Training [3]
// public function trainingIndex() {
// return view('certificate_training_index');
// }
// public function getDataIndexTraining() {
// // $actid = $this->request->getVar('actid');
public function trainingIndex() {
return view('certificate_training_index');
}
public function getDataIndexTraining() {
$userPosId = session()->get('userposid');
$userId = session()->get('userid');
$certificateModel = new CertificateModel();
// // Sample data - replace with actual database query
// $certificates = [
// [
// 'certid' => 'f353ca91-4fc5-49f2-9b9e-304f83d11919',
// 'certname' => 'Jokoh Calibration Certificate',
// 'productname' => 'Jokoh',
// 'productnumber' => 'SN-2024-001',
// 'issuedate' => '2024-01-15',
// 'expirydate' => '2025-01-15',
// 'vendor' => 'Summit Calibration Lab',
// 'isval' => null
// ],
// [
// 'certid' => 'f353ca91-4fc5-49f2-9b9e-304f83d11919',
// 'certname' => 'Electrical Safety Test',
// 'productname' => 'GE Healthcare VIVID',
// 'productnumber' => 'GE-VIV-Q992',
// 'issuedate' => '2024-06-12',
// 'expirydate' => '2026-10-01',
// 'vendor' => 'Pramita Medika Service',
// 'isval' => '2026-03-01'
// ]
// ];
// 1. Mulai Query Builder
$builder = $certificateModel->select('
certificates.cert_id,
certificates.cert_number,
certificates.cert_name,
certificates.cert_type,
certificates.actid,
certificates.issued_date,
certificates.expired_date,
certificates.status,
certificates.user_validation_at,
certificates.spv_validation_at,
certificates.manager_validation_at,
activities.subject as activity_subject,
CONCAT(users.firstname, " ", users.lastname) as fullname
')
->join('activities', 'activities.actid = certificates.actid', 'left')
->join('users', 'users.userid = certificates.user_id', 'left')
->where('certificates.cert_type', 'UTC');
// // If no actid, return all certificates
// if (empty($certificates)) {
// return $this->response->setJSON(null);
// }
// 2. Filter berdasarkan Role
if (in_array($userPosId, [1, 3, 5])) { // Manager & IT: Tidak perlu filter tambahan (lihat semua)
} else if ($userPosId == 2) { // SPV: Melihat data user yang "reportto"-nya adalah ID supervisor ini
$builder->where('users.reportto', $userId);
} else if ($userPosId == 4) { // TSOIVD: Hanya melihat data milik sendiri
$builder->where('certificates.user_id', $userId);
} else {// Role lain: Tidak diberi akses
return $this->response->setJSON([]);
}
// return $this->response->setJSON($certificates);
// }
// public function createTrainingPreview($certid = null) { // Untuk Preview Sertifikat
// //Melakukan search data dari database
// 3. Eksekusi Query
$allData = $builder->findAll();
if (empty($allData)) {
return $this->response->setJSON([]); // Kembalikan array kosong agar frontend tidak error
}
return $this->response->setJSON($allData);
}
public function showDataTraining() { // Untuk API Get Data
$certid = $this->request->getPost('certid');
if (!$certid) {
return $this->response->setStatusCode(400)->setJSON(['error' => 'Certificate ID Not Found']);
}
$certificateModel = new CertificateModel();
$data = $certificateModel->select('
certificates.cert_id,
certificates.cert_number,
certificates.cert_name,
certificates.status,
activities.actid,
activities.subject,
productcatalog.productname,
sites.sitename,
products.productnumber,
CASE
WHEN certificates.cert_type = "MC" THEN "Maintenance"
WHEN certificates.cert_type = "IC" THEN "Installation"
WHEN certificates.cert_type = "UTC" THEN "User Training"
WHEN certificates.cert_type = "BAI" THEN "Berita Acara Instalasi"
WHEN certificates.cert_type = "BAP" THEN "Berita Acara Penarikan"
ELSE certificates.cert_type
END AS certtype,
CONCAT(us.firstname, " ", us.lastname) AS username,
certificates.user_validation_at,
CONCAT(spv.firstname, " ", spv.lastname) AS spvname,
certificates.spv_validation_at,
CONCAT(mgr.firstname, " ", mgr.lastname) AS managername,
certificates.manager_validation_at,
certificates.issued_date,
certificates.expired_date,
CONCAT(contacts.firstname, " ", contacts.lastname) AS fullname
', false)
->join('activities', 'activities.actid = certificates.actid', 'inner')
->join('products', 'products.productid = activities.productid', 'inner')
->join('productcatalog', 'productcatalog.catalogid = products.catalogid', 'inner')
->join('sites', 'sites.siteid = activities.siteid', 'left')
->join('users as us', 'us.userid = certificates.user_id', 'left')
->join('users as spv', 'spv.userid = certificates.spv_id', 'left')
->join('users as mgr', 'mgr.userid = certificates.manager_id', 'left')
->join('userposition', 'userposition.userposid = us.userposid', 'left')
->join('certificates_training', 'certificates_training.cert_id = certificates.cert_id', 'left')
->join('contacts', 'contacts.contactid = certificates_training.contact_id', 'left')
->where('certificates.cert_id', $certid)
->first();
$data['issued_date'] = $data['issued_date'] ? date('d M Y', strtotime($data['issued_date'])) : null;
$data['user_validation_at'] = $data['user_validation_at'] ? date('d M Y H:i', strtotime($data['user_validation_at'])) : null;
$data['spv_validation_at'] = $data['spv_validation_at'] ? date('d M Y H:i', strtotime($data['spv_validation_at'])) : null;
$data['manager_validation_at'] = $data['manager_validation_at'] ? date('d M Y H:i', strtotime($data['manager_validation_at'])) : null;
if (empty($data)) { // Jika Tidak Ada
return $this->response->setStatusCode(404)->setJSON(['error' => 'Maintenance certificate not found']);
}
return $this->response->setJSON($data);
}
public function createTrainingPreview($certid = null) { // Untuk Preview Sertifikat
if (!$certid) {
return $this->response->setStatusCode(400)->setJSON(['error' => 'Certificate ID Not Found']);
}
$certificateModel = new CertificateModel();
$data = $certificateModel->select('
certificates.cert_name,
certificates.issued_date,
certificates.expired_date,
certificates.file_url,
productalias.productaliastext as productname,
sites.sitename as sitename,
products.productnumber,
CASE
WHEN certificates.cert_type = "MC" THEN "maintenance"
WHEN certificates.cert_type = "IC" THEN "installation"
WHEN certificates.cert_type = "UTC" THEN "training"
WHEN certificates.cert_type = "BAI" THEN "Berita Acara Instalasi"
WHEN certificates.cert_type = "BAP" THEN "Berita Acara Penarikan"
ELSE certificates.cert_type
END AS cert_type,
CONCAT(users.firstname, " ", users.lastname) AS fullname,
userposition.texts AS user_position,
certificates.issued_date,
certificates.expired_date,
CONCAT(contacts.firstname, " ", contacts.lastname) AS fullname,
contacts.title,
zones.zonename as city,
certificates.cert_number -- Penting agar callback UUID tetap jalan
', false)
->join('users', 'users.userid = certificates.user_id', 'left')
->join('userposition', 'userposition.userposid = users.userposid', 'left')
->join('activities', 'activities.actid = certificates.actid', 'left')
->join('sites', 'sites.siteid = activities.siteid', 'left')
->join('accounts', 'accounts.accountid = sites.accountid', 'left')
->join('zones', 'zones.zoneid = accounts.zoneid', 'left')
->join('products', 'products.productid = activities.productid', 'left')
->join('productcatalog', 'productcatalog.catalogid = products.catalogid', 'left')
->join('productalias', 'productalias.productaliasid = productcatalog.productaliasid', 'left')
->join('certificates_training', 'certificates_training.cert_id = certificates.cert_id', 'left')
->join('contacts', 'contacts.contactid = certificates_training.contact_id', 'left')
->where('certificates.cert_id', $certid)
->first();
$certificate = [
'certname' => trim($data['cert_name']),
'sitename' => $data['sitename'],
'title' => empty($data['title']) ? '' : ', ' . $data['title'],
'city' => empty($data['city']) ? '' : $data['city'],
'certtype' => $data['cert_type'],
'fullname' => $data['fullname'],
'userposition' => $data['user_position'],
'productname' => $data['productname'],
'productnumber' => $data['productnumber'],
// 'issueddate' => $data['issued_date'],
'expireddate' => $data['expired_date']
];
if ($certificate['certtype'] == 'training') {
$certificate['issueddate'] = date('F d, Y', strtotime($data['issued_date']));
} else {
$certificate['issueddate'] = date('d-M-Y', strtotime($data['issued_date']));
}
$builder = new Builder(
writer: new PngWriter(),
data: $data['file_url'],
size: 120,
margin: 0
);
$result = $builder->build();
$certificate['qrcode'] = $result->getDataUri();
if (empty($certificate)) { // Jika Tidak Ada
return $this->response->setStatusCode(404)->setJSON(['error' => 'Maintenance certificate not found']);
}
// if (!$certid) {
// return $this->response->setStatusCode(400)->setJSON(['error' => 'Certificate ID is required']);
// }
// // Get certificate data Berdasarkan certid
// $certificate = [
// 'certid' => 'f353ca91-4fc5-49f2-9b9e-304f83d11919',
// 'certname' => 'Jokoh Calibration Certificate',
// 'productname' => 'Jokoh',
// 'productnumber' => 'SN-2024-001',
// 'issuedate' => '2024-01-15',
// 'expirydate' => '2025-01-15',
// 'vendor' => 'Summit Calibration Lab',
// 'isval' => null
// ];
// if (empty($certificate)) { // JIka Tidak Ada
// return $this->response->setStatusCode(404)->setJSON(['error' => 'Maintenance certificate not found']);
// }
// return $this->previewPdf($certificate, 'training'); // Preview PDF
// }
return $this->previewPdf($certificate, 'training'); // Preview PDF
}
// Untuk Sertifikat Calibrate [4]
// public function calibrateIndex() {
@ -490,10 +610,12 @@ class Certificates extends BaseController {
$dompdf = new Dompdf($options);
// Format dates
$certificate['issueddate'] = date('d-M-Y', strtotime($certificate['issueddate']));
if(isset($certificate['expireddate'])) {
if ($type == 'training') {
$certificate['issueddate'] = date('F d, Y', strtotime($certificate['issueddate']));
} else {
$certificate['issueddate'] = date('d-M-Y', strtotime($certificate['issueddate']));
}
if (isset($certificate['expireddate'])) {
$certificate['expireddate'] = date('d-M-Y', strtotime($certificate['expireddate']));
}
@ -619,51 +741,95 @@ class Certificates extends BaseController {
$certificateModel->update($certid, ['status' => 'validated']);
// Baru jalankan query berat JOIN di sini untuk keperluan PDF/Notifikasi
// $latestData = $certificateModel->select('
// certificates.cert_name,
// certificates.issued_date,
// certificates.expired_date,
// certificates.file_url,
// productalias.productaliastext as productname,
// sites.sitename as sitename,
// products.productnumber,
// CASE
// WHEN certificates.cert_type = "MC" THEN "Maintenance"
// WHEN certificates.cert_type = "IC" THEN "Installation"
// WHEN certificates.cert_type = "UTC" THEN "Training"
// WHEN certificates.cert_type = "BAI" THEN "Berita Acara Instalasi"
// WHEN certificates.cert_type = "BAP" THEN "Berita Acara Penarikan"
// ELSE certificates.cert_type
// END AS cert_type,
// CONCAT(users.firstname, " ", users.lastname) AS fullname,
// userposition.texts AS user_position,
// certificates.issued_date,
// certificates.expired_date,
// certificates.cert_number -- Penting agar callback UUID tetap jalan
// ', false)
// ->join('users', 'users.userid = certificates.user_id', 'left')
// ->join('userposition', 'userposition.userposid = users.userposid', 'left')
// ->join('activities', 'activities.actid = certificates.actid', 'left')
// ->join('sites', 'sites.siteid = activities.siteid', 'left')
// ->join('products', 'products.productid = activities.productid', 'left')
// ->join('productcatalog', 'productcatalog.catalogid = products.catalogid', 'left')
// ->join('productalias', 'productalias.productaliasid = productcatalog.productaliasid', 'left')
// ->where('certificates.cert_id', $certid)
// ->first();
$latestData = $certificateModel->select('
certificates.cert_name,
certificates.issued_date,
certificates.expired_date,
certificates.file_url,
productalias.productaliastext as productname,
sites.sitename as sitename,
products.productnumber,
CASE
WHEN certificates.cert_type = "MC" THEN "Maintenance"
WHEN certificates.cert_type = "IC" THEN "Installation"
WHEN certificates.cert_type = "UTC" THEN "Training"
WHEN certificates.cert_type = "BAI" THEN "Berita Acara Instalasi"
WHEN certificates.cert_type = "BAP" THEN "Berita Acara Penarikan"
ELSE certificates.cert_type
END AS cert_type,
CONCAT(users.firstname, " ", users.lastname) AS fullname,
userposition.texts AS user_position,
certificates.issued_date,
certificates.expired_date,
certificates.cert_number -- Penting agar callback UUID tetap jalan
', false)
->join('users', 'users.userid = certificates.user_id', 'left')
->join('userposition', 'userposition.userposid = users.userposid', 'left')
->join('activities', 'activities.actid = certificates.actid', 'left')
->join('sites', 'sites.siteid = activities.siteid', 'left')
->join('products', 'products.productid = activities.productid', 'left')
->join('productcatalog', 'productcatalog.catalogid = products.catalogid', 'left')
->join('productalias', 'productalias.productaliasid = productcatalog.productaliasid', 'left')
->where('certificates.cert_id', $certid)
->first();
certificates.cert_name,
certificates.issued_date,
certificates.expired_date,
certificates.file_url,
productalias.productaliastext as productname,
sites.sitename as sitename,
products.productnumber,
CASE
WHEN certificates.cert_type = "MC" THEN "maintenance"
WHEN certificates.cert_type = "IC" THEN "installation"
WHEN certificates.cert_type = "UTC" THEN "training"
WHEN certificates.cert_type = "BAI" THEN "Berita Acara Instalasi"
WHEN certificates.cert_type = "BAP" THEN "Berita Acara Penarikan"
ELSE certificates.cert_type
END AS cert_type,
CONCAT(users.firstname, " ", users.lastname) AS fullname,
userposition.texts AS user_position,
certificates.issued_date,
certificates.expired_date,
CONCAT(contacts.firstname, " ", contacts.lastname) AS fullname,
contacts.title,
certificates.cert_number -- Penting agar callback UUID tetap jalan
', false)
->join('users', 'users.userid = certificates.user_id', 'left')
->join('userposition', 'userposition.userposid = users.userposid', 'left')
->join('activities', 'activities.actid = certificates.actid', 'left')
->join('sites', 'sites.siteid = activities.siteid', 'left')
->join('accounts', 'accounts.accountid = sites.accountid', 'left')
->join('zones', 'zones.zoneid = accounts.zoneid', 'left')
->join('products', 'products.productid = activities.productid', 'left')
->join('productcatalog', 'productcatalog.catalogid = products.catalogid', 'left')
->join('productalias', 'productalias.productaliasid = productcatalog.productaliasid', 'left')
->join('certificates_training', 'certificates_training.cert_id = certificates.cert_id', 'left')
->join('contacts', 'contacts.contactid = certificates_training.contact_id', 'left')
->where('certificates.cert_id', $certid)
->first();
$certificate = [
'title' => empty($latestData['title']) ? '' : ', ' . $latestData['title'],
'file_url' => $latestData['file_url'],
'certname' => $latestData['cert_name'],
'sitename' => $latestData['sitename'],
'city' => empty($data['city']) ? '' : $data['city'],
'certtype' => $latestData['cert_type'],
'fullname' => $latestData['fullname'],
'userposition' => $latestData['user_position'],
'productname' => $latestData['productname'],
'productnumber' => $latestData['productnumber'],
'issueddate' => date('d-M-Y', strtotime($latestData['issued_date'])),
// 'issueddate' => date('d-M-Y', strtotime($latestData['issued_date'])),
'expireddate' => date('d-M-Y', strtotime($latestData['expired_date'])),
'exportToPDF' => true
];
if ($latestData['cert_type'] == 'training') {
$certificate['issueddate'] = date('F d, Y', strtotime($latestData['issued_date']));
} else {
$certificate['issueddate'] = date('d-M-Y', strtotime($latestData['issued_date']));
}
try {

View File

@ -3,6 +3,7 @@
namespace App\Controllers;
use App\Models\ContactsModel;
use App\Models\SiteContactModel;
use CodeIgniter\Controller;
class Contacts extends Controller {
@ -38,7 +39,7 @@ class Contacts extends Controller {
}
if ($this->request->getMethod() === 'POST') {
$rules = [
'contactid' => 'required',
// 'contactid' => 'required',
'firstname' => 'required',
'email_1' => 'required',
'initial' => 'required'
@ -53,7 +54,8 @@ class Contacts extends Controller {
'email_2' => $this->request->getVar('email_2'),
'phone' => $this->request->getVar('phone'),
'mobile_1' => $this->request->getVar('mobile_1'),
'mobile_2' => $this->request->getVar('mobile_2')
'mobile_2' => $this->request->getVar('mobile_2'),
'siteid' => $this->request->getVar('siteid') ?? null //Untuk Create dari AR
];
if($this->validate($rules)){
@ -67,6 +69,18 @@ class Contacts extends Controller {
$contactsModel->set('createdate', 'NOW()', FALSE);
$contactsModel->set('enddate', NULL);
$contactsModel->insert($data['new_value']);
$contactid = $contactsModel->getInsertID();
if($this->request->getVar('siteid') != null) {
$siteContactModel = new SiteContactModel();
$siteContactValue = [
'siteid' => $this->request->getVar('siteid'),
'contactid' => $contactid,
'contactemail' => $this->request->getVar('email_1'),
];
$siteContactModel->insert($siteContactValue);
}
return view('form_success');
}
} else {

View File

@ -0,0 +1,63 @@
<?php
namespace App\Models;
use CodeIgniter\Model;
class CertificateTrainingModel extends Model
{
protected $table = 'certificates_training';
protected $primaryKey = 'cert_training_id';
protected $useAutoIncrement = true;
protected $returnType = 'array'; // Bisa diubah ke 'object' jika lebih suka
// Fitur Soft Delete
protected $useSoftDeletes = true;
// Field yang boleh diisi (Mass Assignment)
protected $allowedFields = ['cert_id', 'contact_id'];
// Fitur Otomatisasi Timestamp
protected $useTimestamps = true;
protected $dateFormat = 'datetime';
protected $createdField = 'created_at';
protected $updatedField = 'updated_at';
protected $deletedField = 'deleted_at';
// Validasi Sederhana
protected $validationRules = [
'cert_id' => 'required|numeric',
'contact_id' => 'required|numeric',
];
protected $validationMessages = [
'cert_id' => [
'required' => 'ID Sertifikat wajib diisi.',
'numeric' => 'ID Sertifikat harus berupa angka.'
],
'contact_id' => [
'required' => 'ID Contact wajib diisi.',
'numeric' => 'ID Contact harus berupa angka.'
]
];
protected $skipValidation = false;
// /**
// * Contoh Method untuk mengambil data training lengkap dengan nama sertifikat dan contact
// */
// public function getTrainingDetails($id = null)
// {
// $builder = $this->db->table($this->table);
// $builder->select('Certificates_Training.*, Certificates.cert_name, Contacts.contact_name'); // Asumsi nama tabel Contacts
// $builder->join('Certificates', 'Certificates.cert_id = Certificates_Training.cert_id');
// $builder->join('Contacts', 'Contacts.contact_id = Certificates_Training.contact_id');
// $builder->where('Certificates_Training.deleted_at', null); // Pastikan yang belum dihapus
// if ($id) {
// return $builder->where('cert_training_id', $id)->get()->getRowArray();
// }
// return $builder->get()->getResultArray();
// }
}

View File

@ -26,6 +26,7 @@ foreach($products as $qdata) {
$now = date('Y-m-d\TH:i');
$siteid = '';
$contactid = '';
$productid = '';
$vendorid = '';
$swversion = '';
@ -62,6 +63,7 @@ if(isset($data)) {
$acttypeid = $data['acttypeid'];
$userid_owner = $data['userid_owner'];
$activitystatus = $data['activitystatus'];
$contactid = $data['contactid'];
// Untuk REFF : Menentukan mana yg dipakai untuk actid_ref
if ($refer_page) {
@ -109,6 +111,9 @@ if(isset($data)) {
<form method="post" enctype="multipart/form-data">
<input type='hidden' name='userid_creator' value='<?=$_SESSION['userid'];?>' />
<!-- Untuk Training -->
<input type='hidden' id='current_actid' value='<?= isset($data['actid']) ? $data['actid'] : '' ?>' />
<div class="card">
<div class="card-body">
@ -194,7 +199,7 @@ if(isset($data)) {
<div class="col-12 col-md-6">
<div class="form-group">
<label for="owner" class="form-label border-start border-5 border-primary ps-1">Activity Owner</label>
<select name="userid_owner" class="form-select form-select-sm select2">
<select id="userid_owner" name="userid_owner" class="form-select form-select-sm select2">
<option value="">-- Choose one --</option>
<?php
foreach ($users as $data) {
@ -250,7 +255,7 @@ if(isset($data)) {
Berita Acara Instalasi
</label>
</div>
</div>
</div> -->
<div class="col-md-6 mb-2">
<div class="form-check">
@ -259,7 +264,7 @@ if(isset($data)) {
User Training Certificate
</label>
</div>
</div> -->
</div>
</div>
</div>
@ -269,7 +274,8 @@ if(isset($data)) {
<div class="col-12 col-md-6">
<div class="form-group">
<label for="status" class="form-label border-start border-5 border-primary ps-1">Activity Status</label>
<select name="activitystatus" id="status" class="form-select form-select-sm" onchange='changestatus()' required>
<!-- <select name="activitystatus" id="status" class="form-select form-select-sm" onchange='changestatus()' required> -->
<select name="activitystatus" id="status" class="form-select form-select-sm" required>
<option value="" disabled>-- Choose one --</option>
<?php
foreach ($stats as $statcode => $stat) {
@ -633,51 +639,7 @@ if(isset($data)) {
</h2>
<div id="trainingAccordion" class="accordion-collapse collapse bg-white" aria-labelledby="trainingHeading" data-bs-parent="#accordionTraining">
<div class="accordion-body">
<input type='hidden' name='trainingid_delete' id='trainingid_delete' />
<div class="row mb-3">
<div class="col-12 col-lg-5">
<div class="form-group">
<label for="trainingname" class="form-label">Nama</label>
<input type='text' class="form-control form-control-sm trainingname" placeholder="Masukkan nama" />
</div>
</div>
<div class="col-12 col-lg-5">
<div class="form-group">
<label for="trainingdate" class="form-label">Tanggal</label>
<input type='text' class="form-control form-control-sm trainingdate" placeholder="YYYY-MM-DD" />
</div>
</div>
<div class="d-grid gap-2 col-12 col-lg-2 mt-2">
<button type='button' class='btn btn-sm btn-success' onclick='addTrainingRow();'>Tambah</button>
</div>
</div>
<hr />
<div class='table-responsive'>
<table class='table table-bordered table-sm' id='training_table'>
<thead>
<tr>
<th style='width:45%;'>Nama Analis</th>
<th style='width:45%;'>Tanggal</th>
<th style='width:10%;'>Action</th>
</tr>
</thead>
<tbody>
<?php
if(isset($trainingdata)) {
foreach($trainingdata as $data) {
$trainingid = $data['trainingid'];
$qname = $data['trainingname'];
$qdate = $data['trainingdate'];
echo "<tr> <input type='hidden' name='trainingnames[]' value='".$qname."'> <input type='hidden' name='trainingdates[]' value='".$qdate."' /> <td>$qname</td> <td>$qdate</td>".
"<td> <button type='button' class='btn btn-sm btn-warning' onclick='deleteTrainingRow(this, $trainingid)'>Hapus</button> </td>".
"</tr>";
}
}
?>
</tbody>
</table>
</div>
<span id='training_form'></span>
</div>
</div>
</div>
@ -804,14 +766,14 @@ $(document).ready(function() {
// $("#calibration").prop('disabled', true);
$("#installation").prop('disabled', true);
// $("#offreport").prop('disabled', true);
// $("#training").prop('disabled', true);
$("#training").prop('disabled', true);
}
else if (this.value == '3') {
$("#maintenance").prop('disabled', true);
// $("#calibration").prop('disabled', false);
$("#installation").prop('disabled', false);
// $("#offreport").prop('disabled', false);
// $("#training").prop('disabled', false);
$("#training").prop('disabled', false);
}
else {
// Ini akan menjadi default jika #acttypeid kosong atau bukan 5 dan 3
@ -819,9 +781,30 @@ $(document).ready(function() {
// $("#calibration").prop('disabled', true);
$("#installation").prop('disabled', true);
// $("#offreport").prop('disabled', true);
// $("#training").prop('disabled', true);
$("#training").prop('disabled', true);
}
}).trigger('change');
let siteid = $('#siteid').val();
let current_actid = $('#current_actid').val();
// Perbaikan di sini: '!current_actid' otomatis mengecek null, undefined, atau string kosong ('')
if (!current_actid) {
current_actid = '';
} else {
current_actid = '/' + current_actid;
}
// Untuk Training
if (siteid !== '') {
$.get("<?=base_url();?>activities/getsitecontact/" + siteid + current_actid, function(data) {
$('#training_form').html(data);
$('.select2').select2({
theme: 'bootstrap-5',
width: '100%'
});
});
}
});
// acttext
@ -832,7 +815,7 @@ function acttext_del(e, actdetailid) {
var actdetailid = actdetailid.toString();
var d = $('#actdetailid_delete');
d.val(d.val()+' '+actdetailid);
console.log(d.val());
// console.log(d.val());
}
}
@ -884,35 +867,6 @@ function acttext_add() {
})
}
// $('#actby').change(function() {
// var actby=this.value;
// if(actby == 'P') {
// var siteid=$('#siteid').val();
// $.get("<?=base_url();?>/activities/getproduct/"+siteid, function(data) {
// $('#actby_item').html(data);
// $('.select2').select2({
// theme: 'bootstrap-5',
// width: '100%'
// });
// })
// } else if (actby == 'V') {
// $.get("<?=base_url();?>/activities/getvendor/0", function(data) {
// $('#actby_item').html(data);
// })
// } else if (actby == 'C') {
// $.get("<?=base_url();?>/activities/getconsumable/0", function(data) {
// $('#actby_item').html(data);
// $('.select2').select2({
// theme: 'bootstrap-5',
// width: '100%'
// });
// })
// }else {
// $('#actby_item').html('');
// }
// })
// 1. Event ketika 'Activity by' diubah (Kode asli Anda dengan sedikit penyesuaian)
$('#actby').change(function() {
var actby = this.value;
@ -949,8 +903,27 @@ $('#actby').change(function() {
// 2. --- MODIFIKASI TAMBAHAN ---
// Event ketika 'Site' diubah
$('#siteid').change(function() {
var actby = $('#actby').val(); // Cek status Activity by saat ini
var siteid = this.value; // Ambil nilai siteid yang baru dipilih
let actby = $('#actby').val(); // Cek status Activity by saat ini
let siteid = $('#siteid').val();
let current_actid = $('#current_actid').val();
if (!current_actid) {
current_actid = '';
} else {
current_actid = '/' + current_actid;
}
// Khusus Untuk Training
if(siteid == '') {
$('#training_form').html('<div class="text-center">Please Fill Site First!</div>');
} else {
$.get("<?=base_url();?>activities/getsitecontact/" + siteid + current_actid, function(data) {
$('#training_form').html(data);
$('.select2').select2({
theme: 'bootstrap-5',
width: '100%'
});
});
}
// Hanya jalankan AJAX ulang JIKA Activity by sedang memilih 'P'
if (actby == 'P') {
@ -988,12 +961,11 @@ $('#status').change(function() {
// else { $("#maintenance").prop('disabled', true); $("#calibration").prop('disabled', true); $("#installation").prop('disabled', true); $("#offreport").prop('disabled', true); $("#training").prop('disabled', true);}
// })
$('#acttypeid').change(function() {
if (this.value=='5'){ $("#maintenance").prop('disabled', false); $("#installation").prop('disabled', true);}
else if (this.value=='3'){ $("#maintenance").prop('disabled', true); $("#installation").prop('disabled', false);}
else { $("#maintenance").prop('disabled', true); $("#installation").prop('disabled', true);}
if (this.value=='5'){ $("#maintenance").prop('disabled', false); $("#installation").prop('disabled', true); $("#training").prop('disabled', true);}
else if (this.value=='3'){ $("#maintenance").prop('disabled', true); $("#installation").prop('disabled', false); $("#training").prop('disabled', false); }
else { $("#maintenance").prop('disabled', true); $("#installation").prop('disabled', true); $("#training").prop('disabled', true);}
})
// reportdate change => opendate.value = reportdate.value
$('#reportdate').change(function() {
$('#opendate').val(this.value);
@ -1012,7 +984,7 @@ function deleteRow(btn, itxid) {
var itxid = itxid.toString();
var d = $('#itxid_delete');
d.val(d.val()+' '+itxid);
console.log(d.val());
// console.log(d.val());
}
}
@ -1097,41 +1069,10 @@ function triggerChange(element){
element.dispatchEvent(changeEvent);
}
// Training Analyst
function addTrainingRow() {
var trainingname = $('.trainingname').val();
var trainingdate = $('.trainingdate').val();
if (trainingname == "" || trainingdate == "") {
alert("Nama dan tanggal tidak boleh kosong");
return;
}
var newRow = "<tr> <input type='hidden' name='trainingnames[]' value='"+trainingname+"'> <input type='hidden' name='trainingdates[]' value='"+trainingdate+"' />"+
" <td>"+trainingname+"</td> <td>"+trainingdate+"</td>"+
" <td class='text-center'> <button type='button' class='btn btn-sm btn-warning' onclick='deleteTrainingRow(this, 0)'>Hapus</button> </td> </tr>";
$("#training_table").append(newRow);
$('.trainingname').val('');
}
function deleteTrainingRow(btn, trainingid) {
if(confirm('Are you sure?')) {
var row = btn.parentNode.parentNode;
row.parentNode.removeChild(row);
if (trainingid > 0) {
var trainingid = trainingid.toString();
var d = $('#trainingid_delete');
d.val(d.val()+' '+trainingid);
console.log(d.val());
}
}
}
// flatpickr for training date
flatpickr(".trainingdate", { allowInput: true, dateFormat: "Y-m-d" });
// Training checkbox enable/disable accordion
function toggleTrainingAccordion() {
var trainingChecked = $('#training').prop('checked');
var trainingDisabled = $('#training').prop('disabled');
let trainingChecked = $('#training').prop('checked');
let trainingDisabled = $('#training').prop('disabled');
if (trainingChecked && !trainingDisabled) {
// Enable accordion
$('#accordionTraining').find('button').prop('disabled', false);
@ -1140,6 +1081,7 @@ function toggleTrainingAccordion() {
$('#accordionTraining').find('button').removeClass('disabled');
$('#accordionTraining').removeClass('opacity-50').removeClass('pointer-events-none');
$('#accordionTraining').find('.accordion-button').attr('data-bs-toggle', 'collapse');
} else {
// Disable accordion
$('#accordionTraining').find('button').prop('disabled', true);
@ -1184,53 +1126,48 @@ function toggleCalibrateAccordion() {
$('#accordionCalibrate').find('.accordion-button').removeAttr('data-bs-toggle');
}
}
// Initialize on page load
toggleCalibrateAccordion();
// toggleCalibrateAccordion();
// Event listener for calibration checkbox change
$('#calibration').change(function() {
toggleCalibrateAccordion();
});
// $('#calibration').change(function() {
// toggleCalibrateAccordion();
// });
// Event listener for calibration checkbox disable/enable (when acttypeid changes)
$('#acttypeid').change(function() {
toggleCalibrateAccordion();
});
// $('#acttypeid').change(function() {
// toggleCalibrateAccordion();
// });
// Calibrate
function addCalibrateRow() {
var calibratename = $('.calibratename').val();
var calibratevalue = $('.calibratevalue').val();
var calibrateunit = $('.calibrateunit').val();
// function addCalibrateRow() {
// var calibratename = $('.calibratename').val();
// var calibratevalue = $('.calibratevalue').val();
// var calibrateunit = $('.calibrateunit').val();
if (calibratename == "" || calibratevalue == "" || calibrateunit == "") {
alert("Nama parameter, nilai, dan satuan tidak boleh kosong");
return;
}
// if (calibratename == "" || calibratevalue == "" || calibrateunit == "") {
// alert("Nama parameter, nilai, dan satuan tidak boleh kosong");
// return;
// }
var newRow = "<tr> <input type='hidden' name='calibratenames[]' value='"+calibratename+"'> <input type='hidden' name='calibratevalues[]' value='"+calibratevalue+"' /> <input type='hidden' name='calibrateunits[]' value='"+calibrateunit+"' />"+
" <td>"+calibratename+"</td> <td>"+calibratevalue+"</td> <td>"+calibrateunit+"</td>"+
" <td class='text-center'> <button type='button' class='btn btn-sm btn-warning' onclick='deleteCalibrateRow(this, 0)'>Hapus</button> </td> </tr>";
$("#calibrate_table").append(newRow);
// var newRow = "<tr> <input type='hidden' name='calibratenames[]' value='"+calibratename+"'> <input type='hidden' name='calibratevalues[]' value='"+calibratevalue+"' /> <input type='hidden' name='calibrateunits[]' value='"+calibrateunit+"' />"+
// " <td>"+calibratename+"</td> <td>"+calibratevalue+"</td> <td>"+calibrateunit+"</td>"+
// " <td class='text-center'> <button type='button' class='btn btn-sm btn-warning' onclick='deleteCalibrateRow(this, 0)'>Hapus</button> </td> </tr>";
// $("#calibrate_table").append(newRow);
$('.calibratename').val('');
$('.calibratevalue').val('');
$('.calibrateunit').val('');
}
function deleteCalibrateRow(btn, calibrateid) {
if(confirm('Are you sure?')) {
var row = btn.parentNode.parentNode;
row.parentNode.removeChild(row);
if (calibrateid > 0) {
var calibrateid = calibrateid.toString();
var d = $('#calibrateid_delete');
d.val(d.val()+' '+calibrateid);
console.log(d.val());
}
}
}
// $('.calibratename').val('');
// $('.calibratevalue').val('');
// $('.calibrateunit').val('');
// }
// function deleteCalibrateRow(btn, calibrateid) {
// if(confirm('Are you sure?')) {
// var row = btn.parentNode.parentNode;
// row.parentNode.removeChild(row);
// if (calibrateid > 0) {
// var calibrateid = calibrateid.toString();
// var d = $('#calibrateid_delete');
// d.val(d.val()+' '+calibrateid);
// console.log(d.val());
// }
// }
// }
</script>
<script src="<?=base_url();?>/assets/uppy/uppy-full.js"></script>

View File

@ -0,0 +1,319 @@
<div class='row mb-2'>
<div class="col-12 text-end">
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#createContactModal">
<i class="fa-solid fa-plus"></i> Tambah Analyst Baru
</button>
</div>
</div>
<div class="modal fade" id="createContactModal" tabindex="-1" aria-labelledby="createContactModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title fw-bold" id="createContactModalLabel">Contact Editor</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="formCreateContact" action="<?= base_url('/contacts/create'); ?>" method="POST">
<div class="modal-body">
<input type='hidden' name='siteid' id='siteid' value='<?= $siteid; ?>' />
<div class="row mb-3 align-items-center">
<label for="firstname" class="col-sm-3 col-form-label">First Name <span class="text-danger">*</span></label>
<div class="col-sm-9">
<input type="text" class="form-control form-control-sm" id="firstname" name="firstname" required>
</div>
</div>
<div class="row mb-3 align-items-center">
<label for="lastname" class="col-sm-3 col-form-label">Last Name <span class="text-danger">*</span></label>
<div class="col-sm-9">
<input type="text" class="form-control form-control-sm" id="lastname" name="lastname" required>
</div>
</div>
<div class="row mb-3 align-items-center">
<label for="initial" class="col-sm-3 col-form-label">Initial <span class="text-danger">*</span></label>
<div class="col-sm-9">
<input type="text" class="form-control form-control-sm" id="initial" name="initial" required>
</div>
</div>
<div class="row mb-3 align-items-center">
<label for="title" class="col-sm-3 col-form-label">Title</label>
<div class="col-sm-9">
<input type="text" class="form-control form-control-sm" id="title" name="title">
</div>
</div>
<div class="row mb-3 align-items-center">
<label for="birthdate" class="col-sm-3 col-form-label">Birthdate</label>
<div class="col-sm-9">
<input type="date" class="form-control form-control-sm" id="birthdate" name="birthdate">
</div>
</div>
<div class="row mb-3 align-items-center">
<label for="email_1" class="col-sm-3 col-form-label">Email 1 <span class="text-danger">*</span></label>
<div class="col-sm-9">
<input type="email" class="form-control form-control-sm" id="email_1" name="email_1" required>
</div>
</div>
<div class="row mb-3 align-items-center">
<label for="email_2" class="col-sm-3 col-form-label">Email 2</label>
<div class="col-sm-9">
<input type="email" class="form-control form-control-sm" id="email_2" name="email_2">
</div>
</div>
<div class="row mb-3 align-items-center">
<label for="phone" class="col-sm-3 col-form-label">Phone</label>
<div class="col-sm-9">
<input type="text" class="form-control form-control-sm" id="phone" name="phone">
</div>
</div>
<div class="row mb-3 align-items-center">
<label for="mobile_1" class="col-sm-3 col-form-label">Mobile 1</label>
<div class="col-sm-9">
<input type="text" class="form-control form-control-sm" id="mobile_1" name="mobile_1">
</div>
</div>
<div class="row mb-3 align-items-center">
<label for="mobile_2" class="col-sm-3 col-form-label">Mobile 2</label>
<div class="col-sm-9">
<input type="text" class="form-control form-control-sm" id="mobile_2" name="mobile_2">
</div>
</div>
</div>
<div class="modal-footer border-top-0">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Save Contact</button>
</div>
</form>
</div>
</div>
</div>
<input type='hidden' name='trainingid_delete' id='trainingid_delete' />
<div class="row g-3 align-items-end mb-3">
<div class="col-12 col-md-4">
<label for="trainingname" class="form-label border-start border-5 border-primary ps-1 mb-1">Analyst Name</label>
<select name="trainingname" id="trainingname" class="form-select form-select-sm select2 trainingname">
<option value="">-- Choose one --</option>
<?php
if(!empty($sitecontacts)) {
foreach ($sitecontacts as $data) {
$qcontactid = $data['contactid'];
$qcontactname = $data['firstname'] . " " . $data['lastname'];
// Siapkan data tambahannya
$qcontactinitial = $data['initial'];
$qcontacttitle = $data['title'];
$qcontactemail = $data['email_1'];
// Simpan di data-attribute
echo "<option value='$qcontactid' data-name='$qcontactname' data-initial='$qcontactinitial' data-title='$qcontacttitle' data-email='$qcontactemail'>$qcontactname</option>";
}
}
?>
</select>
</div>
<div class="col-12 col-md-3">
<label for="trainingdate" class="form-label mb-1">Tanggal</label>
<input type="text" name="trainingdate" id="trainingdate" class="form-control form-control-sm trainingdate"
placeholder="YYYY-MM-DD" />
</div>
<div class="col-12 col-md-2">
<div class="form-check mb-md-1">
<input class="form-check-input" type="checkbox" value="1" id="validatealltraining"
name="validatealltraining" checked>
<label class="form-check-label" for="validatealltraining" style="font-size: 0.875rem;">
Validasi Semua Peserta
</label>
</div>
</div>
<div class="col-12 col-md-3 d-grid">
<button type="button" class="btn btn-sm btn-success" onclick="addTrainingRow();">Tambah</button>
</div>
</div>
<hr />
<div class='table-responsive'>
<table class='table table-bordered table-sm' id='training_table'>
<thead>
<tr>
<th style='width:2%;'>No</th>
<th style='width:8%;'>Initial</th>
<th style='width:30%;'>Nama Analyst</th>
<th style='width:15%;'>Title</th>
<th style='width:25%;'>Email</th>
<th style='width:15%;'>Tanggal</th>
<th style='width:11%;' class='text-center'>Action</th>
</tr>
</thead>
<tbody>
<?php
// Cek apakah ada data $traininguser
if(isset($traininguser) && !empty($traininguser)):
$no = 1;
foreach ($traininguser as $tu):
?>
<tr>
<td class="text-center"><?= $no++ ?></td>
<td><?= esc($tu['initial']) ?></td>
<td>
<?= esc($tu['firstname']) ?> <?= esc($tu['lastname']) ?>
<input type="hidden" name="trainingids[]" value="<?= esc($tu['contactid']) ?>">
<input type="hidden" name="trainingnames[]" value="<?= esc($tu['firstname']) ?> <?= esc($tu['lastname']) ?>">
<input type="hidden" name="trainingdates[]" value="<?= esc($tu['issued_date']) ?>">
</td>
<td><?= esc($tu['title']) ?></td>
<td><?= esc($tu['email_1']) ?></td>
<td><?= esc($tu['issued_date']) ?></td>
<td class="text-center">
<button type="button" class="btn btn-sm btn-danger" onclick="deleteTrainingRow(this, '<?= esc($tu['contactid']) ?>')">Hapus</button>
</td>
</tr>
<?php
endforeach;
endif;
?>
</tbody>
</table>
</div>
<script>
function addTrainingRow() {
var selectEl = $('#trainingname');
var selectedOption = selectEl.find('option:selected');
var contactid = selectEl.val();
var trainingname = selectedOption.data('name');
var trainingdate = $('#trainingdate').val();
if (!contactid || !trainingdate) {
alert("Nama Analyst atau Tanggal tidak boleh kosong.");
return;
}
// Agar Tidak Double Check
var exists = false;
// Selektor ini tetap berfungsi dengan baik karena mencari awalan 'trainingids'
$("input[name^='trainingids']").each(function(){
if($(this).val() == contactid){
exists = true;
}
});
if(exists){
alert("Analyst sudah ditambahkan.");
return;
}
var initial = selectedOption.data('initial') || '-';
var title = selectedOption.data('title') || '-';
var email = selectedOption.data('email') || '-';
var rowIndex = $('#training_table tbody tr').length;
var newRow = `
<tr>
<td class="text-center">${rowIndex + 1}</td>
<td>${initial}</td>
<td>
${trainingname}
<input type="hidden" name="trainingids[]" value="${contactid}">
<input type="hidden" name="trainingnames[]" value="${trainingname}">
<input type="hidden" name="trainingdates[]" value="${trainingdate}">
</td>
<td>${title}</td>
<td>${email}</td>
<td>${trainingdate}</td>
<td class="text-center">
<button type="button" class="btn btn-sm btn-danger" onclick="deleteTrainingRow(this)">Hapus</button>
</td>
</tr>`;
$("#training_table tbody").append(newRow);
// reset input
selectEl.val('').trigger('change');
}
function deleteTrainingRow(btn, contactid) {
if(confirm('Apakah Anda yakin ingin menghapus data ini?')) {
$(btn).closest('tr').remove();
// Update nomor urut tabel otomatis untuk tampilan
$('#training_table tbody tr').each(function(index) {
$(this).find('td:first').text(index + 1);
});
// Memasukkan ID yang dihapus ke input hidden 'trainingid_delete'
if (contactid > 0) {
var d = $('#trainingid_delete');
var currentVal = d.val();
d.val(currentVal ? currentVal + ',' + contactid : contactid);
}
}
}
flatpickr(".trainingdate", { allowInput: true, dateFormat: "Y-m-d" });
$('#formCreateContact').submit(function(e) {
// 1. Cegah form melakukan reload halaman bawaan HTML
e.preventDefault();
// 2. Ambil URL action dan data dari form
var url = $(this).attr('action');
var formData = $(this).serialize();
// 3. Kirim data ke controller menggunakan AJAX POST
$.ajax({
type: "POST",
url: url,
data: formData,
success: function(response) {
// Tutup modal dan bersihkan isian formnya
$('#createContactModal').modal('hide');
$('#formCreateContact')[0].reset();
// Tampilkan pesan sukses
alert('Contact berhasil ditambahkan!');
// --- INI BAGIAN REFRESH-NYA ---
var siteid = $('#siteid').val();
var current_actid = $('#current_actid').val();
// Siapkan format url untuk current_actid (sama seperti logika sebelumnya)
if (current_actid !== '' && current_actid !== undefined) {
current_actid = '/' + current_actid;
} else {
current_actid = '';
}
// Jalankan ulang $.get milikmu khusus untuk training_form
if(siteid !== '') {
$.get("<?=base_url();?>/activities/getsitecontact/" + siteid + current_actid, function(data) {
$('#training_form').html(data);
// Re-inisialisasi select2 agar tampilannya kembali rapi
$('.select2').select2({
theme: 'bootstrap-5',
width: '100%'
});
});
}
},
error: function(xhr, status, error) {
alert('Terjadi kesalahan saat menyimpan data!');
console.error(xhr.responseText);
}
});
});
</script>

View File

@ -3,209 +3,166 @@
<?= $this->section('content') ?>
<div class="page-wrapper">
<div class="container-fluid">
<div class="row page-titles">
<div class="col-md-5 align-self-center">
<h4 class="text-themecolor">Certificates Training Management</h4>
</div>
</div>
<div class="container-fluid">
<div class="row page-titles">
<div class="col-md-5 align-self-center">
<h4 class="text-themecolor">Certificates Training Management</h4>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-body">
<div class="row mb-3">
<div class="col-12 mb-3">
<div class="input-group input-group-sm">
<span class="input-group-text"><i class="fas fa-search"></i></span>
<input type="text" id="searchInput" class="form-control"
placeholder="Search certificates by name, product, type, vendor, or dates...">
</div>
</div>
<div class="col-md-4 mb-2">
<select id="statusFilter" class="form-select form-select-sm">
<option value="">All Status</option>
<option value="active">Active</option>
<option value="expired">Expired</option>
<option value="expiring">Expiring Soon</option>
<option value="isval">Need Validation</option>
</select>
</div>
<div class="col-md-4 mb-2">
<select id="typeFilter" class="form-select form-select-sm">
<option value="">All Types</option>
<option value="tms">TMS</option>
<option value="joko">Jokoh</option>
<option value="boeki">Tokyo Boeki</option>
</select>
</div>
<div class="col-md-4 mb-2">
<button onclick="resetFilters()" class="btn btn-secondary btn-sm w-100">
<i class="fas fa-redo"></i> Reset Filters
</button>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-body">
<div class="row mb-3">
<div class="col-12 mb-3">
<div class="input-group input-group-sm">
<span class="input-group-text"><i class="fas fa-search"></i></span>
<input type="text" id="searchInput" class="form-control"
placeholder="Search certificates by name, product, type, vendor, or dates...">
</div>
</div>
<div class="col-md-4 mb-2">
<select id="productFilter" class="form-select form-select-sm">
<option value="">--Product Filter--</option>
<option value="tms">TMS</option>
<option value="jokoh">Jokoh</option>
<option value="mindray">Mindray</option>
</select>
</div>
<div class="col-md-4 mb-2">
<select id="validationFilter" class="form-select form-select-sm">
<option value="">--Validation Filter--</option>
<option value="valid">Validated</option>
<option value="unval">Unvalidated</option>
</select>
</div>
<div class="col-md-4 mb-2">
<button onclick="resetFilters()" class="btn btn-secondary btn-sm w-100">
<i class="fas fa-redo"></i> Reset Filters
</button>
</div>
</div>
<!-- TABLE -->
<div class="table-responsive" style="width: 100%;">
<table id="certificatesTable" class="table table-striped table-hover border" style="width: 100%;">
<thead class="table-primary">
<tr>
<th style="width:20%">Certificate Name</th>
<th style="width:20%">Product/Equipment</th>
<th style="width:20%">Activity Report</th>
<th style="width:10%">Issue Date</th>
<th style="width:10%">Expiry Date</th>
<th style="width:10%">Status</th>
<th class="text-center" style="width:10%">Action</th>
<th style="width: 32%">Certificate</th>
<th style="width: 38%">Act Report</th>
<th style="width: 10%">Issue Date</th>
<th style="width: 8%">Validation</th>
<th class="text-center" style="width: 12%">Action</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Validate Modal -->
<div class="modal fade" id="validateModal" tabindex="-1" aria-labelledby="validateModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-warning text-dark">
<h5 class="modal-title" id="validateModalLabel">
<i class="fa-solid fa-check-double"></i> &nbsp;Validate Certificate
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Certificate ID</label>
<p id="modalCertId" class="form-control-plaintext">-</p>
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Certificate Name</label>
<p id="modalCertName" class="form-control-plaintext">-</p>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Product/Equipment</label>
<p id="modalProductName" class="form-control-plaintext">-</p>
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Serial Number</label>
<p id="modalProductNumber" class="form-control-plaintext">-</p>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Issue Date</label>
<p id="modalIssueDate" class="form-control-plaintext">-</p>
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Expiry Date</label>
<p id="modalExpiryDate" class="form-control-plaintext">-</p>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Vendor</label>
<p id="modalVendor" class="form-control-plaintext">-</p>
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Activity Report</label>
<p id="modalExpiryDate" class="form-control-plaintext">
<a href="javascript:void(0)" class="activity-report-link text-decoration-none" data-certid="${certid}" style="color:#d43215b0;">Act ID - Nama AR - Nama User <i class="fa-solid fa-up-right-from-square"></i></a>
</p>
</div>
</div>
<div class='row mb-3'>
<label class='form-label fw-bold'>User Analyst yang ditraining</label>
<div class='table-responsive'>
<table class='table table-bordered table-sm'>
<thead>
<tr>
<th style='width:10%'>No</th>
<th style='width:45%'>Nama</th>
<th style='width:45%'>Tanggal</th>
</tr>
</thead>
<tbody id='training-table'>
<tr>
<td>1</td>
<td>Ahmad Fauzi</td>
<td>2024-01-15</td>
</tr>
<tr>
<td>2</td>
<td>Budi Santoso</td>
<td>2024-01-16</td>
</tr>
<tr>
<td>3</td>
<td>Citra Dewi</td>
<td>2024-01-17</td>
</tr>
<tr>
<td>4</td>
<td>Dian Pratama</td>
<td>2024-01-18</td>
</tr>
<tr>
<td>5</td>
<td>Eko Wijaya</td>
<td>2024-01-19</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="alert alert-warning">
<i class="fa-solid fa-info-circle"></i>
<strong>Validation Information:</strong> Please review the certificate details above before validating. Once validated, the certificate status will change from "Need Validation" to "Active".
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 mb-3">
<label class="form-label fw-bold">Certificate Preview</label>
<div class="border rounded p-2" style="height: 500px; overflow: hidden;">
<iframe id="certificatePreview" src="" style="width: 100%; height: 100%; border: none;"></iframe>
</div>
</div>
</div>
<div class="modal fade" id="validateModal" tabindex="-1" aria-labelledby="validateModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-warning text-dark" id='modalHeader'>
<h5 class="modal-title" id="validateModalLabel">
Validate Training Certificate
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row border-bottom mb-3 pb-2">
<div class="col-md-8">
<label class="form-label "><i class="fa-solid fa-certificate me-2"></i>Certificate Name</label>
<h5 id="modalCertName" class="mt-2 fw-bolder">-</h5>
</div>
<div class="col-md-4 text-md-end">
<h5 id="modalValidation">-</h5>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fa-solid fa-times"></i> Cancel
</button>
<button type="button" class="btn btn-warning text-dark" id="confirmValidateBtn">
<i class="fa-solid fa-check"></i> Validate Certificate
</button>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label "><i class="fa-solid fa-book-medical me-2"></i>Certificate Number</label>
<p id="modalCertNumber" class="form-control-plaintext border-bottom fw-bolder">-</p>
</div>
<div class="col-md-6 mb-3">
<label class="form-label "><i class="fa-solid fa-microchip me-2"></i>Product/Equipment</label>
<p id="modalProductName" class="form-control-plaintext border-bottom fw-bolder">-</p>
</div>
<div class="col-md-6 mb-3">
<label class="form-label "><i class="fa-solid fa-hashtag me-2"></i>Serial Number</label>
<p id="modalProductNumber" class="form-control-plaintext border-bottom fw-bolder">-</p>
</div>
<div class="col-md-6 mb-3">
<label class="form-label "><i class="fa-regular fa-hospital me-2"></i>Site</label>
<p id="modalSiteName" class="form-control-plaintext border-bottom fw-bolder">-</p>
</div>
<div class="col-md-6 mb-3">
<label class="form-label "><i class="fa-solid fa-calendar-check me-2"></i>Analyst Name</label>
<p id="modalAnalystName" class="form-control-plaintext border-bottom fw-bolder">-</p>
</div>
<div class="col-md-6 mb-3">
<label class="form-label "><i class="fa-solid fa-calendar-check me-2"></i>Issue Date</label>
<p id="modalIssueDate" class="form-control-plaintext border-bottom fw-bolder">-</p>
</div>
<div class="col-md-12 mb-4">
<label class="form-label "><i class="fa-solid fa-file-lines me-2"></i>Activity Report Reference</label>
<a href="javascript:void(0)" id="modalActivityLink" class="activity-report-link text-decoration-none fw-bolder" style="color:#d43215b0;">
<div class="p-2 border rounded bg-light">
<i class="fa-solid fa-up-right-from-square me-2"></i>
<span id="modalActivity">-</span>
</div>
</a>
</div>
<div class="col-md-4 mb-3">
<label class="form-label"><i class="fa-solid fa-user me-2"></i>Owner Validation</label>
<p id="modalValOwner" class="form-control-plaintext border-bottom fw-bolder">-</p>
</div>
<div class="col-md-4 mb-3">
<label class="form-label"><i class="fa-solid fa-user me-2"></i>SPV Validation</label>
<p id="modalValSpv" class="form-control-plaintext border-bottom fw-bolder">-</p>
</div>
<div class="col-md-4 mb-3">
<label class="form-label"><i class="fa-solid fa-user me-2"></i>Manager Validation</label>
<p id="modalValManager" class="form-control-plaintext border-bottom fw-bolder">-</p>
</div>
</div>
<div id='modalInfo'></div>
<div class="row">
<div class="col-md-12 mb-3">
<div class="border rounded p-2" style="height: 500px; overflow: hidden;">
<iframe id="certificatePreview" src="" style="width: 100%; height: 100%; border: none;"></iframe>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fa-solid fa-times"></i> Cancel
</button>
<button type="button" class="btn btn-warning text-dark" id="confirmValidateBtn">
<i class="fa-solid fa-check"></i> Validate Certificate
</button>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('style') ?>
<style>
#certificatesTable {
width: 100% !important;
}
#certificatesTable_wrapper {
width: 100% !important;
}
</style>
<?= $this->endSection() ?>
<?= $this->section('script') ?>
@ -213,7 +170,7 @@
$(function () {
let table = $('#certificatesTable').DataTable({
order: [[5, 'asc']],
order: [[3, 'asc']], // Order by Validation Column
pageLength: 25,
dom: '<"row"<"col-md-6"l>>rtip',
responsive: true,
@ -224,53 +181,43 @@ $(function () {
.then(response => response.json())
.then(result => {
callback({
data: result.map(cert => {
let certid = cert.certid || '';
let certname = cert.certname || '-';
let productname = cert.productname || '-';
let productnumber = cert.productnumber || '';
let issuedateRaw = cert.issuedate || '';
let expirydateRaw = cert.expirydate || '';
let vendor = cert.vendor || '-';
let isval = cert.isval || null;
data: result.map((cert, index) => {
let certid = cert.cert_id || '';
let certname = cert.cert_name || '-';
let certnumber = cert.cert_number || '';
let actid = cert.actid || '';
let issuedateRaw = cert.issued_date || '';
let status = cert.status; // Validation status
let fullname = cert.fullname;
let activity_subject = cert.activity_subject;
let issuedate = '-';
let expirydate = '-';
let statusBadge = '<span class="badge bg-warning text-dark">Need Validation</span>';
let validationBadge = '';
if (isval != null) {
if (issuedateRaw && issuedateRaw !== '0000-00-00') {
issuedate = new Date(issuedateRaw).toLocaleDateString('en-US', { month: 'short', day: '2-digit', year: 'numeric' });
}
if (issuedateRaw && issuedateRaw !== '0000-00-00') {
let date = new Date(issuedateRaw);
let day = String(date.getDate()).padStart(2, '0');
let month = date.toLocaleString('en-US', { month: 'short' });
let year = date.getFullYear();
issuedate = `${day} ${month} ${year}`;
}
if (expirydateRaw && expirydateRaw !== '0000-00-00') {
expirydate = new Date(expirydateRaw).toLocaleDateString('en-US', { month: 'short', day: '2-digit', year: 'numeric' });
let today = new Date();
let expiryDate = new Date(expirydateRaw);
let days = Math.ceil((expiryDate - today) / (1000 * 60 * 60 * 24));
if (days < 0) {
statusBadge = '<span class="badge bg-danger">Expired</span>';
} else if (days <= 30) {
statusBadge = '<span class="badge bg-info">Expiring Soon</span>';
} else {
statusBadge = '<span class="badge bg-success">Active</span>';
}
} else {
statusBadge = '<span class="badge bg-secondary">N/A</span>';
}
if (status == 'unvalidated') {
validationBadge = '<div class=""><span class="badge bg-warning text-dark">unvalidated</span></div>';
} else {
validationBadge = '<div class=""><span class="badge bg-success">validated</span></div>';
}
return [
`<strong>${certname}</strong><br><small class="text-muted">ID: ${certid}</small>`,
`${productname}${productnumber ? '<br><small class="text-muted">SN: ' + productnumber + '</small>' : ''}`,
`<a href="javascript:void(0)" class="activity-report-link text-decoration-none" data-certid="${certid}" style="color:#d43215b0;">Act ID - Nama AR - Nama User <i class="fa-solid fa-up-right-from-square"></i></a>`,
`<strong>${certname}</strong><br><small class="text-muted">Cert# : ${certnumber}</small>`,
`<a href="javascript:void(0)" class="activity-report-link text-decoration-none" data-certid="${certid}" data-actid="${actid}" style="color:#d43215b0;">#${actid} - ${activity_subject} &nbsp;<i class="fa-solid fa-up-right-from-square"></i></strong><br><small class="text-muted">Owner : ${fullname}</small></a>`,
issuedate,
expirydate,
statusBadge,
isval == null
? `<div class="text-center"><button type="button" class="btn btn-warning text-dark btn-validate" data-certid="${certid}" data-certname="${certname}" data-productname="${productname}" data-productnumber="${productnumber}" data-issuedate="${issuedate}" data-expirydate="${expirydate}" data-vendor="${vendor}"><i class="fa-solid fa-check-double"></i></button></div>`
: `<div class="text-center"><button type="button" class="btn btn-success btn-view" data-certid="${certid}"><i class="fa-regular fa-file-pdf"></i></button></div>`
validationBadge,
status == 'unvalidated'
? `<div class="text-center"><button type="button" class="btn btn-sm btn-warning text-dark btn-validate-modal" data-certid="${certid}"><i class="fa-solid fa-triangle-exclamation me-2"></i>Need Validation</button></div>`
: `<div class="text-center mb-1"><button type="button" class="btn btn-sm btn-info btn-validate-modal" data-certid="${certid}"><i class="fa-regular fa-eye me-2"></i>Detail</button></div>
<div class="text-center"><button type="button" class="btn btn-sm btn-success btn-view" data-certnumber="${certnumber}"><i class="fa-regular fa-file-pdf me-2"></i>Generated PDF</button></div>`
];
})
});
@ -280,19 +227,26 @@ $(function () {
callback({ data: [] });
});
},
columnDefs: [{
targets: 5,
render: function (data, type) {
if (type === 'sort') {
if (data.includes('Need Validation')) return 1;
if (data.includes('Expired')) return 2;
if (data.includes('Expiring Soon')) return 3;
if (data.includes('Active')) return 4;
return 5;
columnDefs: [
{
// Kondisi untuk Kolom 3 (Validation)
targets: 3,
render: function (data, type) {
if (type === 'sort') {
let val = data.toLowerCase();
if (val.includes('unvalidated')) return 1;
if (val.includes('validated')) return 2;
return 3;
}
return data;
}
return data;
},
{
// Kondisi untuk Kolom 4 (Action)
targets: [4],
orderable: false
}
}]
]
});
$('#certificatesTable_filter').hide();
@ -303,70 +257,111 @@ $(function () {
});
// Type filter using DataTables column filter
$('#typeFilter').on('change', function () {
$('#productFilter').on('change', function () {
let type = $(this).val();
// Filter by type column (index 1 - Certificate Name contains type info)
if (type === '') {
table.column(1).search('').draw();
table.column(0).search('').draw();
} else {
// Capitalize first letter for search
let typeText = type.charAt(0).toUpperCase() + type.slice(1);
table.column(1).search(typeText).draw();
table.column(0).search(typeText).draw();
}
});
// Status filter
$('#statusFilter').on('change', function () {
let map = {
active: 'Active',
expired: 'Expired',
expiring: 'Expiring Soon',
isval: 'Need Validation'
};
table.column(5).search(map[this.value] || '').draw();
$('#validationFilter').on('change', function () {
let validation = $(this).val();
if (validation === '') {
table.column(3).search('').draw();
} else if (validation === 'unval') {
table.column(3).search('unv').draw();
} else if (validation === 'valid') {
table.column(3).search('^validated$', true, false).draw();
}
});
// Reset
window.resetFilters = function () {
$('#searchInput, #statusFilter').val('');
table.search('').columns().search('').order([5,'asc']).draw();
$('#searchInput, #productFilter, #validationFilter').val('');
table.search('').columns().search('').draw();
};
// View PDF
$(document).on('click', '.btn-view', function () {
let certid = $(this).data('certid');
window.open('<?= base_url('certificates/training/show/') ?>' + certid, '_blank');
let certnumber = $(this).data('certnumber');
window.open('<?= base_url('certificates/number/') ?>' + certnumber, '_blank');
});
// Activity report
$(document).on('click', '.activity-report-link', function () {
let certid = $(this).data('certid');
let actid = $(this).data('actid');
window.open(
'<?= base_url('certificates/training/activity/') ?>' + certid,
'<?= base_url('activities/detail/') ?>' + actid,
'_blank',
'width=1200,height=800,scrollbars=yes,resizable=yes'
);
});
// Open modal
$(document).on('click', '.btn-validate', function () {
$(document).on('click', '.btn-validate-modal', function () {
let btn = $(this);
let certid = btn.data('certid');
$('#modalCertId').text(btn.data('certid'));
$('#modalCertName').text(btn.data('certname'));
$('#modalProductName').text(btn.data('productname'));
$('#modalProductNumber').text(btn.data('productnumber'));
$('#modalIssueDate').text(btn.data('issuedate'));
$('#modalExpiryDate').text(btn.data('expirydate'));
$('#modalVendor').text(btn.data('vendor'));
// POST API call to fetch certificate data
$.post(
'<?= base_url('certificates/api/showtraining') ?>',
{ certid },
function (data) {
$('#confirmValidateBtn').data('certid', btn.data('certid'));
$('#certificatePreview').attr(
'src',
'<?= base_url('certificates/training/show/') ?>' + btn.data('certid')
);
// console.log(data);
$('#modalCertName').text(data.cert_name || '-');
$('#modalCertNumber').text(data.cert_number || '-');
$('#modalProductName').text(data.productname || '-');
$('#modalProductNumber').text(data.productnumber || '-');
$('#modalIssueDate').text(data.issued_date || '-');
$('#modalAnalystName').text(data.fullname || '-');
$('#modalSiteName').text(data.sitename || '-');
// Cek status validasi
const isValid = data.status === 'validated';
const theme = isValid ? 'success' : 'warning';
const icon = isValid ? 'fa-regular fa-circle-check' : 'fa-solid fa-triangle-exclamation';
const note = isValid
? 'Sertifikat Sudah Divalidasi'
: 'Review data berikut dengan teliti, setelah divalidasi maka sertifikat akan dinyatakan <strong>Valid</strong> dan <strong>sah secara sistem.</strong>';
$('#modalHeader').removeClass('bg-warning bg-success').addClass(`bg-${theme} text-dark`);
$('#modalValidation').html(
`<span class="badge bg-${theme} text-dark py-2 px-3">
<i class="${icon} me-2"></i>${data.status || '-'}
</span>`
);
$('#modalInfo').html(
`<div class="alert alert-${theme} border-0 shadow-sm d-flex align-items-center">
<i class="${icon} fs-4 me-3"></i>
<div>
<strong>Validation Note:</strong><br>
${note}
</div>
</div>`
);
$('#modalActivity').text(data.actid ? `#${data.actid} - ${data.subject || '-'}` : '-');
$('#modalActivityLink').attr('data-actid', data.actid || '');
$('#modalValOwner').html(data.user_validation_at ? `${data.username} - ${data.user_validation_at}<i class="fa-solid fa-circle-check text-success ms-2"></i>` : '-');
$('#modalValSpv').html(data.spv_validation_at ? `${data.spvname} - ${data.spv_validation_at}<i class="fa-solid fa-circle-check text-success ms-2"></i>` : '-');
$('#modalValManager').html(data.manager_validation_at ? `${data.managername} - ${data.manager_validation_at}<i class="fa-solid fa-circle-check text-success ms-2"></i>` : '-');
},
'json'
).fail(function (xhr) {
console.log(xhr);
alert(xhr.responseText);
});
// INI JANGAN DIUBAH
$('#confirmValidateBtn').data('certid', certid);
$('#certificatePreview').attr('src','<?= base_url('certificates/training/show/') ?>' + certid);
$('#validateModal').modal('show');
});
@ -379,63 +374,25 @@ $(function () {
if (!confirm('Are you sure?')) return;
$.post(
'<?= base_url('certificates/api/validateCertificate') ?>',
'<?= base_url('certificates/api/validatecertificate') ?>',
{ certid, certificateType},
function (response) {
if (response.success) {
$('#validateModal').modal('hide');
alert(response.message);
// Generate and save PDF after successful validation
$.post(
'<?= base_url('certificates/api/generatepdf') ?>',
{ certid, certificateType },
function (pdfResponse) {
if (pdfResponse.success) {
alert('PDF generated and saved successfully!');
location.reload();
} else {
alert('Validation successful but PDF generation failed: ' + (pdfResponse.message || 'Unknown error'));
location.reload();
}
},
'json'
).fail(function () {
alert('Validation successful but failed to generate PDF');
location.reload();
});
location.reload();
} else {
alert(response.message || 'Validation failed');
}
}, 'json'
).fail(function () {
$('#validateModal').modal('hide');
alert('Server error.');
).fail(function (xhr) {
console.log(xhr);
alert(xhr.responseText);
});
});
// Delete training user
window.deleteTrainingUser = function(btn) {
if(confirm('Are you sure you want to delete this user?')) {
var row = btn.closest('tr');
row.remove();
updateRowNumbers();
}
};
// Update row numbers after deletion
function updateRowNumbers() {
var tbody = $('#training-table');
var rows = tbody.find('tr');
rows.each(function(index) {
$(this).find('td:first').text(index + 1);
});
}
});
</script>
<?= $this->endSection() ?>

View File

@ -2,14 +2,15 @@
<html>
<head>
<meta charset="utf-8">
<title>Maintenance Certificate - <?= $certificate['certname'] ?></title>
<title><?= $certificate['certname'] ?></title>
<style>
@page {
margin: 0;
padding: 0;
}
body {
font-family: Arial;
/* font-family: Arial; */
font-family: Arial, Helvetica, sans-serif;
margin: 0;
padding: 0;
/* font-size: 12px; */
@ -42,6 +43,18 @@
color:#336600;
}
h2 {
margin :0;
padding:0;
color:#336600;
}
.instrument-font {
margin :0;
padding:0;
color:black;
}
.container {
max-width: 100%;
margin: 0;
@ -51,16 +64,17 @@
text-align: center;
margin: 0;
padding: 0;
margin-top: 230px;
margin-bottom: 25px;
margin-top: 235px;
margin-bottom: 20px;
}
.site-name {
text-align: center;
margin-bottom: 55px;
margin-bottom: 40px;
}
.detail-information {
text-align: center;
margin-bottom: 67px;
/* margin-bottom: 137px; */
margin-bottom: 26px;
}
h4 {
margin :0;
@ -73,12 +87,11 @@
.signature-table {
width: 100%;
border-collapse: collapse;
margin-left: 17%;
margin-bottom: 0px;
margin-bottom: 20px;
}
.signature-table td {
width: 50%; /* Membagi dua sisi sama rata */
text-align: left;
text-align: center;
vertical-align: top;
}
.name {
@ -98,57 +111,104 @@
</style>
</head>
<body>
<!-- Untuk DEV -->
<!-- <div class="bg-container">
<img src="<?=base_url();?>/assets/images/background_certificate/maintenance.jpeg">
</div> -->
<?php for($i=0; $i<$count; $i++) : ?>
<!-- Untuk PROD -->
<div class="bg-container">
<?php $bgPath = FCPATH . 'assets/images/background_certificate/maintenance.jpeg'; ?>
<img src="<?php echo($bgPath) ?>">
</div>
<div class="container">
<div class="instument-name">
<h1> &lt;&lt;Nama Analis&gt;&gt; </h1>
<h1><?= $certificate['fullname'] ?><?= $certificate['title'] ?></h1>
</div>
<div class="site-name">
<h1> at &lt;&lt;Nama Rumah Sakit / Laboratorium - Kota&gt;&gt; </h1>
<h2> <?= $certificate['sitename'] ?> ~ <?= $certificate['city'] ?></h2>
</div>
<div class="detail-information">
<h4>In Recording of Participation and Successful</h4>
<h4>Completion of Mindray Hematology Analyzer Training</h4>
<h2>BC5140</h2>
<h3>Date : February 05, 2026</h3>
<!-- <h4>Serial Number: <?= $certificate['productnumber'] ?></h4> -->
<!-- <h4>has completed through a series of <?= $certificate['certtype'] ?></h4> -->
<h3>In Recording of Participation and Successful Completion of</h3>
<h3>User Training</h3>
<h2 class='instrument-font'><?= $certificate['productname'] ?></h2>
<h3>Date: <?= $certificate['issueddate'] ?></h3>
</div>
<table class="signature-table">
<tr>
<td>
<span class="name"><br></span>
<span class="name"><br></span>
<span class="name"><br></span>
<span class="name"><br></span>
<img src="<?= $certificate['qrcode'] ?>">
</td>
<!-- <td>
<span class="name">Adhitya Pranata Putra</span>
<span class="position">Technical Support Manager</span>
</td>
</td> -->
<td>
<span class="position">Trainer,</span>
<span class="name"><br></span>
<span class="name"><br></span>
<span class="name"><br></span>
<span class="name">&lt;&lt;Nama TSO&gt;&gt;</span>
<span class="position">&lt;&lt;Jabatan&gt;&gt;</span>
</td>
<!-- <td>
<span class="name"></span>
<span class="position"></span>
</td> -->
</tr>
</table>
<div class="fo-wrapper">
<p>&lt;&lt;FO&gt;&gt;</p>
<p>FO.III.12/14.00/2020</p>
</div>
</div>
<div class="bg-container">
<img src="<?=base_url();?>/assets/images/background_certificate/maintenance.jpeg">
<!-- Untuk Tanda Export PDF atau tidak -->
<?php if (!isset($certificate['exportToPDF'])) : ?>
<div class="container">
<div class="instument-name">
<h1><?= $certificate['fullname'] ?><?= $certificate['title'] ?></h1>
</div>
<div class="site-name">
<h2> <?= $certificate['sitename'] ?> ~ <?= $certificate['city'] ?></h2>
</div>
<div class="detail-information">
<!-- <h4>Serial Number: <?= $certificate['productnumber'] ?></h4> -->
<!-- <h4>has completed through a series of <?= $certificate['certtype'] ?></h4> -->
<h3>In Recording of Participation and Successful Completion of</h3>
<h3>User Training</h3>
<h2 class='instrument-font'><?= $certificate['productname'] ?></h2>
<h3>Date: <?= $certificate['issueddate'] ?></h3>
</div>
<table class="signature-table">
<tr>
<td>
<img src="<?= $certificate['qrcode'] ?>">
</td>
<!-- <td>
<span class="name">Adhitya Pranata Putra</span>
<span class="position">Technical Support Manager</span>
</td> -->
<!-- <td>
<span class="name"></span>
<span class="position"></span>
</td> -->
</tr>
</table>
<div class="fo-wrapper">
<p>FO.III.12/14.00/2020</p>
</div>
</div>
<?php endfor ?>
<?php endif ?>
</body>
</html>

View File

@ -42,11 +42,15 @@
<ul aria-expanded="false" class="collapse">
<li><a href="<?=base_url();?>certificates/installation">Installation</a></li>
<li><a href="<?=base_url();?>certificates/maintenance">Maintenance</a></li>
<!-- <li><a href="<?=base_url();?>certificates/training">Training</a></li> -->
<li><a href="<?=base_url();?>certificates/training">Training</a></li>
<!-- <li><a href="<?=base_url();?>certificates/calibration">Callibration</a></li> -->
<!-- <li><a href="<?=base_url();?>certificates/official-report">Official Report</a></li> -->
</ul>
</li>
<?php if ( !($isTSM || $isPS) ): ?>
<li> <a class="waves-effect waves-dark" href='<?=base_url();?>contacts' aria-expanded="false"> <i class="fa-solid fa-address-book"></i><span class="hide-menu">Contact</span></a> </li>
<?php endif ?>
<li> <a class="waves-effect waves-dark" href='<?=base_url();?>guidebook' aria-expanded="false"> <i class="fa-solid fa-book"></i><span class='hide-menu'>Guidebook</span> </a> </li>
<li> <a class="waves-effect waves-dark" href='https://clqms.services-summit.my.id/' aria-expanded="false"> <i class="fa-solid fa-microscope"></i><span class='hide-menu'>CLQMS</span> </a> </li>