Update Fitur CRUD Sertifikat Maintenance

This commit is contained in:
mikael-zakaria 2026-03-04 23:34:45 +07:00
parent 252d30d040
commit 8717310ebb
13 changed files with 2501 additions and 772 deletions

View File

@ -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',

View File

@ -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');
}); });

View File

@ -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();

View File

@ -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"
]);
}
}
} }

View 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();
}
}

View File

@ -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() ?>

View 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> &nbsp;Validate Certificate
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Certificate ID</label>
<p id="modalCertId" class="form-control-plaintext">-</p>
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Certificate Name</label>
<p id="modalCertName" class="form-control-plaintext">-</p>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Product/Equipment</label>
<p id="modalProductName" class="form-control-plaintext">-</p>
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Serial Number</label>
<p id="modalProductNumber" class="form-control-plaintext">-</p>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Issue Date</label>
<p id="modalIssueDate" class="form-control-plaintext">-</p>
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Expiry Date</label>
<p id="modalExpiryDate" class="form-control-plaintext">-</p>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Vendor</label>
<p id="modalVendor" class="form-control-plaintext">-</p>
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Activity Report</label>
<p id="modalExpiryDate" class="form-control-plaintext">
<a href="javascript:void(0)" class="activity-report-link text-decoration-none" data-certid="${certid}" style="color:#d43215b0;">Act ID - Nama AR - Nama User <i class="fa-solid fa-up-right-from-square"></i></a>
</p>
</div>
</div>
<div class="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() ?>

View 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> &nbsp;Validate Certificate
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Certificate ID</label>
<p id="modalCertId" class="form-control-plaintext">-</p>
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Certificate Name</label>
<p id="modalCertName" class="form-control-plaintext">-</p>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Product/Equipment</label>
<p id="modalProductName" class="form-control-plaintext">-</p>
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Serial Number</label>
<p id="modalProductNumber" class="form-control-plaintext">-</p>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Issue Date</label>
<p id="modalIssueDate" class="form-control-plaintext">-</p>
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Expiry Date</label>
<p id="modalExpiryDate" class="form-control-plaintext">-</p>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Vendor</label>
<p id="modalVendor" class="form-control-plaintext">-</p>
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Activity Report</label>
<p id="modalExpiryDate" class="form-control-plaintext">
<a href="javascript:void(0)" class="activity-report-link text-decoration-none" data-certid="${certid}" style="color:#d43215b0;">Act ID - Nama AR - Nama User <i class="fa-solid fa-up-right-from-square"></i></a>
</p>
</div>
</div>
<div class="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() ?>

View File

@ -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> &nbsp;Validate Certificate <i class="fa-solid fa-check-double"></i> &nbsp;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} &nbsp;<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');
} }

View File

@ -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> &nbsp;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> &nbsp;Create Certificate Training <i class="fa-solid fa-check-double"></i> &nbsp;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">&nbsp;</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() ?>

View 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> &lt;&lt;Nama Alat&gt;&gt; </h1>
</div>
<div class="site-name">
<h1> at &lt;&lt;Nama Rumah Sakit / Laboratorium - Kota&gt;&gt; </h1>
</div>
<div class="detail-information">
<h4>Serial Number: &lt;&lt;Serial Number&gt;&gt;</h4>
<h4>has completed through a series of &lt;&lt;Jenis Sertifikat&gt;&gt;</h4>
<h4>and the final result:</h4>
<h2>PASSED</h2>
<h4>Date of &lt;&lt;Jenis Sertifikat&gt;&gt; and Inspection: &lt;&lt;Tanggal Terbit&gt;&gt;</h4>
<h4>&lt;&lt;Valid&gt;&gt; &lt;&lt;Tanggal Berakhir&gt;&gt;</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">&lt;&lt;Nama TSO&gt;&gt;</span>
<span class="position">&lt;&lt;Jabatan&gt;&gt;</span>
</td>
</tr>
</table>
<div class="fo-wrapper">
<p>&lt;&lt;FO&gt;&gt;</p>
</div>
</div>
<div class="container">
<div class="instument-name">
<h1> &lt;&lt;Nama Alat&gt;&gt; </h1>
</div>
<div class="site-name">
<h1> at &lt;&lt;Nama Rumah Sakit / Laboratorium - Kota&gt;&gt; </h1>
</div>
<div class="detail-information">
<h4>Serial Number: &lt;&lt;Serial Number&gt;&gt;</h4>
<h4>has completed through a series of &lt;&lt;Jenis Sertifikat&gt;&gt;</h4>
<h4>and the final result:</h4>
<h2>PASSED</h2>
<h4>Date of &lt;&lt;Jenis Sertifikat&gt;&gt; and Inspection: &lt;&lt;Tanggal Terbit&gt;&gt;</h4>
<h4>&lt;&lt;Valid&gt;&gt; &lt;&lt;Tanggal Berakhir&gt;&gt;</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">&lt;&lt;Nama TSO&gt;&gt;</span>
<span class="position">&lt;&lt;Jabatan&gt;&gt;</span>
</td>
</tr>
</table>
<div class="fo-wrapper">
<p>&lt;&lt;FO&gt;&gt;</p>
</div>
</div>
</body>
</html>

