forked from mahdahar/crm-summit
Update Fitur CRUD Sertifikat Maintenance
This commit is contained in:
parent
252d30d040
commit
8717310ebb
@ -76,7 +76,7 @@ class Filters extends BaseFilters
|
|||||||
'before' => [
|
'before' => [
|
||||||
// 'cors',
|
// 'cors',
|
||||||
'auth' => [ 'except' => [
|
'auth' => [ 'except' => [
|
||||||
'auth/*', 'lqms/*', 'key/*', 'api/*'
|
'auth/*', 'lqms/*', 'key/*', 'api/*', 'certificates/number/*'
|
||||||
]]
|
]]
|
||||||
// 'honeypot',
|
// 'honeypot',
|
||||||
// 'csrf',
|
// 'csrf',
|
||||||
|
|||||||
@ -211,24 +211,30 @@ $routes->group('certificates', function($routes) {
|
|||||||
|
|
||||||
// Untuk Index Tiap Menu
|
// Untuk Index Tiap Menu
|
||||||
$routes->get('maintenance', 'Certificates::maintenanceIndex'); // OK
|
$routes->get('maintenance', 'Certificates::maintenanceIndex'); // OK
|
||||||
$routes->get('installation', 'Certificates::installationIndex');
|
// $routes->get('installation', 'Certificates::installationIndex'); // OK
|
||||||
$routes->get('training', 'Certificates::trainingIndex');
|
// $routes->get('training', 'Certificates::trainingIndex'); // OK
|
||||||
$routes->get('calibration', 'Certificates::calibrateIndex');
|
// $routes->get('calibration', 'Certificates::calibrateIndex'); // OK
|
||||||
|
|
||||||
|
|
||||||
|
// Untuk Get API
|
||||||
$routes->get('api/getindexmaintenance', 'Certificates::getDataIndexMaintenance'); // OK
|
$routes->get('api/getindexmaintenance', 'Certificates::getDataIndexMaintenance'); // OK
|
||||||
$routes->get('api/getindextraining', 'Certificates::getDataIndexTraining');
|
// $routes->get('api/getindexinstallation', 'Certificates::getDataIndexInstallation'); // OK
|
||||||
|
// $routes->get('api/getindextraining', 'Certificates::getDataIndexTraining'); // OK
|
||||||
|
// $routes->get('api/getindexcalibrate', 'Certificates::getDataIndexCalibrate');
|
||||||
|
|
||||||
|
$routes->post('api/showmaintenance', 'Certificates::showDataMaintenance'); // OK
|
||||||
|
|
||||||
$routes->post('api/validateCertificate', 'Certificates::validateCertificate'); // OK
|
// Untuk Preview Cerificate
|
||||||
$routes->post('api/generatepdf', 'Certificates::generatePdf'); // OK
|
|
||||||
|
|
||||||
|
|
||||||
// Untuk View Cerificate
|
|
||||||
$routes->get('maintenance/show/(:any)', 'Certificates::createMaintenancePreview/$1'); // OK
|
$routes->get('maintenance/show/(:any)', 'Certificates::createMaintenancePreview/$1'); // OK
|
||||||
$routes->get('training/show/(:any)', 'Certificates::createTraining/$1');
|
// $routes->get('installation/show/(:any)', 'Certificates::createInstallationPreview/$1'); // OK
|
||||||
$routes->get('calibration/show/(:any)/(:any)', 'Certificates::createCalibrate/$1/$2');
|
// $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
|
||||||
|
|
||||||
|
$routes->get('maintenance/number/$1', 'Certificates::getMaintenanceCertificate'); // OK
|
||||||
|
|
||||||
|
$routes->get('number/(:segment)', 'Certificates::view/$1');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
|
use App\Models\CertificateModel;
|
||||||
use App\Models\ActivitiesModel;
|
use App\Models\ActivitiesModel;
|
||||||
use App\Models\ActdetailModel;
|
use App\Models\ActdetailModel;
|
||||||
use App\Models\InvTransModel;
|
use App\Models\InvTransModel;
|
||||||
@ -441,7 +442,6 @@ class Activities extends Controller {
|
|||||||
$sql = "INSERT INTO actstatus_log (activityid, activitystatus, userid, logdate)
|
$sql = "INSERT INTO actstatus_log (activityid, activitystatus, userid, logdate)
|
||||||
VALUES ($actid,'$actstatus', $userid, NOW())";
|
VALUES ($actid,'$actstatus', $userid, NOW())";
|
||||||
$query = $db->query($sql);
|
$query = $db->query($sql);
|
||||||
|
|
||||||
$acttextid = $data['new_value']['acttextid'];
|
$acttextid = $data['new_value']['acttextid'];
|
||||||
$textvalue = $data['new_value']['textvalue'];
|
$textvalue = $data['new_value']['textvalue'];
|
||||||
foreach($data['new_value']['acttextid'] as $qindex => $qacttextid) {
|
foreach($data['new_value']['acttextid'] as $qindex => $qacttextid) {
|
||||||
@ -449,6 +449,33 @@ class Activities extends Controller {
|
|||||||
$sql = "insert into actdetail (actid, acttextid, textvalue, createdate) values ('$actid','$qacttextid', '$qtextvalue', NOW())";
|
$sql = "insert into actdetail (actid, acttextid, textvalue, createdate) values ('$actid','$qacttextid', '$qtextvalue', NOW())";
|
||||||
$query = $db->query($sql);
|
$query = $db->query($sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UNTUK CERTIFICATES
|
||||||
|
$sql = "SELECT prl.productaliastext as productname, pr.productnumber as snnumber, st.sitename
|
||||||
|
FROM `activities` act
|
||||||
|
LEFT JOIN sites st ON st.siteid = act.siteid
|
||||||
|
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();
|
||||||
|
if ($this->request->getVar('maintenance')) { // Maintenance
|
||||||
|
$issuedDate = $data['new_value']['closedate'] ?? null;
|
||||||
|
$expiredDate = date('Y-m-d', strtotime($issuedDate . ' + 6 months'));
|
||||||
|
$insertCert = [
|
||||||
|
'cert_name' => "MC_" . ($result['productname'] ?? 'UNKNOWN') . "_" . ($result['snnumber'] ?? '0') . "_" . $actid,
|
||||||
|
'cert_type' => "MC",
|
||||||
|
'actid' => $actid,
|
||||||
|
'issued_date' => $issuedDate,
|
||||||
|
'expired_date' => $expiredDate,
|
||||||
|
'user_id' => $data['new_value']['userid_owner']
|
||||||
|
];
|
||||||
|
$certificateModel = new CertificateModel();
|
||||||
|
$certificateModel->insert($insertCert);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// update edit
|
// update edit
|
||||||
$activitiesModel = new ActivitiesModel();
|
$activitiesModel = new ActivitiesModel();
|
||||||
|
|||||||
@ -6,6 +6,8 @@ use App\Controllers\BaseController;
|
|||||||
use Dompdf\Dompdf;
|
use Dompdf\Dompdf;
|
||||||
use Dompdf\Options;
|
use Dompdf\Options;
|
||||||
|
|
||||||
|
use App\Models\CertificateModel;
|
||||||
|
|
||||||
class Certificates extends BaseController {
|
class Certificates extends BaseController {
|
||||||
|
|
||||||
protected array $data;
|
protected array $data;
|
||||||
@ -16,17 +18,6 @@ class Certificates extends BaseController {
|
|||||||
}
|
}
|
||||||
public function getDataIndexInstallation() { // Untuk API Get Data
|
public function getDataIndexInstallation() { // Untuk API Get Data
|
||||||
// $actid = $this->request->getVar('actid'); Siapa Tahu Buat
|
// $actid = $this->request->getVar('actid'); Siapa Tahu Buat
|
||||||
}
|
|
||||||
public function createinstallationPreview($certid = null) { // Untuk Preview Sertifikat
|
|
||||||
//Melakukan search data dari database
|
|
||||||
}
|
|
||||||
|
|
||||||
// Untuk Sertifikat Maintenance [2]
|
|
||||||
public function maintenanceIndex() { // Index
|
|
||||||
return view('certificate_maintenance_index');
|
|
||||||
}
|
|
||||||
public function getDataIndexMaintenance() { // Untuk API Get Data
|
|
||||||
// $actid = $this->request->getVar('actid'); Siapa Tahu Buat
|
|
||||||
|
|
||||||
$certificates = [
|
$certificates = [
|
||||||
[
|
[
|
||||||
@ -45,10 +36,30 @@ class Certificates extends BaseController {
|
|||||||
'productname' => 'GE Healthcare VIVID',
|
'productname' => 'GE Healthcare VIVID',
|
||||||
'productnumber' => 'GE-VIV-Q992',
|
'productnumber' => 'GE-VIV-Q992',
|
||||||
'issuedate' => '2024-06-12',
|
'issuedate' => '2024-06-12',
|
||||||
'expirydate' => '2026-10-01',
|
'expirydate' => '2026-03-01',
|
||||||
'vendor' => 'Pramita Medika Service',
|
'vendor' => 'Pramita Medika Service',
|
||||||
'isval' => '2026-03-01'
|
'isval' => '2026-03-01'
|
||||||
]
|
],
|
||||||
|
[
|
||||||
|
'certid' => 'f353ca91-4fc5-49f2-9b9e-304f83d11915',
|
||||||
|
'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-09-01'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'certid' => 'f353ca91-4fc5-49f2-9b9e-304f83d11915',
|
||||||
|
'certname' => 'Electrical Safety Test',
|
||||||
|
'productname' => 'GE Healthcare VIVID',
|
||||||
|
'productnumber' => 'GE-VIV-Q992',
|
||||||
|
'issuedate' => '2024-06-12',
|
||||||
|
'expirydate' => '2026-01-01',
|
||||||
|
'vendor' => 'Pramita Medika Service',
|
||||||
|
'isval' => '2026-09-01'
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
// If no actid, return all certificates
|
// If no actid, return all certificates
|
||||||
@ -58,7 +69,7 @@ class Certificates extends BaseController {
|
|||||||
|
|
||||||
return $this->response->setJSON($certificates);
|
return $this->response->setJSON($certificates);
|
||||||
}
|
}
|
||||||
public function createMaintenancePreview($certid = null) { // Untuk Preview Sertifikat
|
public function createinstallationPreview($certid = null) { // Untuk Preview Sertifikat
|
||||||
//Melakukan search data dari database
|
//Melakukan search data dari database
|
||||||
|
|
||||||
if (!$certid) {
|
if (!$certid) {
|
||||||
@ -81,9 +92,167 @@ class Certificates extends BaseController {
|
|||||||
return $this->response->setStatusCode(404)->setJSON(['error' => 'Maintenance certificate not found']);
|
return $this->response->setStatusCode(404)->setJSON(['error' => 'Maintenance certificate not found']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $this->previewPdf($certificate, 'installation'); // Preview PDF
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Untuk Sertifikat Maintenance [2]
|
||||||
|
public function maintenanceIndex() { // Index
|
||||||
|
return view('certificate_maintenance_index');
|
||||||
|
}
|
||||||
|
public function getDataIndexMaintenance() {
|
||||||
|
$userPosId = session()->get('userposid');
|
||||||
|
$userId = session()->get('userid');
|
||||||
|
|
||||||
|
$certificateModel = new CertificateModel();
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
|
||||||
|
// 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([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 showDataMaintenance() { // 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 "Before After Inspection"
|
||||||
|
WHEN certificates.cert_type = "BAP" THEN "Berita Acara Pengerjaan"
|
||||||
|
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
|
||||||
|
', 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')
|
||||||
|
->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 createMaintenancePreview($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,
|
||||||
|
productcatalog.productname 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 "User Training"
|
||||||
|
WHEN certificates.cert_type = "BAI" THEN "Before After Inspection"
|
||||||
|
WHEN certificates.cert_type = "BAP" THEN "Berita Acara Pengerjaan"
|
||||||
|
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')
|
||||||
|
->where('certificates.cert_id', $certid)
|
||||||
|
->first();
|
||||||
|
$certificate = [
|
||||||
|
'certname' => $data['cert_name'],
|
||||||
|
'sitename' => $data['sitename'],
|
||||||
|
'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 (empty($certificate)) { // Jika Tidak Ada
|
||||||
|
return $this->response->setStatusCode(404)->setJSON(['error' => 'Maintenance certificate not found']);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->previewPdf($certificate, 'maintenance'); // Preview PDF
|
return $this->previewPdf($certificate, 'maintenance'); // Preview PDF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Untuk Sertifikat Training [3]
|
// Untuk Sertifikat Training [3]
|
||||||
public function trainingIndex() {
|
public function trainingIndex() {
|
||||||
return view('certificate_training_index');
|
return view('certificate_training_index');
|
||||||
@ -94,40 +263,24 @@ class Certificates extends BaseController {
|
|||||||
// Sample data - replace with actual database query
|
// Sample data - replace with actual database query
|
||||||
$certificates = [
|
$certificates = [
|
||||||
[
|
[
|
||||||
'certid' => 'CERT001',
|
'certid' => 'f353ca91-4fc5-49f2-9b9e-304f83d11919',
|
||||||
'certname' => 'Jokoh Calibration Certificate',
|
'certname' => 'Jokoh Calibration Certificate',
|
||||||
'productname' => 'Jokoh',
|
'productname' => 'Jokoh',
|
||||||
'productnumber' => 'SN-2024-001',
|
'productnumber' => 'SN-2024-001',
|
||||||
'issuedate' => '2024-01-15',
|
'issuedate' => '2024-01-15',
|
||||||
'expirydate' => '2025-01-15',
|
'expirydate' => '2025-01-15',
|
||||||
'vendor' => 'Summit Calibration Lab',
|
'vendor' => 'Summit Calibration Lab',
|
||||||
'type' => 'joko',
|
'isval' => null
|
||||||
'sitename' => 'Jakarta Office',
|
|
||||||
'siteid' => 'SITE001'
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'certid' => 'CERT002',
|
'certid' => 'f353ca91-4fc5-49f2-9b9e-304f83d11919',
|
||||||
'certname' => 'TMS50i Calibration Certificate',
|
'certname' => 'Electrical Safety Test',
|
||||||
'productname' => 'TMS50i',
|
'productname' => 'GE Healthcare VIVID',
|
||||||
'productnumber' => 'SN-2024-002',
|
'productnumber' => 'GE-VIV-Q992',
|
||||||
'issuedate' => '2024-02-15',
|
'issuedate' => '2024-06-12',
|
||||||
'expirydate' => '2025-02-15',
|
'expirydate' => '2026-10-01',
|
||||||
'vendor' => 'Summit Calibration Lab',
|
'vendor' => 'Pramita Medika Service',
|
||||||
'type' => 'tms',
|
'isval' => '2026-03-01'
|
||||||
'sitename' => 'Surabaya Office',
|
|
||||||
'siteid' => 'SITE002'
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'certid' => 'CERT003',
|
|
||||||
'certname' => 'Tokyo Boeki Calibration Certificate',
|
|
||||||
'productname' => 'Tokyo Boeki',
|
|
||||||
'productnumber' => 'SN-2024-003',
|
|
||||||
'issuedate' => '2024-03-15',
|
|
||||||
'expirydate' => '2025-03-15',
|
|
||||||
'vendor' => 'Summit Calibration Lab',
|
|
||||||
'type' => 'boeki',
|
|
||||||
'sitename' => 'Bandung Office',
|
|
||||||
'siteid' => 'SITE003'
|
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -138,15 +291,100 @@ class Certificates extends BaseController {
|
|||||||
|
|
||||||
return $this->response->setJSON($certificates);
|
return $this->response->setJSON($certificates);
|
||||||
}
|
}
|
||||||
|
public function createTrainingPreview($certid = null) { // Untuk Preview Sertifikat
|
||||||
|
//Melakukan search data dari database
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Untuk Sertifikat Calibrate [4]
|
// Untuk Sertifikat Calibrate [4]
|
||||||
public function calibrateIndex() {
|
public function calibrateIndex() {
|
||||||
return view('certificate_calibration_index');
|
return view('certificate_calibrate_index');
|
||||||
|
}
|
||||||
|
public function getDataIndexCalibrate() {
|
||||||
|
// $actid = $this->request->getVar('actid');
|
||||||
|
|
||||||
|
// 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'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
// If no actid, return all certificates
|
||||||
|
if (empty($certificates)) {
|
||||||
|
return $this->response->setJSON(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->response->setJSON($certificates);
|
||||||
|
}
|
||||||
|
public function createCalibratePreview($certid = null) { // Untuk Preview Sertifikat
|
||||||
|
//Melakukan search data dari database
|
||||||
|
|
||||||
|
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, 'calibrate', 'tms24i'); // Preview PDF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Helper Function Preview dan Validate
|
// Helper Function Preview dan Validate
|
||||||
private function previewPdf($certificate, $type, $productType=null) { // Untuk Show/Preview PDF
|
private function previewPdf($certificate, $type) { // Untuk Show/Preview PDF
|
||||||
// Generate PDF
|
// Generate PDF
|
||||||
$options = new Options();
|
$options = new Options();
|
||||||
$options->set('isRemoteEnabled', true);
|
$options->set('isRemoteEnabled', true);
|
||||||
@ -156,27 +394,11 @@ class Certificates extends BaseController {
|
|||||||
$dompdf = new Dompdf($options);
|
$dompdf = new Dompdf($options);
|
||||||
|
|
||||||
// Format dates
|
// Format dates
|
||||||
$issuedate = date('F d, Y', strtotime($certificate['issuedate']));
|
$certificate['issueddate'] = date('d-M-Y', strtotime($certificate['issueddate']));
|
||||||
$expirydate = date('F d, Y', strtotime($certificate['expirydate']));
|
$certificate['expireddate'] = date('d-M-Y', strtotime($certificate['expireddate']));
|
||||||
|
|
||||||
// Get status
|
|
||||||
$today = date('Y-m-d');
|
|
||||||
$expiryCheck = date('Y-m-d', strtotime($certificate['expirydate']));
|
|
||||||
$daysUntilExpiry = (strtotime($expiryCheck) - strtotime($today)) / (60 * 60 * 24);
|
|
||||||
|
|
||||||
if ($daysUntilExpiry < 0) {
|
|
||||||
$status = 'Expired';
|
|
||||||
$statusColor = '#dc3545';
|
|
||||||
} elseif ($daysUntilExpiry <= 30) {
|
|
||||||
$status = 'Expiring Soon';
|
|
||||||
$statusColor = '#ffc107';
|
|
||||||
} else {
|
|
||||||
$status = 'Active';
|
|
||||||
$statusColor = '#28a745';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select template and orientation based on type
|
// Select template and orientation based on type
|
||||||
$template = 'certificate_pdf';
|
$template = '';
|
||||||
$orientation = 'portrait';
|
$orientation = 'portrait';
|
||||||
|
|
||||||
switch($type) {
|
switch($type) {
|
||||||
@ -210,15 +432,13 @@ class Certificates extends BaseController {
|
|||||||
$template = 'certificates/certificate_maintenance';
|
$template = 'certificates/certificate_maintenance';
|
||||||
$orientation = 'landscape';
|
$orientation = 'landscape';
|
||||||
break;
|
break;
|
||||||
|
case 'installation':
|
||||||
|
$template = 'certificates/certificate_installation';
|
||||||
|
$orientation = 'landscape';
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$html = view($template, [
|
$html = view($template, [ 'certificate' => $certificate]);
|
||||||
'certificate' => $certificate,
|
|
||||||
'issuedate' => $issuedate,
|
|
||||||
'expirydate' => $expirydate,
|
|
||||||
'status' => $status,
|
|
||||||
'statusColor' => $statusColor
|
|
||||||
]);
|
|
||||||
|
|
||||||
$dompdf->loadHtml($html);
|
$dompdf->loadHtml($html);
|
||||||
// $dompdf->set_option('isRemoteEnabled', true);
|
// $dompdf->set_option('isRemoteEnabled', true);
|
||||||
@ -226,203 +446,283 @@ class Certificates extends BaseController {
|
|||||||
$dompdf->render();
|
$dompdf->render();
|
||||||
|
|
||||||
// Output PDF
|
// Output PDF
|
||||||
$filename = 'Certificate_' . $certificate['certid'] . '.pdf';
|
$filename = $certificate['certname']. '.pdf';
|
||||||
$dompdf->stream($filename, ['Attachment' => false]);
|
$dompdf->stream($filename, ['Attachment' => false]);
|
||||||
}
|
}
|
||||||
public function validateCertificate() { // Untuk Validasi Certificate
|
public function validateCertificate() {
|
||||||
$certid = $this->request->getPost('certid');
|
$certid = $this->request->getPost('certid');
|
||||||
$certificateType = $this->request->getPost('certificateType');
|
$certificateType = $this->request->getPost('certificateType');
|
||||||
|
|
||||||
if (!$certid) {
|
if (!$certid || !$certificateType) {
|
||||||
return $this->response->setJSON([
|
return $this->response->setJSON(['success' => false, 'message' => 'Parameter tidak lengkap.']);
|
||||||
'success' => false,
|
|
||||||
'message' => 'Certificate ID is required'
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dummy data certificates
|
$userId = session()->get('userid');
|
||||||
$certificates = [
|
$userPosId = session()->get('userposid');
|
||||||
'certid' => 'f353ca91-4fc5-49f2-9b9e-304f83d11914',
|
$certificateModel = new CertificateModel();
|
||||||
'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 ($certificates['certid'] === $certid) {
|
// 1. Ambil data dasar saja dulu untuk pengecekan awal
|
||||||
return $this->response->setJSON([
|
$currentCert = $certificateModel->find($certid);
|
||||||
'success' => true,
|
|
||||||
'message' => 'Certificate has already been validateda'
|
if (!$currentCert) {
|
||||||
]);
|
return $this->response->setJSON(['success' => false, 'message' => 'Data tidak ditemukan.']);
|
||||||
} else {
|
|
||||||
return $this->response->setJSON([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Certificate not found'
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// $validationDate = date('Y-m-d');
|
$updateData = [];
|
||||||
|
$currentTime = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
// return $this->response->setJSON([
|
// 2. Filter Role & Cek Duplikasi
|
||||||
// 'success' => true,
|
switch ($userPosId) {
|
||||||
// 'message' => 'Certificate validated ' . $certificateType,
|
case 1: // Manager
|
||||||
// 'validationDate' => $validationDate
|
if (!empty($currentCert['manager_validation_at']))
|
||||||
// ]);
|
return $this->response->setJSON(['success' => false, 'message' => 'Anda sudah Melakukan Validasi.']);
|
||||||
}
|
$updateData = ['manager_id' => $userId, 'manager_validation_at' => $currentTime];
|
||||||
|
break;
|
||||||
|
case 2: // SPV
|
||||||
// Helper Generate dan Save PDF
|
if (!empty($currentCert['spv_validation_at']))
|
||||||
public function generatePdf() {
|
return $this->response->setJSON(['success' => false, 'message' => 'Anda sudah Melakukan Validasi.']);
|
||||||
$certid = $this->request->getPost('certid');
|
$updateData = ['spv_id' => $userId, 'spv_validation_at' => $currentTime];
|
||||||
$certificateType = $this->request->getPost('certificateType');
|
break;
|
||||||
|
case 4: // TSOIVD
|
||||||
if (!$certid) {
|
if ($currentCert['user_id'] != $userId)
|
||||||
return $this->response->setJSON([
|
return $this->response->setJSON(['success' => false, 'message' => 'Bukan pemilik sertifikat.']);
|
||||||
'success' => false,
|
if (!empty($currentCert['user_validation_at']))
|
||||||
'message' => 'Certificate ID is required'
|
return $this->response->setJSON(['success' => false, 'message' => 'Anda sudah Melakukan Validasi.']);
|
||||||
]);
|
$updateData = ['user_validation_at' => $currentTime];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return $this->response->setJSON(['success' => false, 'message' => 'Akses ditolak.'], 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dummy data certificates
|
// 3. Eksekusi Update Validasi Role
|
||||||
$certificate = [
|
if ($certificateModel->update($certid, $updateData)) {
|
||||||
'certid' => 'f353ca91-4fc5-49f2-9b9e-304f83d11914',
|
|
||||||
'certname' => 'Jokoh Calibration Certificate',
|
// 4. Cek apakah ini validasi terakhir?
|
||||||
'productname' => 'Jokoh',
|
// Ambil ulang data terbaru (cukup kolom validation saja untuk efisiensi)
|
||||||
'productnumber' => 'SN-2024-001',
|
$checkFinal = $certificateModel->select('user_validation_at, spv_validation_at, manager_validation_at')
|
||||||
'issuedate' => '2024-01-15',
|
->find($certid);
|
||||||
'expirydate' => '2025-01-15',
|
|
||||||
'vendor' => 'Summit Calibration Lab',
|
|
||||||
'isval' => null
|
|
||||||
];
|
|
||||||
|
|
||||||
if (empty($certificate)) {
|
if (!empty($checkFinal['user_validation_at']) &&
|
||||||
return $this->response->setJSON([
|
!empty($checkFinal['spv_validation_at']) &&
|
||||||
'success' => false,
|
!empty($checkFinal['manager_validation_at'])) {
|
||||||
'message' => 'Certificate not found'
|
|
||||||
]);
|
// Update Status Utama
|
||||||
}
|
$certificateModel->update($certid, ['status' => 'validated']);
|
||||||
|
|
||||||
try {
|
// Baru jalankan query berat JOIN di sini untuk keperluan PDF/Notifikasi
|
||||||
$filePath = $this->savePdf($certificate, $certificateType, null);
|
$latestData = $certificateModel->select('
|
||||||
|
certificates.cert_name,
|
||||||
|
certificates.issued_date,
|
||||||
|
certificates.expired_date,
|
||||||
|
productcatalog.productname 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 "Before After Inspection"
|
||||||
|
WHEN certificates.cert_type = "BAP" THEN "Berita Acara Pengerjaan"
|
||||||
|
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')
|
||||||
|
->where('certificates.cert_id', $certid)
|
||||||
|
->first();
|
||||||
|
$certificate = [
|
||||||
|
'certname' => $latestData['cert_name'],
|
||||||
|
'sitename' => $latestData['sitename'],
|
||||||
|
'certtype' => $latestData['cert_type'],
|
||||||
|
'fullname' => $latestData['fullname'],
|
||||||
|
'userposition' => $latestData['user_position'],
|
||||||
|
'productname' => $latestData['productname'],
|
||||||
|
'productnumber' => $latestData['productnumber'],
|
||||||
|
'issueddate' => $latestData['issued_date'],
|
||||||
|
'expireddate' => $latestData['expired_date']
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdfAfterValidation = $this->savePdf($certificate, $latestData['cert_type']); // Simpan ke PDF
|
||||||
|
|
||||||
|
$certificateModel->update($certid, [ // Update ke tabel certificates
|
||||||
|
'file_location' => $pdfAfterValidation['file_relative'],
|
||||||
|
'metadata_title' => $pdfAfterValidation['metadata_title'],
|
||||||
|
'metadata_keywords' => $pdfAfterValidation['metadata_keywords'],
|
||||||
|
'file_url' => base_url('certificates/number/'.$latestData['cert_number'])
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Semua validasi telah dilakukan, PDF sudah di generate'
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
|
||||||
|
return $this->response->setStatusCode(500)->setJSON([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
if (!$filePath) {
|
|
||||||
return $this->response->setJSON([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Failed to generate PDF'
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$relativePath = str_replace(FCPATH, '', $filePath);
|
|
||||||
|
|
||||||
return $this->response->setJSON([
|
return $this->response->setJSON([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'message' => 'PDF generated and saved successfully',
|
'message' => "Sertifikat {$certificateType} berhasil divalidasi."
|
||||||
'filePath' => $relativePath
|
|
||||||
]);
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return $this->response->setJSON([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Failed to generate PDF: ' . $e->getMessage()
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $this->response->setJSON(['success' => false, 'message' => 'Gagal memperbarui data.']);
|
||||||
}
|
}
|
||||||
private function savePdf($certificate, $type, $productType=null) {
|
public function savePdf($certificate, $certificateType, $productType = null) {
|
||||||
// Generate PDF
|
$certificateType = strtolower($certificateType);
|
||||||
|
|
||||||
|
switch ($certificateType) {
|
||||||
|
case 'training':
|
||||||
|
$template = 'certificates/certificate_training';
|
||||||
|
$orientation = 'landscape';
|
||||||
|
$subDir = 'training';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'calibration':
|
||||||
|
$orientation = 'portrait';
|
||||||
|
$subDir = 'calibration';
|
||||||
|
|
||||||
|
switch ($productType) {
|
||||||
|
case 'tms50i':
|
||||||
|
$template = 'certificates/callibrations_template/certificate_tms50i_calibration';
|
||||||
|
break;
|
||||||
|
case 'tms24i':
|
||||||
|
$template = 'certificates/callibrations_template/certificate_tms24i_calibration';
|
||||||
|
break;
|
||||||
|
case 'tms30i':
|
||||||
|
$template = 'certificates/callibrations_template/certificate_tms30i_calibration';
|
||||||
|
break;
|
||||||
|
case 'bs430':
|
||||||
|
$template = 'certificates/callibrations_template/certificate_bs430_calibration';
|
||||||
|
break;
|
||||||
|
case 'cl900i':
|
||||||
|
$template = 'certificates/callibrations_template/certificate_cl900i_calibration';
|
||||||
|
break;
|
||||||
|
case 'jokoh':
|
||||||
|
$template = 'certificates/callibrations_template/certificate_jokoh_calibration';
|
||||||
|
break;
|
||||||
|
case 'bc760r':
|
||||||
|
$template = 'certificates/callibrations_template/certificate_bc760r_calibration';
|
||||||
|
break;
|
||||||
|
case 'bc5140':
|
||||||
|
$template = 'certificates/callibrations_template/certificate_bc5140_calibration';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new \Exception('Product type calibration tidak valid');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'maintenance':
|
||||||
|
$template = 'certificates/certificate_maintenance';
|
||||||
|
$orientation = 'landscape';
|
||||||
|
$subDir = 'maintenance';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'installation':
|
||||||
|
$template = 'certificates/certificate_installation';
|
||||||
|
$orientation = 'landscape';
|
||||||
|
$subDir = 'installation';
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new \Exception('Certificate type tidak valid');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($template)) {
|
||||||
|
throw new \Exception('Template tidak ditemukan');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dompdf
|
||||||
$options = new Options();
|
$options = new Options();
|
||||||
$options->set('isRemoteEnabled', true);
|
$options->set('isRemoteEnabled', true);
|
||||||
$options->set('isHtml5ParserEnabled', true);
|
$options->set('isHtml5ParserEnabled', true);
|
||||||
|
|
||||||
$dompdf = new Dompdf($options);
|
$dompdf = new Dompdf($options);
|
||||||
|
$html = view($template, ['certificate' => $certificate]);
|
||||||
// Format dates
|
|
||||||
$issuedate = date('F d, Y', strtotime($certificate['issuedate']));
|
|
||||||
$expirydate = date('F d, Y', strtotime($certificate['expirydate']));
|
|
||||||
|
|
||||||
// Get status
|
|
||||||
$today = date('Y-m-d');
|
|
||||||
$expiryCheck = date('Y-m-d', strtotime($certificate['expirydate']));
|
|
||||||
$daysUntilExpiry = (strtotime($expiryCheck) - strtotime($today)) / (60 * 60 * 24);
|
|
||||||
|
|
||||||
if ($daysUntilExpiry < 0) {
|
|
||||||
$status = 'Expired';
|
|
||||||
$statusColor = '#dc3545';
|
|
||||||
} elseif ($daysUntilExpiry <= 30) {
|
|
||||||
$status = 'Expiring Soon';
|
|
||||||
$statusColor = '#ffc107';
|
|
||||||
} else {
|
|
||||||
$status = 'Active';
|
|
||||||
$statusColor = '#28a745';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select template and orientation based on type
|
|
||||||
$template = 'certificate_pdf';
|
|
||||||
$orientation = 'portrait';
|
|
||||||
|
|
||||||
switch($type) {
|
|
||||||
case 'training':
|
|
||||||
$template = 'certificates/certificate_training';
|
|
||||||
$orientation = 'landscape';
|
|
||||||
break;
|
|
||||||
case 'calibration':
|
|
||||||
if ($productType == 'tms50i') {
|
|
||||||
$template = 'certificates/callibrations_template/certificate_tms50i_calibration';
|
|
||||||
} else if ($productType == 'tms24i') {
|
|
||||||
$template = 'certificates/callibrations_template/certificate_tms24i_calibration';
|
|
||||||
} else if ($productType == 'tms30i') {
|
|
||||||
$template = 'certificates/callibrations_template/certificate_tms30i_calibration';
|
|
||||||
} else if ($productType == 'bs430') {
|
|
||||||
$template = 'certificates/callibrations_template/certificate_bs430_calibration';
|
|
||||||
} else if ($productType == 'cl900i') {
|
|
||||||
$template = 'certificates/callibrations_template/certificate_cl900i_calibration';
|
|
||||||
} else if ($productType == 'jokoh') {
|
|
||||||
$template = 'certificates/callibrations_template/certificate_jokoh_calibration';
|
|
||||||
} else if ($productType == 'bc760r') {
|
|
||||||
$template = 'certificates/callibrations_template/certificate_bc760r_calibration';
|
|
||||||
} else if ($productType == 'bc5140') {
|
|
||||||
$template = 'certificates/callibrations_template/certificate_bc5140_calibration';
|
|
||||||
} else {
|
|
||||||
return $this->response->setStatusCode(404)->setJSON(['error' => 'Not Found']);
|
|
||||||
}
|
|
||||||
$orientation = 'portrait';
|
|
||||||
break;
|
|
||||||
case 'maintenance':
|
|
||||||
$template = 'certificates/certificate_maintenance';
|
|
||||||
$orientation = 'landscape';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$html = view($template, [
|
|
||||||
'certificate' => $certificate,
|
|
||||||
'issuedate' => $issuedate,
|
|
||||||
'expirydate' => $expirydate,
|
|
||||||
'status' => $status,
|
|
||||||
'statusColor' => $statusColor
|
|
||||||
]);
|
|
||||||
|
|
||||||
$dompdf->loadHtml($html);
|
$dompdf->loadHtml($html);
|
||||||
$dompdf->setPaper('A4', $orientation);
|
$dompdf->setPaper('A4', $orientation);
|
||||||
$dompdf->render();
|
$dompdf->render();
|
||||||
|
|
||||||
// Generate filename with timestamp
|
// Metadata
|
||||||
$timestamp = date('YmdHis');
|
$dompdf->addInfo('Title', $certificate['certname']);
|
||||||
$filename = 'Certificate_' . $certificate['certid'] . '_' . $timestamp . '.pdf';
|
$dompdf->addInfo('Keywords', $certificate['certtype'] . ' Certificate');
|
||||||
|
|
||||||
|
// Folder
|
||||||
|
$uploadDir = FCPATH . 'upload/documents/' . $subDir;
|
||||||
|
|
||||||
// Ensure upload directory exists
|
|
||||||
$uploadDir = FCPATH . 'upload' . DIRECTORY_SEPARATOR . 'documents';
|
|
||||||
if (!is_dir($uploadDir)) {
|
if (!is_dir($uploadDir)) {
|
||||||
mkdir($uploadDir, 0755, true);
|
mkdir($uploadDir, 0755, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save PDF to file
|
// Safe filename
|
||||||
|
$cleanName = preg_replace('/[^A-Za-z0-9_\-]/', '_', $certificate['certname']);
|
||||||
|
$timestamp = date('YmdHis');
|
||||||
|
$filename = $cleanName . '_' . $timestamp . '.pdf';
|
||||||
|
|
||||||
$filePath = $uploadDir . DIRECTORY_SEPARATOR . $filename;
|
$filePath = $uploadDir . DIRECTORY_SEPARATOR . $filename;
|
||||||
file_put_contents($filePath, $dompdf->output());
|
|
||||||
|
|
||||||
// Return file path
|
if (!file_put_contents($filePath, $dompdf->output())) {
|
||||||
return $filePath;
|
throw new \Exception('Gagal menyimpan file PDF');
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'file_name' => $filename,
|
||||||
|
'file_path' => $filePath,
|
||||||
|
'file_relative' => 'upload/documents/' . $subDir . '/' . $filename,
|
||||||
|
'metadata_title' => $certificate['certname'],
|
||||||
|
'metadata_keywords' => $certificate['certtype'] . ' Certificate'
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
public function view($uuid) {
|
||||||
|
try {
|
||||||
|
$certificateModel = new CertificateModel();
|
||||||
|
|
||||||
|
// Ambil dari model (UUID → binary otomatis)
|
||||||
|
$certificate = $certificateModel->getByUuid($uuid);
|
||||||
|
|
||||||
|
if (!$certificate) {
|
||||||
|
throw new \Exception('Certificate tidak ditemukan');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($certificate['file_location'])) {
|
||||||
|
throw new \Exception('File PDF belum tersedia');
|
||||||
|
}
|
||||||
|
|
||||||
|
$filePath = FCPATH . $certificate['file_location'];
|
||||||
|
|
||||||
|
if (!file_exists($filePath)) {
|
||||||
|
throw new \Exception('File tidak ditemukan di server');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->response
|
||||||
|
->setHeader('Content-Type', 'application/pdf')
|
||||||
|
->setHeader(
|
||||||
|
'Content-Disposition',
|
||||||
|
'inline; filename="' . basename($filePath) . '"'
|
||||||
|
)
|
||||||
|
->setBody(file_get_contents($filePath));
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return $this->response->setStatusCode(404)->setJSON([
|
||||||
|
// 'success' => false,
|
||||||
|
'message' => "404 Not Found"
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
76
app/Models/CertificateModel.php
Normal file
76
app/Models/CertificateModel.php
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use CodeIgniter\Model;
|
||||||
|
use Ramsey\Uuid\Uuid;
|
||||||
|
|
||||||
|
class CertificateModel extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'certificates';
|
||||||
|
protected $primaryKey = 'cert_id';
|
||||||
|
protected $useAutoIncrement = true;
|
||||||
|
protected $returnType = 'array';
|
||||||
|
protected $useSoftDeletes = true;
|
||||||
|
protected $allowedFields = [
|
||||||
|
'cert_number', 'cert_name', 'cert_type', 'actid', 'issued_date',
|
||||||
|
'expired_date', 'user_id', 'user_validation_at', 'spv_id',
|
||||||
|
'spv_validation_at', 'manager_id', 'manager_validation_at',
|
||||||
|
'status', 'file_location', 'file_url', 'metadata_title', 'metadata_keywords'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Timestamps
|
||||||
|
protected $useTimestamps = true;
|
||||||
|
protected $createdField = 'created_at';
|
||||||
|
protected $updatedField = 'updated_at';
|
||||||
|
protected $deletedField = 'deleted_at';
|
||||||
|
|
||||||
|
// Callbacks
|
||||||
|
protected $beforeInsert = ['generateUuidBinary'];
|
||||||
|
protected $afterFind = ['convertBinaryToUuidString'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Otomatis Generate UUIDv4 dan ubah ke Binary sebelum Insert
|
||||||
|
*/
|
||||||
|
protected function generateUuidBinary(array $data)
|
||||||
|
{
|
||||||
|
if (!isset($data['data']['cert_number'])) {
|
||||||
|
$uuid = Uuid::uuid4();
|
||||||
|
// Simpan dalam format byte (binary 16)
|
||||||
|
$data['data']['cert_number'] = $uuid->getBytes();
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Otomatis ubah Binary ke String UUID setelah data diambil (Get)
|
||||||
|
*/
|
||||||
|
protected function convertBinaryToUuidString(array $data)
|
||||||
|
{
|
||||||
|
if (empty($data['data'])) return $data;
|
||||||
|
|
||||||
|
// Cek apakah data berupa single row atau multiple rows
|
||||||
|
if (isset($data['data']['cert_number'])) {
|
||||||
|
// Single row (find/first)
|
||||||
|
$data['data']['cert_number'] = Uuid::fromBytes($data['data']['cert_number'])->toString();
|
||||||
|
} else {
|
||||||
|
// Multiple rows (findAll)
|
||||||
|
foreach ($data['data'] as &$row) {
|
||||||
|
if (isset($row['cert_number'])) {
|
||||||
|
$row['cert_number'] = Uuid::fromBytes($row['cert_number'])->toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper untuk mencari data berdasarkan String UUID
|
||||||
|
*/
|
||||||
|
public function getByUuid(string $uuidString)
|
||||||
|
{
|
||||||
|
$binary = Uuid::fromString($uuidString)->getBytes();
|
||||||
|
return $this->where('cert_number', $binary)->first();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -609,6 +609,144 @@ if(isset($data)) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Training Analyst -->
|
||||||
|
<div class='card'>
|
||||||
|
<div class='card-body'>
|
||||||
|
<div class="accordion accordion-flush mb-3" id="accordionTraining">
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="trainingHeading">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#trainingAccordion"
|
||||||
|
aria-expanded="true" aria-controls="sparepart">
|
||||||
|
Training Analyst
|
||||||
|
</button>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Callibration -->
|
||||||
|
<div class='card'>
|
||||||
|
<div class='card-body'>
|
||||||
|
<div class="accordion accordion-flush mb-3" id="accordionCalibrate">
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="calibrateHeading">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#calibrateAccordion"
|
||||||
|
aria-expanded="true" aria-controls="sparepart">
|
||||||
|
Calibrate
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="calibrateAccordion" class="accordion-collapse collapse bg-white" aria-labelledby="calibrateHeading" data-bs-parent="#accordionCalibrate">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<input type='hidden' name='calibrateid_delete' id='calibrateid_delete' />
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-lg-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="calibratename" class="form-label">Nama Parameter</label>
|
||||||
|
<input type='text' class="form-control form-control-sm calibratename" placeholder="Masukkan nama parameter" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="calibratevalue" class="form-label">Nilai</label>
|
||||||
|
<input type='text' class="form-control form-control-sm calibratevalue" placeholder="Masukkan nilai" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="calibrateunit" class="form-label">Satuan</label>
|
||||||
|
<input type='text' class="form-control form-control-sm calibrateunit" placeholder="Masukkan satuan" />
|
||||||
|
</div>
|
||||||
|
</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='addCalibrateRow();'>Tambah</button>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class='table-responsive'>
|
||||||
|
<table class='table table-bordered table-sm' id='calibrate_table'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style='width:33%;'>Nama Parameter</th>
|
||||||
|
<th style='width:33%;'>Nilai</th>
|
||||||
|
<th style='width:33%;'>Satuan</th>
|
||||||
|
<th style='width:10%;'>Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php
|
||||||
|
if(isset($calibratedata)) {
|
||||||
|
foreach($calibratedata as $data) {
|
||||||
|
$calibrateid = $data['calibrateid'];
|
||||||
|
$qname = $data['calibratename'];
|
||||||
|
$qvalue = $data['calibratevalue'];
|
||||||
|
$qunit = $data['calibrateunit'];
|
||||||
|
echo "<tr> <input type='hidden' name='calibratenames[]' value='".$qname."'> <input type='hidden' name='calibratevalues[]' value='".$qvalue."' /> <input type='hidden' name='calibrateunits[]' value='".$qunit."' /> <td>$qname</td> <td>$qvalue</td> <td>$qunit</td>".
|
||||||
|
"<td> <button type='button' class='btn btn-sm btn-warning' onclick='deleteCalibrateRow(this, $calibrateid)'>Hapus</button> </td>".
|
||||||
|
"</tr>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class='card'>
|
<div class='card'>
|
||||||
<div class='card-body'>
|
<div class='card-body'>
|
||||||
@ -634,6 +772,14 @@ if(isset($data)) {
|
|||||||
<?= $this->endSection() ?>
|
<?= $this->endSection() ?>
|
||||||
|
|
||||||
<?= $this->section('script') ?>
|
<?= $this->section('script') ?>
|
||||||
|
<style>
|
||||||
|
.opacity-50 {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.pointer-events-none {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$('.select2').select2({
|
$('.select2').select2({
|
||||||
@ -854,7 +1000,143 @@ function triggerChange(element){
|
|||||||
let changeEvent = new Event ('change');
|
let changeEvent = new Event ('change');
|
||||||
element.dispatchEvent(changeEvent);
|
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');
|
||||||
|
if (trainingChecked && !trainingDisabled) {
|
||||||
|
// Enable accordion
|
||||||
|
$('#accordionTraining').find('button').prop('disabled', false);
|
||||||
|
$('#accordionTraining').find('input').prop('disabled', false);
|
||||||
|
$('#accordionTraining').find('select').prop('disabled', false);
|
||||||
|
$('#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);
|
||||||
|
$('#accordionTraining').find('input').prop('disabled', true);
|
||||||
|
$('#accordionTraining').find('select').prop('disabled', true);
|
||||||
|
$('#accordionTraining').find('button').addClass('disabled');
|
||||||
|
$('#accordionTraining').addClass('opacity-50').addClass('pointer-events-none');
|
||||||
|
$('#accordionTraining').find('.accordion-button').removeAttr('data-bs-toggle');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Initialize on page load
|
||||||
|
toggleTrainingAccordion();
|
||||||
|
// Event listener for training checkbox change
|
||||||
|
$('#training').change(function() {
|
||||||
|
toggleTrainingAccordion();
|
||||||
|
});
|
||||||
|
// Event listener for training checkbox disable/enable (when acttypeid changes)
|
||||||
|
$('#acttypeid').change(function() {
|
||||||
|
toggleTrainingAccordion();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calibration checkbox enable/disable accordion
|
||||||
|
function toggleCalibrateAccordion() {
|
||||||
|
var calibrationChecked = $('#calibration').prop('checked');
|
||||||
|
var calibrationDisabled = $('#calibration').prop('disabled');
|
||||||
|
|
||||||
|
if (calibrationChecked && !calibrationDisabled) {
|
||||||
|
// Enable accordion
|
||||||
|
$('#accordionCalibrate').find('button').prop('disabled', false);
|
||||||
|
$('#accordionCalibrate').find('input').prop('disabled', false);
|
||||||
|
$('#accordionCalibrate').find('select').prop('disabled', false);
|
||||||
|
$('#accordionCalibrate').find('button').removeClass('disabled');
|
||||||
|
$('#accordionCalibrate').removeClass('opacity-50').removeClass('pointer-events-none');
|
||||||
|
$('#accordionCalibrate').find('.accordion-button').attr('data-bs-toggle', 'collapse');
|
||||||
|
} else {
|
||||||
|
// Disable accordion
|
||||||
|
$('#accordionCalibrate').find('button').prop('disabled', true);
|
||||||
|
$('#accordionCalibrate').find('input').prop('disabled', true);
|
||||||
|
$('#accordionCalibrate').find('select').prop('disabled', true);
|
||||||
|
$('#accordionCalibrate').find('button').addClass('disabled');
|
||||||
|
$('#accordionCalibrate').addClass('opacity-50').addClass('pointer-events-none');
|
||||||
|
$('#accordionCalibrate').find('.accordion-button').removeAttr('data-bs-toggle');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize on page load
|
||||||
|
toggleCalibrateAccordion();
|
||||||
|
|
||||||
|
// Event listener for calibration checkbox change
|
||||||
|
$('#calibration').change(function() {
|
||||||
|
toggleCalibrateAccordion();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Event listener for calibration checkbox disable/enable (when acttypeid changes)
|
||||||
|
$('#acttypeid').change(function() {
|
||||||
|
toggleCalibrateAccordion();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calibrate
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="<?=base_url();?>/assets/uppy/uppy-full.js"></script>
|
<script src="<?=base_url();?>/assets/uppy/uppy-full.js"></script>
|
||||||
<?= $this->endSection() ?>
|
<?= $this->endSection() ?>
|
||||||
|
|
||||||
|
|||||||
382
app/Views/certificate_calibrate_index.php
Normal file
382
app/Views/certificate_calibrate_index.php
Normal file
@ -0,0 +1,382 @@
|
|||||||
|
<?= $this->extend('layouts/main.php') ?>
|
||||||
|
|
||||||
|
<?= $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="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>
|
||||||
|
|
||||||
|
<!-- 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>
|
||||||
|
</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> 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="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 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>
|
||||||
|
<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') ?>
|
||||||
|
<script>
|
||||||
|
$(function () {
|
||||||
|
|
||||||
|
let table = $('#certificatesTable').DataTable({
|
||||||
|
order: [[5, 'asc']],
|
||||||
|
pageLength: 25,
|
||||||
|
dom: '<"row"<"col-md-6"l>>rtip',
|
||||||
|
responsive: true,
|
||||||
|
serverSide: false,
|
||||||
|
autoWidth: false,
|
||||||
|
ajax: function (data, callback, settings) {
|
||||||
|
fetch('<?= base_url('certificates/api/getindextraining') ?>')
|
||||||
|
.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;
|
||||||
|
|
||||||
|
let issuedate = '-';
|
||||||
|
let expirydate = '-';
|
||||||
|
let statusBadge = '<span class="badge bg-warning text-dark">Need Validation</span>';
|
||||||
|
|
||||||
|
if (isval != null) {
|
||||||
|
if (issuedateRaw && issuedateRaw !== '0000-00-00') {
|
||||||
|
issuedate = new Date(issuedateRaw).toLocaleDateString('en-US', { month: 'short', day: '2-digit', year: 'numeric' });
|
||||||
|
}
|
||||||
|
|
||||||
|
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>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>`,
|
||||||
|
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>`
|
||||||
|
];
|
||||||
|
})
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching data:', error);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#certificatesTable_filter').hide();
|
||||||
|
|
||||||
|
// Search
|
||||||
|
$('#searchInput').on('keyup', function () {
|
||||||
|
table.search(this.value).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Type filter using DataTables column filter
|
||||||
|
$('#typeFilter').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();
|
||||||
|
} else {
|
||||||
|
// Capitalize first letter for search
|
||||||
|
let typeText = type.charAt(0).toUpperCase() + type.slice(1);
|
||||||
|
table.column(1).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();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset
|
||||||
|
window.resetFilters = function () {
|
||||||
|
$('#searchInput, #statusFilter').val('');
|
||||||
|
table.search('').columns().search('').order([5,'asc']).draw();
|
||||||
|
};
|
||||||
|
|
||||||
|
// View PDF
|
||||||
|
$(document).on('click', '.btn-view', function () {
|
||||||
|
let certid = $(this).data('certid');
|
||||||
|
window.open('<?= base_url('certificates/training/show/') ?>' + certid, '_blank');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Activity report
|
||||||
|
$(document).on('click', '.activity-report-link', function () {
|
||||||
|
let certid = $(this).data('certid');
|
||||||
|
window.open(
|
||||||
|
'<?= base_url('certificates/training/activity/') ?>' + certid,
|
||||||
|
'_blank',
|
||||||
|
'width=1200,height=800,scrollbars=yes,resizable=yes'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Open modal
|
||||||
|
$(document).on('click', '.btn-validate', function () {
|
||||||
|
|
||||||
|
let btn = $(this);
|
||||||
|
|
||||||
|
$('#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'));
|
||||||
|
|
||||||
|
$('#confirmValidateBtn').data('certid', btn.data('certid'));
|
||||||
|
$('#certificatePreview').attr(
|
||||||
|
'src',
|
||||||
|
'<?= base_url('certificates/training/show/') ?>' + btn.data('certid')
|
||||||
|
);
|
||||||
|
|
||||||
|
$('#validateModal').modal('show');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Confirm validate
|
||||||
|
$('#confirmValidateBtn').on('click', function () {
|
||||||
|
|
||||||
|
let certid = $(this).data('certid');
|
||||||
|
let certificateType = 'training';
|
||||||
|
if (!confirm('Are you sure?')) return;
|
||||||
|
|
||||||
|
$.post(
|
||||||
|
'<?= 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();
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
alert(response.message || 'Validation failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
}, 'json'
|
||||||
|
).fail(function () {
|
||||||
|
$('#validateModal').modal('hide');
|
||||||
|
alert('Server error.');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<?= $this->endSection() ?>
|
||||||
381
app/Views/certificate_installation_index.php
Normal file
381
app/Views/certificate_installation_index.php
Normal file
@ -0,0 +1,381 @@
|
|||||||
|
<?= $this->extend('layouts/main.php') ?>
|
||||||
|
|
||||||
|
<?= $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 Installation 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>
|
||||||
|
|
||||||
|
<!-- 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>
|
||||||
|
</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> 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="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 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>
|
||||||
|
<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') ?>
|
||||||
|
<script>
|
||||||
|
$(function () {
|
||||||
|
|
||||||
|
let table = $('#certificatesTable').DataTable({
|
||||||
|
order: [[5, 'asc']],
|
||||||
|
pageLength: 25,
|
||||||
|
dom: '<"row"<"col-md-6"l>>rtip',
|
||||||
|
responsive: true,
|
||||||
|
serverSide: false,
|
||||||
|
autoWidth: false,
|
||||||
|
ajax: function (data, callback, settings) {
|
||||||
|
fetch('<?= base_url('certificates/api/getindexinstallation') ?>')
|
||||||
|
.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;
|
||||||
|
|
||||||
|
let issuedate = '-';
|
||||||
|
let expirydate = '-';
|
||||||
|
let statusBadge = '<span class="badge bg-warning text-dark">Need Validation</span>';
|
||||||
|
|
||||||
|
if (isval != null) {
|
||||||
|
if (issuedateRaw && issuedateRaw !== '0000-00-00') {
|
||||||
|
issuedate = new Date(issuedateRaw).toLocaleDateString('en-US', { month: 'short', day: '2-digit', year: 'numeric' });
|
||||||
|
}
|
||||||
|
|
||||||
|
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>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>`,
|
||||||
|
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>`
|
||||||
|
];
|
||||||
|
})
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching data:', error);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#certificatesTable_filter').hide();
|
||||||
|
|
||||||
|
// Search
|
||||||
|
$('#searchInput').on('keyup', function () {
|
||||||
|
table.search(this.value).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Type filter using DataTables column filter
|
||||||
|
$('#typeFilter').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();
|
||||||
|
} else {
|
||||||
|
// Capitalize first letter for search
|
||||||
|
let typeText = type.charAt(0).toUpperCase() + type.slice(1);
|
||||||
|
table.column(1).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();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset
|
||||||
|
window.resetFilters = function () {
|
||||||
|
$('#searchInput, #statusFilter').val('');
|
||||||
|
table.search('').columns().search('').order([5,'asc']).draw();
|
||||||
|
};
|
||||||
|
|
||||||
|
// View PDF
|
||||||
|
$(document).on('click', '.btn-view', function () {
|
||||||
|
let certid = $(this).data('certid');
|
||||||
|
window.open('<?= base_url('certificates/installation/show/') ?>' + certid, '_blank');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Activity report
|
||||||
|
$(document).on('click', '.activity-report-link', function () {
|
||||||
|
let certid = $(this).data('certid');
|
||||||
|
window.open(
|
||||||
|
'<?= base_url('certificates/installation/activity/') ?>' + certid,
|
||||||
|
'_blank',
|
||||||
|
'width=1200,height=800,scrollbars=yes,resizable=yes'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Open modal
|
||||||
|
$(document).on('click', '.btn-validate', function () {
|
||||||
|
|
||||||
|
let btn = $(this);
|
||||||
|
|
||||||
|
$('#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'));
|
||||||
|
|
||||||
|
$('#confirmValidateBtn').data('certid', btn.data('certid'));
|
||||||
|
$('#certificatePreview').attr(
|
||||||
|
'src',
|
||||||
|
'<?= base_url('certificates/installation/show/') ?>' + btn.data('certid')
|
||||||
|
);
|
||||||
|
|
||||||
|
$('#validateModal').modal('show');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Confirm validate
|
||||||
|
$('#confirmValidateBtn').on('click', function () {
|
||||||
|
|
||||||
|
let certid = $(this).data('certid');
|
||||||
|
let certificateType = 'installation';
|
||||||
|
if (!confirm('Are you sure?')) return;
|
||||||
|
|
||||||
|
$.post(
|
||||||
|
'<?= 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();
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
alert(response.message || 'Validation failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
}, 'json'
|
||||||
|
).fail(function () {
|
||||||
|
$('#validateModal').modal('hide');
|
||||||
|
alert('Server error.');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<?= $this->endSection() ?>
|
||||||
@ -22,24 +22,30 @@
|
|||||||
placeholder="Search certificates by name, product, type, vendor, or dates...">
|
placeholder="Search certificates by name, product, type, vendor, or dates...">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 mb-2">
|
<div class="col-md-3 mb-2">
|
||||||
<select id="statusFilter" class="form-select form-select-sm">
|
<select id="statusFilter" class="form-select form-select-sm">
|
||||||
<option value="">All Status</option>
|
<option value="">--Status Filter--</option>
|
||||||
<option value="active">Active</option>
|
<option value="active">Active</option>
|
||||||
<option value="expired">Expired</option>
|
<option value="expired">Expired</option>
|
||||||
<option value="expiring">Expiring Soon</option>
|
<option value="expiring">Expiring Soon</option>
|
||||||
<option value="isval">Need Validation</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 mb-2">
|
<div class="col-md-3 mb-2">
|
||||||
|
<select disabled id="validationFilter" class="form-select form-select-sm">
|
||||||
|
<option value="">--Validation Filter--</option>
|
||||||
|
<option value="validated">Validated</option>
|
||||||
|
<option value="unvalidated">Unvalidated</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3 mb-2">
|
||||||
<select id="typeFilter" class="form-select form-select-sm">
|
<select id="typeFilter" class="form-select form-select-sm">
|
||||||
<option value="">All Types</option>
|
<option value="">--Product Filter--</option>
|
||||||
<option value="tms">TMS</option>
|
<option value="tms">TMS</option>
|
||||||
<option value="joko">Jokoh</option>
|
<option value="jokoh">Jokoh</option>
|
||||||
<option value="boeki">Tokyo Boeki</option>
|
<option value="boeki">Tokyo Boeki</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 mb-2">
|
<div class="col-md-3 mb-2">
|
||||||
<button onclick="resetFilters()" class="btn btn-secondary btn-sm w-100">
|
<button onclick="resetFilters()" class="btn btn-secondary btn-sm w-100">
|
||||||
<i class="fas fa-redo"></i> Reset Filters
|
<i class="fas fa-redo"></i> Reset Filters
|
||||||
</button>
|
</button>
|
||||||
@ -51,13 +57,14 @@
|
|||||||
<table id="certificatesTable" class="table table-striped table-hover border" style="width: 100%;">
|
<table id="certificatesTable" class="table table-striped table-hover border" style="width: 100%;">
|
||||||
<thead class="table-primary">
|
<thead class="table-primary">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:20%">Certificate Name</th>
|
<th class="text-center" style="width: 3%">No</th>
|
||||||
<th style="width:20%">Product/Equipment</th>
|
<th style="width: 20%">Certificate</th>
|
||||||
<th style="width:20%">Activity Report</th>
|
<th style="width: 28%">Act Report</th>
|
||||||
<th style="width:10%">Issue Date</th>
|
<th style="width: 10%">Issue Date</th>
|
||||||
<th style="width:10%">Expiry Date</th>
|
<th style="width: 10%">Expiry Date</th>
|
||||||
<th style="width:10%">Status</th>
|
<th class="text-center" style="width: 7%">Status</th>
|
||||||
<th class="text-center" style="width:10%">Action</th>
|
<th class="text-center" style="width: 7%">Validation</th>
|
||||||
|
<th class="text-center" style="width: 5%">Action</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -77,63 +84,88 @@
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header bg-warning text-dark">
|
<div class="modal-header bg-warning text-dark">
|
||||||
<h5 class="modal-title" id="validateModalLabel">
|
<h5 class="modal-title" id="validateModalLabel">
|
||||||
<i class="fa-solid fa-check-double"></i> Validate Certificate
|
<i class="fa-solid fa-check-double"></i> Validate Maintenance Certificate
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"
|
<button type="button" class="btn-close" data-bs-dismiss="modal"
|
||||||
aria-label="Close"></button>
|
aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="row">
|
<div class="row border-bottom mb-3 pb-2">
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-8">
|
||||||
<label class="form-label fw-bold">Certificate ID</label>
|
<!-- <label class="form-label small mb-0"><i class="fa-solid fa-certificate me-2"></i>Certificate Name</label> -->
|
||||||
<p id="modalCertId" class="form-control-plaintext">-</p>
|
<label class="form-label "><i class="fa-solid fa-certificate me-2"></i>Certificate Name</label>
|
||||||
</div>
|
<h5 id="modalCertName" class="mt-2 fw-bolder">-</h5>
|
||||||
<div class="col-md-6 mb-3">
|
</div>
|
||||||
<label class="form-label fw-bold">Certificate Name</label>
|
<div class="col-md-4 text-md-end">
|
||||||
<p id="modalCertName" class="form-control-plaintext">-</p>
|
<h5><span id="modalValidation" class="badge bg-warning text-dark">-</span></h5>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
<div class="row">
|
||||||
<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">
|
<div class="col-md-6 mb-3">
|
||||||
<label class="form-label fw-bold">Activity Report</label>
|
<label class="form-label "><i class="fa-solid fa-book-medical me-2"></i>Certificate Number</label>
|
||||||
<p id="modalExpiryDate" class="form-control-plaintext">
|
<p id="modalCertNumber" class="form-control-plaintext border-bottom fw-bolder">-</p>
|
||||||
<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>
|
</div>
|
||||||
</p>
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label "><i class="fa-solid fa-microchip me-2"></i>Product/Equipment</label>
|
||||||
</div>
|
<p id="modalProductName" class="form-control-plaintext border-bottom fw-bolder">-</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="alert alert-warning">
|
<div class="col-md-6 mb-3">
|
||||||
<i class="fa-solid fa-info-circle"></i>
|
<label class="form-label "><i class="fa-solid fa-hashtag me-2"></i>Serial Number</label>
|
||||||
<strong>Validation Information:</strong> Please review the certificate details above before validating. Once validated, the certificate status will change from "Need Validation" to "Active".
|
<p id="modalProductNumber" class="form-control-plaintext border-bottom fw-bolder">-</p>
|
||||||
</div>
|
</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-4 mb-3">
|
||||||
|
<label class="form-label text-success"><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-4 mb-3">
|
||||||
|
<label class="form-label text-danger"><i class="fa-solid fa-calendar-xmark me-2"></i>Expiry Date</label>
|
||||||
|
<p id="modalExpiryDate" class="form-control-plaintext border-bottom fw-bolder">-</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label">Expired Status</label>
|
||||||
|
<p id="modalExpiryStatus" class="form-control-plaintext border-bottom">-</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></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 class="alert alert-warning border-0 shadow-sm d-flex align-items-center">
|
||||||
|
<i class="fa-solid fa-circle-info fa-2xl me-3"></i>
|
||||||
|
<div>
|
||||||
|
<strong>Validation Note:</strong>
|
||||||
|
Review data di berikut dengan teliti, setelah divalidasi maka sertifikat akan dinyatakan <strong>Valid</strong> dan <strong>sah secara sistem.</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12 mb-3">
|
<div class="col-md-12 mb-3">
|
||||||
<label class="form-label fw-bold">Certificate Preview</label>
|
<label class="form-label ">Certificate Preview</label>
|
||||||
<div class="border rounded p-2" style="height: 500px; overflow: hidden;">
|
<div class="border rounded p-2" style="height: 500px; overflow: hidden;">
|
||||||
<iframe id="certificatePreview" src="" style="width: 100%; height: 100%; border: none;"></iframe>
|
<iframe id="certificatePreview" src="" style="width: 100%; height: 100%; border: none;"></iframe>
|
||||||
</div>
|
</div>
|
||||||
@ -156,14 +188,6 @@
|
|||||||
<?= $this->endSection() ?>
|
<?= $this->endSection() ?>
|
||||||
|
|
||||||
<?= $this->section('style') ?>
|
<?= $this->section('style') ?>
|
||||||
<style>
|
|
||||||
#certificatesTable {
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
#certificatesTable_wrapper {
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<?= $this->endSection() ?>
|
<?= $this->endSection() ?>
|
||||||
|
|
||||||
<?= $this->section('script') ?>
|
<?= $this->section('script') ?>
|
||||||
@ -171,7 +195,7 @@
|
|||||||
$(function () {
|
$(function () {
|
||||||
|
|
||||||
let table = $('#certificatesTable').DataTable({
|
let table = $('#certificatesTable').DataTable({
|
||||||
order: [[5, 'asc']],
|
order: [[4, 'asc']],
|
||||||
pageLength: 25,
|
pageLength: 25,
|
||||||
dom: '<"row"<"col-md-6"l>>rtip',
|
dom: '<"row"<"col-md-6"l>>rtip',
|
||||||
responsive: true,
|
responsive: true,
|
||||||
@ -182,53 +206,73 @@ $(function () {
|
|||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(result => {
|
.then(result => {
|
||||||
callback({
|
callback({
|
||||||
data: result.map(cert => {
|
data: result.map((cert, index) => {
|
||||||
let certid = cert.certid || '';
|
|
||||||
let certname = cert.certname || '-';
|
// console.log(result);
|
||||||
let productname = cert.productname || '-';
|
let certid = cert.cert_id || '';
|
||||||
let productnumber = cert.productnumber || '';
|
let certname = cert.cert_name || '-';
|
||||||
let issuedateRaw = cert.issuedate || '';
|
let certnumber = cert.cert_number || '';
|
||||||
let expirydateRaw = cert.expirydate || '';
|
let actid = cert.actid || '';
|
||||||
let vendor = cert.vendor || '-';
|
let issuedateRaw = cert.issued_date || '';
|
||||||
let isval = cert.isval || null;
|
let expirydateRaw = cert.expired_date || '';
|
||||||
|
let managerValidation = cert.manager_validation_at || null;
|
||||||
|
let spvValidation = cert.spv_validation_at || null;
|
||||||
|
let userValidation = cert.user_validation_at || null;
|
||||||
|
let status = cert.status;
|
||||||
|
let fullname = cert.fullname;
|
||||||
|
let activity_subject = cert.activity_subject;
|
||||||
|
|
||||||
let issuedate = '-';
|
let issuedate = '-';
|
||||||
let expirydate = '-';
|
let expirydate = '-';
|
||||||
let statusBadge = '<span class="badge bg-warning text-dark">Need Validation</span>';
|
// let statusBadge = '<span class="badge bg-warning text-dark">need validation</span>';
|
||||||
|
let validationBadge = '';
|
||||||
|
|
||||||
if (isval != null) {
|
if (issuedateRaw && issuedateRaw !== '0000-00-00') {
|
||||||
if (issuedateRaw && issuedateRaw !== '0000-00-00') {
|
let date = new Date(issuedateRaw);
|
||||||
issuedate = new Date(issuedateRaw).toLocaleDateString('en-US', { month: 'short', day: '2-digit', year: 'numeric' });
|
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') {
|
if (expirydateRaw && expirydateRaw !== '0000-00-00') {
|
||||||
expirydate = new Date(expirydateRaw).toLocaleDateString('en-US', { month: 'short', day: '2-digit', year: 'numeric' });
|
let date = new Date(expirydateRaw);
|
||||||
let today = new Date();
|
let day = String(date.getDate()).padStart(2, '0');
|
||||||
let expiryDate = new Date(expirydateRaw);
|
let month = date.toLocaleString('en-US', { month: 'short' });
|
||||||
let days = Math.ceil((expiryDate - today) / (1000 * 60 * 60 * 24));
|
let year = date.getFullYear();
|
||||||
|
expirydate = `${day} ${month} ${year}`;
|
||||||
if (days < 0) {
|
let today = new Date();
|
||||||
statusBadge = '<span class="badge bg-danger">Expired</span>';
|
let expiryDate = new Date(expirydateRaw);
|
||||||
} else if (days <= 30) {
|
let days = Math.ceil((expiryDate - today) / (1000 * 60 * 60 * 24));
|
||||||
statusBadge = '<span class="badge bg-info">Expiring Soon</span>';
|
if (days < 0) {
|
||||||
} else {
|
statusBadge = '<div class="text-center"><span class="badge bg-danger">Expired</span></div>';
|
||||||
statusBadge = '<span class="badge bg-success">Active</span>';
|
} else if (days <= 30) {
|
||||||
}
|
statusBadge = '<div class="text-center"><span class="badge bg-info">Expiring Soon</span></div>';
|
||||||
} else {
|
} else {
|
||||||
statusBadge = '<span class="badge bg-secondary">N/A</span>';
|
statusBadge = '<div class="text-center"><span class="badge bg-success">Active</span></div>';
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
statusBadge = '<div class="text-center"><span class="badge bg-secondary">n/a</span></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status == 'unvalidated') {
|
||||||
|
|
||||||
|
validationBadge = '<div class="text-center"><span class="badge bg-warning text-dark">unvalidated</span></div>';
|
||||||
|
} else {
|
||||||
|
validationBadge = '<div class="text-center"><span class="badge bg-success">validated</span></div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
`<strong>${certname}</strong><br><small class="text-muted">ID: ${certid}</small>`,
|
index + 1,
|
||||||
`${productname}${productnumber ? '<br><small class="text-muted">SN: ' + productnumber + '</small>' : ''}`,
|
`<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}" style="color:#d43215b0;">Act ID - Nama AR - Nama User <i class="fa-solid fa-up-right-from-square"></i></a>`,
|
`<a href="javascript:void(0)" class="activity-report-link text-decoration-none" data-certid="${certid}" data-actid="${actid}" style="color:#d43215b0;">#${actid} - ${activity_subject} <i class="fa-solid fa-up-right-from-square"></i></strong><br><small class="text-muted">Owner : ${fullname}</small></a>`,
|
||||||
issuedate,
|
issuedate,
|
||||||
expirydate,
|
expirydate,
|
||||||
statusBadge,
|
statusBadge,
|
||||||
isval == null
|
validationBadge,
|
||||||
? `<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>`
|
status == 'unvalidated'
|
||||||
: `<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>`
|
? `<div class="text-center"><button type="button" class="btn btn-warning text-dark btn-validate-modal" data-certid="${certid}"><i class="fa-solid fa-check-double"></i></button></div>`
|
||||||
|
: `<div class="text-center"><button type="button" class="btn btn-success btn-view" data-certnumber="${certnumber}"><i class="fa-regular fa-file-pdf"></i></button></div>`
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@ -238,19 +282,29 @@ $(function () {
|
|||||||
callback({ data: [] });
|
callback({ data: [] });
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
columnDefs: [{
|
columnDefs: [
|
||||||
targets: 5,
|
{
|
||||||
render: function (data, type) {
|
targets: 4,
|
||||||
if (type === 'sort') {
|
render: function (data, type) {
|
||||||
if (data.includes('Need Validation')) return 1;
|
if (type === 'sort') {
|
||||||
if (data.includes('Expired')) return 2;
|
if (data.includes('need validation')) return 1;
|
||||||
if (data.includes('Expiring Soon')) return 3;
|
if (data.includes('expired')) return 2;
|
||||||
if (data.includes('Active')) return 4;
|
if (data.includes('expiring soon')) return 3;
|
||||||
return 5;
|
if (data.includes('active')) return 4;
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
return data;
|
},
|
||||||
|
{
|
||||||
|
targets: [5, 6],
|
||||||
|
orderable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targets: 7,
|
||||||
|
orderable: false
|
||||||
}
|
}
|
||||||
}]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#certificatesTable_filter').hide();
|
$('#certificatesTable_filter').hide();
|
||||||
@ -274,57 +328,103 @@ $(function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Status filter
|
|
||||||
$('#statusFilter').on('change', function () {
|
$('#statusFilter').on('change', function () {
|
||||||
let map = {
|
let status = $(this).val();
|
||||||
active: 'Active',
|
|
||||||
expired: 'Expired',
|
if (status === '') {
|
||||||
expiring: 'Expiring Soon',
|
table.column(5).search('').draw();
|
||||||
isval: 'Need Validation'
|
} else if (status === 'active') {
|
||||||
};
|
table.column(5).search('active').draw();
|
||||||
table.column(5).search(map[this.value] || '').draw();
|
} else if (status === 'expired') {
|
||||||
|
table.column(5).search('expired').draw();
|
||||||
|
} else if (status === 'expiring') {
|
||||||
|
table.column(5).search('expiring soon').draw();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reset
|
// Reset
|
||||||
window.resetFilters = function () {
|
window.resetFilters = function () {
|
||||||
$('#searchInput, #statusFilter').val('');
|
$('#searchInput, #statusFilter, #typeFilter').val('');
|
||||||
table.search('').columns().search('').order([5,'asc']).draw();
|
table.search('').columns().search('').order([4,'asc']).draw();
|
||||||
};
|
};
|
||||||
|
|
||||||
// View PDF
|
// View PDF
|
||||||
$(document).on('click', '.btn-view', function () {
|
$(document).on('click', '.btn-view', function () {
|
||||||
let certid = $(this).data('certid');
|
let certnumber = $(this).data('certnumber');
|
||||||
window.open('<?= base_url('certificates/maintenance/show/') ?>' + certid, '_blank');
|
window.open('<?= base_url('certificates/number/') ?>' + certnumber, '_blank');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Activity report
|
// Activity report
|
||||||
$(document).on('click', '.activity-report-link', function () {
|
$(document).on('click', '.activity-report-link', function () {
|
||||||
let certid = $(this).data('certid');
|
let actid = $(this).data('actid');
|
||||||
window.open(
|
window.open(
|
||||||
'<?= base_url('certificates/maintenance/activity/') ?>' + certid,
|
'<?= base_url('activities/detail/') ?>' + actid,
|
||||||
'_blank',
|
'_blank',
|
||||||
'width=1200,height=800,scrollbars=yes,resizable=yes'
|
'width=1200,height=800,scrollbars=yes,resizable=yes'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Open modal
|
// Open modal
|
||||||
$(document).on('click', '.btn-validate', function () {
|
$(document).on('click', '.btn-validate-modal', function () {
|
||||||
|
|
||||||
let btn = $(this);
|
let btn = $(this);
|
||||||
|
let certid = btn.data('certid');
|
||||||
|
|
||||||
$('#modalCertId').text(btn.data('certid'));
|
// POST API call to fetch certificate data
|
||||||
$('#modalCertName').text(btn.data('certname'));
|
$.post(
|
||||||
$('#modalProductName').text(btn.data('productname'));
|
'<?= base_url('certificates/api/showmaintenance') ?>',
|
||||||
$('#modalProductNumber').text(btn.data('productnumber'));
|
{ certid },
|
||||||
$('#modalIssueDate').text(btn.data('issuedate'));
|
function (data) {
|
||||||
$('#modalExpiryDate').text(btn.data('expirydate'));
|
// console.log(data);
|
||||||
$('#modalVendor').text(btn.data('vendor'));
|
// Populate modal with data
|
||||||
|
$('#modalCertName').text(data.cert_name || '-');
|
||||||
|
$('#modalCertNumber').text(data.cert_number || '-');
|
||||||
|
$('#modalProductName').text(data.productname || '-');
|
||||||
|
$('#modalProductNumber').text(data.productnumber || '-');
|
||||||
|
$('#modalIssueDate').text(data.issued_date || '-');
|
||||||
|
|
||||||
$('#confirmValidateBtn').data('certid', btn.data('certid'));
|
const formattedDate = data.expired_date
|
||||||
$('#certificatePreview').attr(
|
? new Date(data.expired_date).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })
|
||||||
'src',
|
: '-';
|
||||||
'<?= base_url('certificates/maintenance/show/') ?>' + btn.data('certid')
|
$('#modalExpiryDate').text(formattedDate);
|
||||||
);
|
|
||||||
|
$('#modalSiteName').text(data.sitename || '-');
|
||||||
|
$('#modalValidation').html(`<i class="fa-solid fa-triangle-exclamation me-2"></i>${data.status || '-'}`);
|
||||||
|
|
||||||
|
const today = new Date();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
const expiryDate = new Date(data.expired_date);
|
||||||
|
expiryDate.setHours(0, 0, 0, 0);
|
||||||
|
const diffTime = expiryDate - today;
|
||||||
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||||
|
let badge = '';
|
||||||
|
if (diffDays < 0) {
|
||||||
|
// Sudah melewati tanggal expired
|
||||||
|
badge = '<span class="badge bg-danger">Expired</span>';
|
||||||
|
} else if (diffDays <= 30) {
|
||||||
|
// Masih aktif tapi sisa 30 hari atau kurang
|
||||||
|
badge = '<span class="badge bg-warning">Expiring Soon</span>';
|
||||||
|
} else {
|
||||||
|
// Lebih dari 30 hari
|
||||||
|
badge = '<span class="badge bg-success">Active</span>';
|
||||||
|
}
|
||||||
|
$('#modalExpiryStatus').html(badge);
|
||||||
|
|
||||||
|
$('#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 () {
|
||||||
|
console.error('Error fetching certificate data');
|
||||||
|
});
|
||||||
|
|
||||||
|
// INI JANGAN DIUBAH
|
||||||
|
$('#confirmValidateBtn').data('certid', certid);
|
||||||
|
$('#certificatePreview').attr('src','<?= base_url('certificates/maintenance/show/') ?>' + certid);
|
||||||
|
|
||||||
$('#validateModal').modal('show');
|
$('#validateModal').modal('show');
|
||||||
});
|
});
|
||||||
@ -337,33 +437,14 @@ $(function () {
|
|||||||
if (!confirm('Are you sure?')) return;
|
if (!confirm('Are you sure?')) return;
|
||||||
|
|
||||||
$.post(
|
$.post(
|
||||||
'<?= base_url('certificates/api/validateCertificate') ?>',
|
'<?= base_url('certificates/api/validatecertificate') ?>',
|
||||||
{ certid, certificateType},
|
{ certid, certificateType},
|
||||||
function (response) {
|
function (response) {
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
$('#validateModal').modal('hide');
|
$('#validateModal').modal('hide');
|
||||||
alert(response.message);
|
alert(response.message);
|
||||||
|
location.reload();
|
||||||
// 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();
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
alert(response.message || 'Validation failed');
|
alert(response.message || 'Validation failed');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
<?= $this->extend('layouts/main.php') ?>
|
<?= $this->extend('layouts/main.php') ?>
|
||||||
|
|
||||||
<?= $this->section('content') ?>
|
<?= $this->section('content') ?>
|
||||||
@ -6,16 +5,9 @@
|
|||||||
<div class="page-wrapper">
|
<div class="page-wrapper">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row page-titles">
|
<div class="row page-titles">
|
||||||
<div class="col-md-6 align-self-center">
|
<div class="col-md-5 align-self-center">
|
||||||
<h4 class="text-themecolor">Certificates Training Management</h4>
|
<h4 class="text-themecolor">Certificates Training Management</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6 align-self-center text-end">
|
|
||||||
<button type="button" class="btn btn-info text-white btn-sm" data-bs-toggle="modal"
|
|
||||||
data-bs-target="#createModal">
|
|
||||||
<i class="fas fa-plus-circle"></i> Create
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@ -30,7 +22,16 @@
|
|||||||
placeholder="Search certificates by name, product, type, vendor, or dates...">
|
placeholder="Search certificates by name, product, type, vendor, or dates...">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-2">
|
<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">
|
<select id="typeFilter" class="form-select form-select-sm">
|
||||||
<option value="">All Types</option>
|
<option value="">All Types</option>
|
||||||
<option value="tms">TMS</option>
|
<option value="tms">TMS</option>
|
||||||
@ -38,29 +39,30 @@
|
|||||||
<option value="boeki">Tokyo Boeki</option>
|
<option value="boeki">Tokyo Boeki</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-2">
|
<div class="col-md-4 mb-2">
|
||||||
<button onclick="resetFilters()" class="btn btn-secondary btn-sm w-100">
|
<button onclick="resetFilters()" class="btn btn-secondary btn-sm w-100">
|
||||||
<i class="fas fa-redo"></i> Reset Filters
|
<i class="fas fa-redo"></i> Reset Filters
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="table-responsive">
|
<!-- TABLE -->
|
||||||
<table id="certificatesTable" class="table table-striped table-hover border">
|
<div class="table-responsive" style="width: 100%;">
|
||||||
<thead class="table-primary">
|
<table id="certificatesTable" class="table table-striped table-hover border" style="width: 100%;">
|
||||||
<tr>
|
<thead class="table-primary">
|
||||||
<th class="text-center" style="width: 5%;">No</th>
|
<tr>
|
||||||
<th style="width: 37%;">Certificate Name</th>
|
<th style="width:20%">Certificate Name</th>
|
||||||
<th style="width: 15%;">Product/Equipment</th>
|
<th style="width:20%">Product/Equipment</th>
|
||||||
<th style="width: 12%;">Issue Date</th>
|
<th style="width:20%">Activity Report</th>
|
||||||
<th style="width: 10%;">Vendor</th>
|
<th style="width:10%">Issue Date</th>
|
||||||
<th class="text-center" style="width: 9%;">Action</th>
|
<th style="width:10%">Expiry Date</th>
|
||||||
</tr>
|
<th style="width:10%">Status</th>
|
||||||
</thead>
|
<th class="text-center" style="width:10%">Action</th>
|
||||||
<tbody>
|
</tr>
|
||||||
<!-- Saya Ingin Ini Pakai JSON -->
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -69,366 +71,371 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Create Modal -->
|
<!-- Validate Modal -->
|
||||||
<div class="modal fade" id="createModal" tabindex="-1" aria-labelledby="createModalLabel" aria-hidden="true">
|
<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-dialog modal-xl modal-dialog-centered">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header bg-info text-white">
|
<div class="modal-header bg-warning text-dark">
|
||||||
<h5 class="modal-title" id="createModalLabel">
|
<h5 class="modal-title" id="validateModalLabel">
|
||||||
<i class="fas fa-plus-circle"></i> Create Certificate Training
|
<i class="fa-solid fa-check-double"></i> Validate Certificate
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"
|
<button type="button" class="btn-close" data-bs-dismiss="modal"
|
||||||
aria-label="Close"></button>
|
aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<form id="createForm" action="<?= base_url('certificates/create') ?>" method="post">
|
<div class="modal-body">
|
||||||
<div class="modal-body">
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
<div class="row">
|
<label class="form-label fw-bold">Certificate ID</label>
|
||||||
<div class="col-md-12 mb-3">
|
<p id="modalCertId" class="form-control-plaintext">-</p>
|
||||||
<label for="type" class="form-label">Activity Report <span class="text-danger">*</span></label>
|
|
||||||
<select class="form-select select2" id="type" name="type" required>
|
|
||||||
<option value=''>-- Choose one --</option>
|
|
||||||
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="site" class="form-label">Site<span
|
|
||||||
class="text-danger">*</span></label>
|
|
||||||
<input type="text" class="form-control" id="site" name="site" required readonly>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="productid" class="form-label">Product/Equipment<span
|
|
||||||
class="text-danger">*</span></label>
|
|
||||||
<input type="text" class="form-control" id="productid" name="productid" required readonly>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
<div class="row">
|
<label class="form-label fw-bold">Certificate Name</label>
|
||||||
<div class="col-md-6 mb-3">
|
<p id="modalCertName" class="form-control-plaintext">-</p>
|
||||||
<label for="certname" class="form-label">Certificate Name <span
|
|
||||||
class="text-danger">*</span></label>
|
|
||||||
<input type="text" class="form-control" id="certname" name="certname" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="mainTrainingDate" class="form-label">Training Date<span
|
|
||||||
class="text-danger">*</span></label>
|
|
||||||
<input type="date" class="form-control" id="mainTrainingDate" name="trainingdate">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr>
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col-12">
|
|
||||||
<button type="button" class="btn btn-sm btn-success" onclick="addAnalystRow()">
|
|
||||||
<i class="fas fa-plus-circle"></i> Add Analyst
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="analystRowsContainer">
|
|
||||||
<!-- Dynamic analyst rows will be added here -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- <div class="mb-3">
|
|
||||||
<label for="description" class="form-label">Description</label>
|
|
||||||
<textarea class="form-control" id="description" name="description" rows="2"></textarea>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="row">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
<div class="col-md-6 mb-3">
|
||||||
<i class="fas fa-times"></i> Cancel
|
<label class="form-label fw-bold">Product/Equipment</label>
|
||||||
</button>
|
<p id="modalProductName" class="form-control-plaintext">-</p>
|
||||||
<button type="submit" class="btn btn-info text-white">
|
</div>
|
||||||
<i class="fas fa-save"></i> Save
|
<div class="col-md-6 mb-3">
|
||||||
</button>
|
<label class="form-label fw-bold">Serial Number</label>
|
||||||
|
<p id="modalProductNumber" class="form-control-plaintext">-</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
<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 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 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>
|
||||||
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?= $this->endSection() ?>
|
<?= $this->endSection() ?>
|
||||||
|
|
||||||
|
<?= $this->section('style') ?>
|
||||||
|
<style>
|
||||||
|
#certificatesTable {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
#certificatesTable_wrapper {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<?= $this->endSection() ?>
|
||||||
|
|
||||||
<?= $this->section('script') ?>
|
<?= $this->section('script') ?>
|
||||||
<script>
|
<script>
|
||||||
$(function () {
|
$(function () {
|
||||||
// Store DataTable instance
|
|
||||||
let table = null;
|
|
||||||
|
|
||||||
// Initialize DataTable
|
let table = $('#certificatesTable').DataTable({
|
||||||
table = $('#certificatesTable').DataTable({
|
order: [[5, 'asc']],
|
||||||
"order": [
|
pageLength: 25,
|
||||||
[0, "asc"]
|
dom: '<"row"<"col-md-6"l>>rtip',
|
||||||
],
|
responsive: true,
|
||||||
"pageLength": 25,
|
serverSide: false,
|
||||||
"lengthMenu": [
|
autoWidth: false,
|
||||||
[10, 25, 50, 100, -1],
|
ajax: function (data, callback, settings) {
|
||||||
[10, 25, 50, 100, "All"]
|
fetch('<?= base_url('certificates/api/getindextraining') ?>')
|
||||||
],
|
.then(response => response.json())
|
||||||
"language": {
|
.then(result => {
|
||||||
"emptyTable": "No certificates available",
|
callback({
|
||||||
"info": "Showing _START_ to _END_ of _TOTAL_ certificates",
|
data: result.map(cert => {
|
||||||
"infoEmpty": "No certificates found",
|
let certid = cert.certid || '';
|
||||||
"infoFiltered": "(filtered from _MAX_ total certificates)"
|
let certname = cert.certname || '-';
|
||||||
},
|
let productname = cert.productname || '-';
|
||||||
"dom": '<"row"<"col-md-6"l>>rtip',
|
let productnumber = cert.productnumber || '';
|
||||||
"columnDefs": [{
|
let issuedateRaw = cert.issuedate || '';
|
||||||
"orderable": false,
|
let expirydateRaw = cert.expirydate || '';
|
||||||
"targets": [5]
|
let vendor = cert.vendor || '-';
|
||||||
}, // Disable sorting on Action column
|
let isval = cert.isval || null;
|
||||||
{
|
|
||||||
"searchable": true,
|
|
||||||
"targets": [0, 1, 2, 3, 4]
|
|
||||||
} // Enable search on all columns except Action
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Hide default DataTables search
|
let issuedate = '-';
|
||||||
$('#certificatesTable_filter').hide();
|
let expirydate = '-';
|
||||||
|
let statusBadge = '<span class="badge bg-warning text-dark">Need Validation</span>';
|
||||||
|
|
||||||
// Initialize Select2
|
if (isval != null) {
|
||||||
$('.select2').select2({
|
if (issuedateRaw && issuedateRaw !== '0000-00-00') {
|
||||||
theme: 'bootstrap-5',
|
issuedate = new Date(issuedateRaw).toLocaleDateString('en-US', { month: 'short', day: '2-digit', year: 'numeric' });
|
||||||
width: '100%',
|
}
|
||||||
dropdownParent: $('.modal')
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fetch data from API and populate table
|
if (expirydateRaw && expirydateRaw !== '0000-00-00') {
|
||||||
async function fetchCertificateData() {
|
expirydate = new Date(expirydateRaw).toLocaleDateString('en-US', { month: 'short', day: '2-digit', year: 'numeric' });
|
||||||
try {
|
let today = new Date();
|
||||||
const response = await fetch('<?= base_url('certificates/api/getindextraining') ?>');
|
let expiryDate = new Date(expirydateRaw);
|
||||||
const data = await response.json();
|
let days = Math.ceil((expiryDate - today) / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
if (data && Array.isArray(data)) {
|
|
||||||
populateTable(data);
|
|
||||||
} else {
|
|
||||||
console.log('No data available or invalid response format');
|
|
||||||
table.clear().draw();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching certificate data:', error);
|
|
||||||
table.clear().draw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate DataTable with fetched data
|
if (days < 0) {
|
||||||
function populateTable(certificates) {
|
statusBadge = '<span class="badge bg-danger">Expired</span>';
|
||||||
table.clear();
|
} else if (days <= 30) {
|
||||||
|
statusBadge = '<span class="badge bg-info">Expiring Soon</span>';
|
||||||
certificates.forEach((cert, index) => {
|
} else {
|
||||||
const certid = cert.certid || '';
|
statusBadge = '<span class="badge bg-success">Active</span>';
|
||||||
const certname = cert.certname || '-';
|
}
|
||||||
const productname = cert.productname || '-';
|
} else {
|
||||||
const productnumber = cert.productnumber || '';
|
statusBadge = '<span class="badge bg-secondary">N/A</span>';
|
||||||
const issuedate = cert.issuedate || '-';
|
}
|
||||||
const vendor = cert.vendor || '-';
|
}
|
||||||
const type = cert.type || '-';
|
|
||||||
|
|
||||||
// Format issue date
|
|
||||||
let formattedDate = '-';
|
|
||||||
if (issuedate && issuedate !== '0000-00-00') {
|
|
||||||
const dateObj = new Date(issuedate);
|
|
||||||
if (!isNaN(dateObj.getTime())) {
|
|
||||||
formattedDate = dateObj.toLocaleDateString('en-US', { month: 'short', day: '2-digit', year: 'numeric' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rowData = [
|
|
||||||
index + 1,
|
|
||||||
`<strong>${escapeHtml(certname)}</strong><br><small class="text-muted">ID: ${escapeHtml(certid)}</small>`,
|
|
||||||
`${escapeHtml(productname)}${productnumber ? `<br><small class="text-muted">SN: ${escapeHtml(productnumber)}</small>` : ''}`,
|
|
||||||
formattedDate,
|
|
||||||
escapeHtml(vendor),
|
|
||||||
`<div class="btn-group btn-group-sm"><button type="button" class="btn btn-success btn-view" data-certid="${escapeHtml(certid)}" data-certtype="${escapeHtml(type)}" title="View PDF"><i class="fas fa-file-pdf"></i></button></div>`
|
|
||||||
];
|
|
||||||
|
|
||||||
table.row.add(rowData);
|
|
||||||
});
|
|
||||||
|
|
||||||
table.draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to escape HTML
|
return [
|
||||||
function escapeHtml(text) {
|
`<strong>${certname}</strong><br><small class="text-muted">ID: ${certid}</small>`,
|
||||||
if (!text) return '';
|
`${productname}${productnumber ? '<br><small class="text-muted">SN: ' + productnumber + '</small>' : ''}`,
|
||||||
const div = document.createElement('div');
|
`<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>`,
|
||||||
div.textContent = text;
|
issuedate,
|
||||||
return div.innerHTML;
|
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>`
|
||||||
|
];
|
||||||
|
})
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching data:', error);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
// Fetch data on page load
|
$('#certificatesTable_filter').hide();
|
||||||
fetchCertificateData();
|
|
||||||
|
|
||||||
// Handle Activity Report dropdown change
|
// Search
|
||||||
$('#type').on('change', function() {
|
$('#searchInput').on('keyup', function () {
|
||||||
const certid = $(this).val();
|
table.search(this.value).draw();
|
||||||
|
});
|
||||||
if (!certid) {
|
|
||||||
$('#site').val('');
|
|
||||||
$('#productid').val('');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch certificate data from API
|
|
||||||
$.ajax({
|
|
||||||
url: '<?= base_url('certificates/api/getindextraining') ?>',
|
|
||||||
method: 'GET',
|
|
||||||
data: { actid: certid },
|
|
||||||
dataType: 'json',
|
|
||||||
success: function(response) {
|
|
||||||
if (response) {
|
|
||||||
$('#site').val(response.sitename || '');
|
|
||||||
$('#productid').val(response.productname || '');
|
|
||||||
} else {
|
|
||||||
$('#site').val('');
|
|
||||||
$('#productid').val('');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
|
||||||
console.error('Error fetching certificate data:', error);
|
|
||||||
$('#site').val('');
|
|
||||||
$('#productid').val('');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add one default analyst row when modal opens
|
// Type filter using DataTables column filter
|
||||||
$('#createModal').on('shown.bs.modal', function() {
|
$('#typeFilter').on('change', function () {
|
||||||
if($('#analystRowsContainer .analyst-row').length === 0) {
|
let type = $(this).val();
|
||||||
addAnalystRow();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate Activity Report dropdown
|
|
||||||
populateActivityDropdown();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Populate Activity Report dropdown from API
|
// Filter by type column (index 1 - Certificate Name contains type info)
|
||||||
async function populateActivityDropdown() {
|
if (type === '') {
|
||||||
try {
|
table.column(1).search('').draw();
|
||||||
const response = await fetch('<?= base_url('certificates/api/getindextraining') ?>');
|
} else {
|
||||||
const data = await response.json();
|
// Capitalize first letter for search
|
||||||
|
let typeText = type.charAt(0).toUpperCase() + type.slice(1);
|
||||||
const dropdown = $('#type');
|
table.column(1).search(typeText).draw();
|
||||||
dropdown.empty();
|
}
|
||||||
dropdown.append('<option value="">-- Choose one --</option>');
|
});
|
||||||
|
|
||||||
if (data && Array.isArray(data)) {
|
|
||||||
data.forEach(item => {
|
|
||||||
dropdown.append(`<option value="${escapeHtml(item.certid)}">${escapeHtml(item.certname)}</option>`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching activity data:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Custom search functionality
|
// Status filter
|
||||||
$('#searchInput').on('keyup', function () {
|
$('#statusFilter').on('change', function () {
|
||||||
let searchValue = $(this).val().toLowerCase();
|
let map = {
|
||||||
table.search(searchValue).draw();
|
active: 'Active',
|
||||||
});
|
expired: 'Expired',
|
||||||
|
expiring: 'Expiring Soon',
|
||||||
|
isval: 'Need Validation'
|
||||||
|
};
|
||||||
|
table.column(5).search(map[this.value] || '').draw();
|
||||||
|
});
|
||||||
|
|
||||||
// Type filter using DataTables column filter
|
// Reset
|
||||||
$('#typeFilter').on('change', function () {
|
window.resetFilters = function () {
|
||||||
let type = $(this).val();
|
$('#searchInput, #statusFilter').val('');
|
||||||
|
table.search('').columns().search('').order([5,'asc']).draw();
|
||||||
|
};
|
||||||
|
|
||||||
// Filter by type column (index 1 - Certificate Name contains type info)
|
// View PDF
|
||||||
if (type === '') {
|
$(document).on('click', '.btn-view', function () {
|
||||||
table.column(1).search('').draw();
|
let certid = $(this).data('certid');
|
||||||
} else {
|
window.open('<?= base_url('certificates/training/show/') ?>' + certid, '_blank');
|
||||||
// Capitalize first letter for search
|
});
|
||||||
let typeText = type.charAt(0).toUpperCase() + type.slice(1);
|
|
||||||
table.column(1).search(typeText).draw();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// Activity report
|
||||||
|
$(document).on('click', '.activity-report-link', function () {
|
||||||
|
let certid = $(this).data('certid');
|
||||||
|
window.open(
|
||||||
|
'<?= base_url('certificates/training/activity/') ?>' + certid,
|
||||||
|
'_blank',
|
||||||
|
'width=1200,height=800,scrollbars=yes,resizable=yes'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// Reset filters
|
// Open modal
|
||||||
window.resetFilters = function () {
|
$(document).on('click', '.btn-validate', function () {
|
||||||
$('#searchInput').val('');
|
|
||||||
$('#typeFilter').val('');
|
|
||||||
|
|
||||||
// Reset DataTables search and filters
|
let btn = $(this);
|
||||||
table.search('').columns().search('').draw();
|
|
||||||
|
|
||||||
// Re-apply default ordering
|
$('#modalCertId').text(btn.data('certid'));
|
||||||
table.order([0, 'asc']).draw();
|
$('#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'));
|
||||||
|
|
||||||
// View button click - Open PDF in new tab based on certificate type
|
$('#confirmValidateBtn').data('certid', btn.data('certid'));
|
||||||
$(document).on('click', '.btn-view', function () {
|
$('#certificatePreview').attr(
|
||||||
let certid = $(this).data('certid');
|
'src',
|
||||||
let url = '<?= base_url('certificates/training/show/') ?>' + certid;
|
'<?= base_url('certificates/training/show/') ?>' + btn.data('certid')
|
||||||
window.open(url, '_blank');
|
);
|
||||||
});
|
|
||||||
|
|
||||||
// Print button
|
$('#validateModal').modal('show');
|
||||||
$('#btnPrint').on('click', function () {
|
});
|
||||||
window.print();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Date picker enhancement
|
// Confirm validate
|
||||||
$('input[type="date"]').on('focus', function () {
|
$('#confirmValidateBtn').on('click', function () {
|
||||||
this.showPicker();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Re-attach date picker focus for dynamically added elements
|
let certid = $(this).data('certid');
|
||||||
$(document).on('focus', '.analyst-training-date', function() {
|
let certificateType = 'training';
|
||||||
this.showPicker();
|
if (!confirm('Are you sure?')) return;
|
||||||
});
|
|
||||||
|
|
||||||
// Add analyst row
|
$.post(
|
||||||
window.addAnalystRow = function() {
|
'<?= base_url('certificates/api/validateCertificate') ?>',
|
||||||
let rowCount = $('#analystRowsContainer .analyst-row').length;
|
{ certid, certificateType},
|
||||||
let defaultTrainingDate = $('#mainTrainingDate').val();
|
function (response) {
|
||||||
|
|
||||||
let newRow = `
|
|
||||||
<div class="row analyst-row mb-2" data-row-index="${rowCount}">
|
|
||||||
<div class="col-md-6 mb-2">
|
|
||||||
<label class="form-label">Analyst Name <span class="text-danger">*</span></label>
|
|
||||||
<input type="text" class="form-control analyst-name" name="analyst_name[]" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-5 mb-2">
|
|
||||||
<label class="form-label">Training Date <span class="text-danger">*</span></label>
|
|
||||||
<input type="date" class="form-control analyst-training-date" name="analyst_training_date[]" value="${defaultTrainingDate}" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-1 mb-2">
|
|
||||||
<label class="form-label"> </label>
|
|
||||||
<button type="button" class="btn btn-danger btn-sm w-100" onclick="removeAnalystRow(this)">
|
|
||||||
<i class="fas fa-trash"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
$('#analystRowsContainer').append(newRow);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Remove analyst row
|
if (response.success) {
|
||||||
window.removeAnalystRow = function(btn) {
|
$('#validateModal').modal('hide');
|
||||||
let totalRows = $('#analystRowsContainer .analyst-row').length;
|
alert(response.message);
|
||||||
|
|
||||||
if(totalRows <= 1) {
|
|
||||||
alert('At least one analyst is required. Cannot delete the last row.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(confirm('Are you sure you want to remove this analyst?')) {
|
|
||||||
$(btn).closest('.analyst-row').remove();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// When main training date changes, update all analyst training dates
|
// Generate and save PDF after successful validation
|
||||||
$('#mainTrainingDate').on('change', function() {
|
$.post(
|
||||||
let newDate = $(this).val();
|
'<?= base_url('certificates/api/generatepdf') ?>',
|
||||||
$('.analyst-training-date').val(newDate);
|
{ 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();
|
||||||
|
});
|
||||||
|
|
||||||
// Re-attach event handlers after DataTables pagination/draw
|
} else {
|
||||||
table.on('draw.dt', function () {
|
alert(response.message || 'Validation failed');
|
||||||
// Event handlers are already attached using $(document).on(), so no need to re-attach
|
}
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
}, 'json'
|
||||||
|
).fail(function () {
|
||||||
|
$('#validateModal').modal('hide');
|
||||||
|
alert('Server error.');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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>
|
</script>
|
||||||
<?= $this->endSection() ?>
|
<?= $this->endSection() ?>
|
||||||
183
app/Views/certificates/certificate_installation.php
Normal file
183
app/Views/certificates/certificate_installation.php
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Maintenance Installation - <?= $certificate['certname'] ?></title>
|
||||||
|
<style>
|
||||||
|
@page {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: Arial;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
/* font-size: 12px; */
|
||||||
|
/* line-height: 1.4; */
|
||||||
|
color: #000000;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
/* Container untuk background agar memenuhi halaman */
|
||||||
|
.bg-container {
|
||||||
|
margin:0;
|
||||||
|
padding:0;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: -1000;
|
||||||
|
}
|
||||||
|
.bg-container img {
|
||||||
|
margin:0;
|
||||||
|
padding:0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin :0;
|
||||||
|
padding:0;
|
||||||
|
color:#336600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
/* padding-top: 20px; */
|
||||||
|
}
|
||||||
|
.instument-name {
|
||||||
|
text-align: center;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin-top: 230px;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
.site-name {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
.detail-information {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 137px;
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
margin :0;
|
||||||
|
padding:0;
|
||||||
|
/* color:#336600; */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Menggunakan table agar dompdf tidak bingung */
|
||||||
|
.signature-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
.signature-table td {
|
||||||
|
width: 50%; /* Membagi dua sisi sama rata */
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.name {
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: underline;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 1px;
|
||||||
|
}
|
||||||
|
.position {
|
||||||
|
font-weight: bold;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fo-wrapper {
|
||||||
|
margin-left: 40px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="bg-container">
|
||||||
|
<img src="<?=base_url();?>/assets/images/background_certificate/maintenance.jpeg">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="instument-name">
|
||||||
|
<h1> <<Nama Alat>> </h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="site-name">
|
||||||
|
<h1> at <<Nama Rumah Sakit / Laboratorium - Kota>> </h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-information">
|
||||||
|
<h4>Serial Number: <<Serial Number>></h4>
|
||||||
|
|
||||||
|
<h4>has completed through a series of <<Jenis Sertifikat>></h4>
|
||||||
|
<h4>and the final result:</h4>
|
||||||
|
<h2>PASSED</h2>
|
||||||
|
<h4>Date of <<Jenis Sertifikat>> and Inspection: <<Tanggal Terbit>></h4>
|
||||||
|
<h4><<Valid>> <<Tanggal Berakhir>></h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="signature-table">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="name">Adhitya Pranata Putra</span>
|
||||||
|
<span class="position">Technical Support Manager</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span class="name"><<Nama TSO>></span>
|
||||||
|
<span class="position"><<Jabatan>></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="fo-wrapper">
|
||||||
|
<p><<FO>></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="instument-name">
|
||||||
|
<h1> <<Nama Alat>> </h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="site-name">
|
||||||
|
<h1> at <<Nama Rumah Sakit / Laboratorium - Kota>> </h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-information">
|
||||||
|
<h4>Serial Number: <<Serial Number>></h4>
|
||||||
|
|
||||||
|
<h4>has completed through a series of <<Jenis Sertifikat>></h4>
|
||||||
|
<h4>and the final result:</h4>
|
||||||
|
<h2>PASSED</h2>
|
||||||
|
<h4>Date of <<Jenis Sertifikat>> and Inspection: <<Tanggal Terbit>></h4>
|
||||||
|
<h4><<Valid>> <<Tanggal Berakhir>></h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="signature-table">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="name">Adhitya Pranata Putra</span>
|
||||||
|
<span class="position">Technical Support Manager</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span class="name"><<Nama TSO>></span>
|
||||||
|
<span class="position"><<Jabatan>></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="fo-wrapper">
|
||||||
|
<p><<FO>></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -2,7 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Maintenance Certificate - <?= $certificate['certname'] ?></title>
|
<title><?= $certificate['certname'] ?></title>
|
||||||
<style>
|
<style>
|
||||||
@page {
|
@page {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -103,21 +103,21 @@
|
|||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="instument-name">
|
<div class="instument-name">
|
||||||
<h1> <<Nama Alat>> </h1>
|
<h1> <<<?= $certificate['productname'] ?>>> </h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="site-name">
|
<div class="site-name">
|
||||||
<h1> at <<Nama Rumah Sakit / Laboratorium - Kota>> </h1>
|
<h1> at <<<?= $certificate['sitename'] ?>>> </h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="detail-information">
|
<div class="detail-information">
|
||||||
<h4>Serial Number: <<Serial Number>></h4>
|
<h4>Serial Number: <<<?= $certificate['productnumber'] ?>>></h4>
|
||||||
|
|
||||||
<h4>has completed through a series of <<Jenis Sertifikat>></h4>
|
<h4>has completed through a series of <<<?= $certificate['certtype'] ?>>></h4>
|
||||||
<h4>and the final result:</h4>
|
<h4>and the final result:</h4>
|
||||||
<h2>PASSED</h2>
|
<h2>PASSED</h2>
|
||||||
<h4>Date of <<Jenis Sertifikat>> and Inspection: <<Tanggal Terbit>></h4>
|
<h4>Date of Instrument Maintenance and Inspection: <<<?= $certificate['issueddate'] ?>>></h4>
|
||||||
<h4><<Valid>> <<Tanggal Berakhir>></h4>
|
<h4>Valid until <<<?= $certificate['expireddate'] ?>>></h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="signature-table">
|
<table class="signature-table">
|
||||||
@ -128,35 +128,35 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<span class="name"><<Nama TSO>></span>
|
<span class="name"><<<?= $certificate['fullname'] ?>>></span>
|
||||||
<span class="position"><<Jabatan>></span>
|
<span class="position"><<<?= $certificate['userposition'] ?>>></span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div class="fo-wrapper">
|
<div class="fo-wrapper">
|
||||||
<p><<FO>></p>
|
<p>FO.IV.01/20.00/2020</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="instument-name">
|
<div class="instument-name">
|
||||||
<h1> <<Nama Alat>> </h1>
|
<h1> <<<?= $certificate['productname'] ?>>> </h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="site-name">
|
<div class="site-name">
|
||||||
<h1> at <<Nama Rumah Sakit / Laboratorium - Kota>> </h1>
|
<h1> at <<<?= $certificate['sitename'] ?>>> </h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="detail-information">
|
<div class="detail-information">
|
||||||
<h4>Serial Number: <<Serial Number>></h4>
|
<h4>Serial Number: <<<?= $certificate['productnumber'] ?>>></h4>
|
||||||
|
|
||||||
<h4>has completed through a series of <<Jenis Sertifikat>></h4>
|
<h4>has completed through a series of <<<?= $certificate['certtype'] ?>>></h4>
|
||||||
<h4>and the final result:</h4>
|
<h4>and the final result:</h4>
|
||||||
<h2>PASSED</h2>
|
<h2>PASSED</h2>
|
||||||
<h4>Date of <<Jenis Sertifikat>> and Inspection: <<Tanggal Terbit>></h4>
|
<h4>Date of Instrument Maintenance and Inspection: <<<?= $certificate['issueddate'] ?>>></h4>
|
||||||
<h4><<Valid>> <<Tanggal Berakhir>></h4>
|
<h4>Valid until <<<?= $certificate['expireddate'] ?>>></h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="signature-table">
|
<table class="signature-table">
|
||||||
@ -167,14 +167,14 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<span class="name"><<Nama TSO>></span>
|
<span class="name"><<<?= $certificate['fullname'] ?>>></span>
|
||||||
<span class="position"><<Jabatan>></span>
|
<span class="position"><<<?= $certificate['userposition'] ?>>></span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div class="fo-wrapper">
|
<div class="fo-wrapper">
|
||||||
<p><<FO>></p>
|
<p>FO.IV.01/20.00/2020</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -98,9 +98,8 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="bg-container">
|
|
||||||
<img src="<?=base_url();?>/assets/images/background_certificate/maintenance.jpeg">
|
<?php for($i=0; $i<$count; $i++) : ?>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="instument-name">
|
<div class="instument-name">
|
||||||
@ -145,6 +144,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-container">
|
||||||
|
<img src="<?=base_url();?>/assets/images/background_certificate/maintenance.jpeg">
|
||||||
|
</div>
|
||||||
|
<?php endfor ?>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user