View File

@ -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> &lt;&lt;Nama Alat&gt;&gt; </h1> <h1> &lt;&lt;<?= $certificate['productname'] ?>&gt;&gt; </h1>
</div> </div>
<div class="site-name"> <div class="site-name">
<h1> at &lt;&lt;Nama Rumah Sakit / Laboratorium - Kota&gt;&gt; </h1> <h1> at &lt;&lt;<?= $certificate['sitename'] ?>&gt;&gt; </h1>
</div> </div>
<div class="detail-information"> <div class="detail-information">
<h4>Serial Number: &lt;&lt;Serial Number&gt;&gt;</h4> <h4>Serial Number: &lt;&lt;<?= $certificate['productnumber'] ?>&gt;&gt;</h4>
<h4>has completed through a series of &lt;&lt;Jenis Sertifikat&gt;&gt;</h4> <h4>has completed through a series of &lt;&lt;<?= $certificate['certtype'] ?>&gt;&gt;</h4>
<h4>and the final result:</h4> <h4>and the final result:</h4>
<h2>PASSED</h2> <h2>PASSED</h2>
<h4>Date of &lt;&lt;Jenis Sertifikat&gt;&gt; and Inspection: &lt;&lt;Tanggal Terbit&gt;&gt;</h4> <h4>Date of Instrument Maintenance and Inspection: &lt;&lt;<?= $certificate['issueddate'] ?>&gt;&gt;</h4>
<h4>&lt;&lt;Valid&gt;&gt; &lt;&lt;Tanggal Berakhir&gt;&gt;</h4> <h4>Valid until &lt;&lt;<?= $certificate['expireddate'] ?>&gt;&gt;</h4>
</div> </div>
<table class="signature-table"> <table class="signature-table">
@ -128,35 +128,35 @@
</td> </td>
<td> <td>
<span class="name">&lt;&lt;Nama TSO&gt;&gt;</span> <span class="name">&lt;&lt;<?= $certificate['fullname'] ?>&gt;&gt;</span>
<span class="position">&lt;&lt;Jabatan&gt;&gt;</span> <span class="position">&lt;&lt;<?= $certificate['userposition'] ?>&gt;&gt;</span>
</td> </td>
</tr> </tr>
</table> </table>
<div class="fo-wrapper"> <div class="fo-wrapper">
<p>&lt;&lt;FO&gt;&gt;</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> &lt;&lt;Nama Alat&gt;&gt; </h1> <h1> &lt;&lt;<?= $certificate['productname'] ?>&gt;&gt; </h1>
</div> </div>
<div class="site-name"> <div class="site-name">
<h1> at &lt;&lt;Nama Rumah Sakit / Laboratorium - Kota&gt;&gt; </h1> <h1> at &lt;&lt;<?= $certificate['sitename'] ?>&gt;&gt; </h1>
</div> </div>
<div class="detail-information"> <div class="detail-information">
<h4>Serial Number: &lt;&lt;Serial Number&gt;&gt;</h4> <h4>Serial Number: &lt;&lt;<?= $certificate['productnumber'] ?>&gt;&gt;</h4>
<h4>has completed through a series of &lt;&lt;Jenis Sertifikat&gt;&gt;</h4> <h4>has completed through a series of &lt;&lt;<?= $certificate['certtype'] ?>&gt;&gt;</h4>
<h4>and the final result:</h4> <h4>and the final result:</h4>
<h2>PASSED</h2> <h2>PASSED</h2>
<h4>Date of &lt;&lt;Jenis Sertifikat&gt;&gt; and Inspection: &lt;&lt;Tanggal Terbit&gt;&gt;</h4> <h4>Date of Instrument Maintenance and Inspection: &lt;&lt;<?= $certificate['issueddate'] ?>&gt;&gt;</h4>
<h4>&lt;&lt;Valid&gt;&gt; &lt;&lt;Tanggal Berakhir&gt;&gt;</h4> <h4>Valid until &lt;&lt;<?= $certificate['expireddate'] ?>&gt;&gt;</h4>
</div> </div>
<table class="signature-table"> <table class="signature-table">
@ -167,14 +167,14 @@
</td> </td>
<td> <td>
<span class="name">&lt;&lt;Nama TSO&gt;&gt;</span> <span class="name">&lt;&lt;<?= $certificate['fullname'] ?>&gt;&gt;</span>
<span class="position">&lt;&lt;Jabatan&gt;&gt;</span> <span class="position">&lt;&lt;<?= $certificate['userposition'] ?>&gt;&gt;</span>
</td> </td>
</tr> </tr>
</table> </table>
<div class="fo-wrapper"> <div class="fo-wrapper">
<p>&lt;&lt;FO&gt;&gt;</p> <p>FO.IV.01/20.00/2020</p>
</div> </div>
</div> </div>

View File

@ -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>