1183 lines
68 KiB
PHP
1183 lines
68 KiB
PHP
<?php include 'header.php'; ?>
|
|
<!-- partial -->
|
|
<div class="main-panel">
|
|
<div class="content-wrapper">
|
|
<div class="row">
|
|
<div class="col-md-12 grid-margin">
|
|
<div class="row">
|
|
<div class="col-12 col-xl-8 mb-4 mb-xl-0">
|
|
<h3 class="font-weight-bold">Manage Occupant</h3>
|
|
<h6 class="font-weight-normal mb-0">Manage approved occupant data, <span class="text-primary">Managing active occupants!</span></h6>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-12 grid-margin stretch-card">
|
|
<div class="card" style="border: 1px solid #e2e8f0; box-shadow: 0 4px 12px rgba(0,0,0,0.03); border-radius: 10px;">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<p class="card-title mb-0">Data Occupant</p>
|
|
<div>
|
|
<button class="btn btn-sm btn-outline-light" id="btnSelectMode" style="font-weight:bold; border-radius:6px; padding: 6px 10px; font-size:16px;" title="Select Multiple"><i class="mdi mdi-checkbox-multiple-marked-outline"></i></button>
|
|
<button class="btn btn-sm btn-danger ml-2" id="btnBulkDelete" style="display:none; font-weight:bold; border-radius:6px; padding: 6px 10px; font-size:16px;" title="Delete Selected"><i class="mdi mdi-delete"></i></button>
|
|
</div>
|
|
</div>
|
|
<div class="table-responsive">
|
|
<table class="table table-borderless" id="myTable">
|
|
<thead>
|
|
<tr>
|
|
<th>No</th>
|
|
<th>Name</th>
|
|
<th>NIK / NIP</th>
|
|
<th>Phone No.</th>
|
|
<th>Status</th>
|
|
<th>Action</th>
|
|
</tr>
|
|
</thead>
|
|
<?php
|
|
include 'koneksi.php';
|
|
$stmt = $pdo->query("SELECT * FROM occupant WHERE status = 'approved' ORDER BY id_occupant ASC");
|
|
$occupants = $stmt->fetchAll();
|
|
?>
|
|
<tbody>
|
|
<?php
|
|
$no = 1;
|
|
foreach($occupants as $row):
|
|
$isChecked = ($row['status'] === 'approved') ? 'checked' : '';
|
|
$statusToggle = '<label class="ios-switch mb-0" onclick="event.stopPropagation();" title="Toggle Status">
|
|
<input type="checkbox" onchange="toggleStatus(\''.$row['id_occupant'].'\')" ' . $isChecked . '>
|
|
<span class="ios-slider"></span>
|
|
</label>';
|
|
|
|
$fotoSrc = (!empty($row['foto']) && file_exists($row['foto'])) ? $row['foto'] : 'images/faces/user.jpg';
|
|
$hasFoto = (!empty($row['foto']) && file_exists($row['foto'])) ? '1' : '0';
|
|
$codeRaw = $row['code_register'];
|
|
$codeDisplay = htmlspecialchars($codeRaw);
|
|
if ($row['progress'] === 'used' && strlen($codeRaw) > 10) $codeDisplay = htmlspecialchars(substr($codeRaw, 0, 24)) . '...';
|
|
|
|
// Encrypt the fingerprint ID for display (Hash and slice 9 chars)
|
|
$fingerId = (isset($row['finger_id']) && $row['finger_id'] !== '-') ? substr(hash('sha256', $row['finger_id']), 0, 9) : '';
|
|
$fingerOk = (isset($row['finger_id']) && trim($row['finger_id']) !== '' && $row['finger_id'] !== '0' && $row['finger_id'] !== '-' && strtolower(trim($row['finger_id'])) !== 'null');
|
|
|
|
$sc = ($no % 2 == 1) ? 'row-odd' : 'row-even';
|
|
?>
|
|
<tr class="<?= $sc; ?>">
|
|
<td style="vertical-align:middle; font-weight:600; color:#555;"><?= $no++; ?></td>
|
|
<td style="vertical-align:middle;"><div class="d-flex align-items-center"><span style="font-weight:500; font-size:14px; color:#212529; display:inline-flex; align-items:center;"><?= htmlspecialchars($row['name']); ?><?php
|
|
$isVerified = !empty($row['name']) && !empty($row['nik_nip']) && !empty($row['no_hp']) && !empty($row['pin']) && !empty($row['foto']) && !empty($row['finger_id']) && $row['finger_id'] !== '-' && !empty($row['code_register']) && $row['progress'] === 'used' && $row['status'] === 'approved' && $row['access'] === 'active';
|
|
if($isVerified): ?><i class="mdi mdi-check-decagram" style="color: #1da1f2; font-size: 16px; margin-left: 4px;" title="Verified Approved"></i><?php endif; ?></span></div></td>
|
|
<td style="vertical-align:middle; font-size:13px; color:#495057;"><?= htmlspecialchars($row['nik_nip'] ?: '-'); ?></td>
|
|
<td style="vertical-align:middle; font-weight:500;"><?= htmlspecialchars($row['no_hp']); ?></td>
|
|
<td style="vertical-align:middle;"><?= $statusToggle; ?></td>
|
|
<td class="details-control"
|
|
data-id="<?= $row['id_occupant']; ?>"
|
|
data-name="<?= htmlspecialchars($row['name']); ?>"
|
|
data-nik="<?= htmlspecialchars($row['nik_nip']); ?>"
|
|
data-no_hp="<?= htmlspecialchars($row['no_hp']); ?>"
|
|
data-foto="<?= htmlspecialchars($fotoSrc); ?>"
|
|
data-hasfoto="<?= $hasFoto; ?>"
|
|
data-fingerid="<?= $fingerId; ?>"
|
|
data-fingerok="<?= $fingerOk ? '1' : '0'; ?>"
|
|
data-code="<?= htmlspecialchars($codeDisplay); ?>"
|
|
data-coderaw="<?= htmlspecialchars($codeRaw); ?>"
|
|
data-progress="<?= $row['progress']; ?>"
|
|
data-status="<?= $row['status']; ?>"
|
|
data-access="<?= $row['access']; ?>"
|
|
style="cursor:pointer; vertical-align:middle; text-align:center;">
|
|
<i class="mdi mdi-chevron-down expand-arrow" style="font-size:20px; transition: transform 0.3s; color:#555;"></i>
|
|
<input type="checkbox" class="bulk-cb" value="<?= $row['id_occupant']; ?>" style="display:none; transform: scale(1.5); cursor:pointer; margin-top:5px;">
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ==================== MODAL: TAMBAH ==================== -->
|
|
<div class="modal fade" id="modalTambah" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content mdl-card">
|
|
<form id="formTambahOccupant" enctype="multipart/form-data">
|
|
<div class="modal-header">
|
|
<h6 class="modal-title font-weight-bold"><i class="mdi mdi-account-plus mr-1 text-primary"></i> Add Occupant</h6>
|
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
|
</div>
|
|
<div class="modal-body py-3">
|
|
<div class="pin-warning mb-3">
|
|
<i class="mdi mdi-alert-outline"></i>
|
|
<div><strong>Important!</strong> PIN is used to view the original password. Make sure you <u>remember the PIN</u> you enter.</div>
|
|
</div>
|
|
<div class="text-center mb-3">
|
|
<div class="foto-wrapper" style="cursor:default;">
|
|
<img id="previewFoto" src="images/faces/user.jpg" class="foto-circle">
|
|
<div class="foto-badge"><i class="mdi mdi-camera"></i></div>
|
|
</div>
|
|
<small class="text-muted d-block" style="font-size:11px;"><i class="mdi mdi-information-outline"></i> Photo is taken via QR code after data is saved</small>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-6"><div class="form-group mb-2"><label class="frm-lbl">Full Name</label><input type="text" name="name" class="form-control form-control-sm" placeholder="Full name" required></div></div>
|
|
<div class="col-6"><div class="form-group mb-2"><label class="frm-lbl">NIK / NIP</label><input type="text" name="nik_nip" class="form-control form-control-sm" placeholder="NIK or NIP" required></div></div>
|
|
</div>
|
|
<div class="form-group mb-2"><label class="frm-lbl">Phone Number (Telegram)</label><input type="text" name="no_hp" class="form-control form-control-sm" placeholder="08xxxxxxxxxxx" required></div>
|
|
<div class="row">
|
|
</div>
|
|
|
|
<div class="form-group mb-1">
|
|
<label class="frm-lbl">PIN (6 Digit)</label>
|
|
<input type="password" name="pin" id="inputPinField" maxlength="6" class="form-control form-control-sm" placeholder="6 digit PIN" pattern="\d{6}" required style="letter-spacing:4px;">
|
|
<small class="text-muted" style="font-size:10px;"><i class="mdi mdi-information mr-1"></i>PIN is required to view the password on the detail page.</small>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer py-2">
|
|
<button type="button" class="btn btn-light btn-sm" data-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-primary btn-sm" id="btnSubmitOccupant"><i class="mdi mdi-content-save mr-1"></i>Save</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ==================== MODAL: EDIT ==================== -->
|
|
<div class="modal fade" id="modalEdit" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content mdl-card">
|
|
<form id="formEditOccupant">
|
|
<input type="hidden" name="id_occupant" id="edit_id">
|
|
<div class="modal-header">
|
|
<h6 class="modal-title font-weight-bold"><i class="mdi mdi-pencil mr-1 text-primary"></i> Edit Occupant</h6>
|
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
|
</div>
|
|
<div class="modal-body py-3">
|
|
|
|
<div class="row">
|
|
<div class="col-6"><div class="form-group mb-2"><label class="frm-lbl">Full Name</label><input type="text" name="name" id="edit_name" class="form-control form-control-sm" required></div></div>
|
|
<div class="col-6"><div class="form-group mb-2"><label class="frm-lbl">NIK / NIP</label><input type="text" name="nik_nip" id="edit_nik" class="form-control form-control-sm" required></div></div>
|
|
</div>
|
|
<div class="form-group mb-2"><label class="frm-lbl">Phone Number (Telegram)</label><input type="text" name="no_hp" id="edit_no_hp" class="form-control form-control-sm" required></div>
|
|
|
|
|
|
</div>
|
|
<div class="modal-footer py-2">
|
|
<button type="button" class="btn btn-light btn-sm" data-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-primary btn-sm" id="btnEditOccupant"><i class="mdi mdi-content-save mr-1"></i>Save</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ==================== MODAL: EDIT ==================== -->
|
|
<div class="modal fade" id="modalEditFoto" tabindex="-1" data-backdrop="static">
|
|
<div class="modal-dialog" style="max-width: 400px;">
|
|
<div class="modal-content">
|
|
<div class="modal-header py-2 bg-light">
|
|
<h6 class="modal-title font-weight-bold"><i class="mdi mdi-camera mr-1 text-primary"></i> Edit Photo</h6>
|
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
|
</div>
|
|
<div class="modal-body text-center" id="editFotoBody" style="padding-bottom:20px;">
|
|
<input type="hidden" id="editFotoOccupantId">
|
|
|
|
<div class="info-alert mb-3 text-left">
|
|
<div class="info-alert-icon"><i class="mdi mdi-check-circle-outline"></i></div>
|
|
<div class="info-alert-text" style="flex:1; font-size:12px; line-height:1.4;">
|
|
<strong style="display:block; margin-bottom:2px;">Data Updated Successfully!</strong>
|
|
Click the photo below to update the photo. If you <b>don't</b> want to update the photo, click <b>Finish</b>.
|
|
</div>
|
|
</div>
|
|
|
|
<div id="editFotoDefaultView" style="cursor:pointer;" onclick="generateEditFotoQr()">
|
|
<img id="editFotoPreview" src="images/faces/user.jpg" style="width:120px; height:120px; border-radius:50%; object-fit:cover; border:3px solid #e0e7ff; box-shadow:0 4px 10px rgba(0,0,0,0.05); transition:transform 0.2s;">
|
|
<p class="mt-3 text-muted" style="font-size:12px; font-weight:500;"><i class="mdi mdi-cursor-default-click text-primary mr-1"></i>Click the photo above to change via QR Code</p>
|
|
</div>
|
|
<div id="editFotoLoadingView" style="display:none; padding:30px;">
|
|
<div class="spinner-border text-primary" role="status"></div>
|
|
<p class="mt-2" style="font-size:12px; color:#999; font-weight:500;">Generating QR...</p>
|
|
</div>
|
|
<div id="editFotoQrView" style="display:none;">
|
|
<div id="editFotoQrCanvas" style="display:inline-block; padding:12px; background:#fff; border-radius:12px; border:2px solid #e0e7ff; box-shadow:0 4px 16px rgba(99,102,241,0.1);"></div>
|
|
|
|
<div class="input-group mb-2 mt-3">
|
|
<input type="text" id="editFotoLinkText" class="form-control form-control-sm" readonly style="font-size:11px;">
|
|
<div class="input-group-append">
|
|
<button class="btn btn-outline-light text-dark btn-sm" type="button" onclick="copyEditFotoLink()"><i class="mdi mdi-content-copy mr-1"></i>Copy</button>
|
|
</div>
|
|
</div>
|
|
<button class="btn btn-sm btn-primary mt-2 w-100" onclick="downloadEditFotoQr()" style="border-radius:8px; font-weight:bold; letter-spacing:0.5px;"><i class="mdi mdi-download mr-1"></i>Download QR Code</button>
|
|
</div>
|
|
|
|
<hr class="mt-4 mb-3">
|
|
<button class="btn btn-sm btn-light w-100 font-weight-bold" onclick="closeEditFotoModal()" style="border-radius:8px;"><i class="mdi mdi-check-all mr-1"></i>Finish</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ==================== MODAL: VERIFIKASI PIN QR ==================== -->
|
|
<div class="modal fade" id="modalVerifikasiPin" tabindex="-1" data-backdrop="static">
|
|
<div class="modal-dialog modal-sm">
|
|
<div class="modal-content">
|
|
<div class="modal-header py-2 bg-light">
|
|
<h6 class="modal-title font-weight-bold"><i class="mdi mdi-face-recognition mr-1 text-primary"></i> Verify PIN</h6>
|
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
|
</div>
|
|
<div class="modal-body text-center" id="verifikasiPinBody" style="padding-bottom:20px;">
|
|
<input type="hidden" id="verifikasiPinOccupantId">
|
|
<div id="verifikasiPinDefaultView" style="cursor:pointer;" onclick="generatePinQr()">
|
|
<div style="width:100px; height:100px; border-radius:16px; background:#f8fafc; border:2px dashed #cbd5e1; margin:0 auto; display:flex; align-items:center; justify-content:center; box-shadow:0 4px 10px rgba(0,0,0,0.02); transition:transform 0.2s;">
|
|
<i class="mdi mdi-qrcode-scan" style="font-size:40px; color:#94a3b8;"></i>
|
|
</div>
|
|
<p class="mt-3 text-muted" style="font-size:12px; font-weight:500;"><i class="mdi mdi-cursor-default-click text-primary mr-1"></i>Click the icon above to generate QR Code</p>
|
|
</div>
|
|
<div id="verifikasiPinLoadingView" style="display:none; padding:30px;">
|
|
<div class="spinner-border text-primary" role="status"></div>
|
|
<p class="mt-2" style="font-size:12px; color:#999; font-weight:500;">Generating QR...</p>
|
|
</div>
|
|
<div id="verifikasiPinQrView" style="display:none;">
|
|
<div id="qrPinContainer" style="display:inline-block; padding:16px; background:#fff; border-radius:12px; box-shadow:0 8px 24px rgba(0,0,0,0.12); border: 2px solid #e2e8f0; margin-bottom:16px;">
|
|
<div id="verifikasiPinQrCanvas"></div>
|
|
</div>
|
|
<div class="input-group mb-2">
|
|
<input type="text" id="verifikasiPinLinkText" class="form-control form-control-sm" readonly style="font-size:11px;">
|
|
<div class="input-group-append">
|
|
<button class="btn btn-outline-light text-dark btn-sm" type="button" onclick="copyPinQrLink()"><i class="mdi mdi-content-copy mr-1"></i>Copy</button>
|
|
</div>
|
|
</div>
|
|
<button class="btn btn-sm btn-primary mt-2 w-100" onclick="downloadPinQr()" style="border-radius:8px; font-weight:bold; letter-spacing:0.5px;"><i class="mdi mdi-download mr-1"></i>Download QR Code</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ==================== MODAL: PHOTO PREVIEW ==================== -->
|
|
<div class="modal fade" id="modalPhotoPreview" tabindex="-1" data-backdrop="static">
|
|
<div class="modal-dialog" style="max-width: 400px;">
|
|
<div class="modal-content" style="border-radius:12px; overflow:hidden;">
|
|
<div class="modal-header py-2" style="border-bottom: none; position:relative; background:#f8fafc;">
|
|
<h6 class="modal-title font-weight-bold" style="color:#1e293b;"><i class="mdi mdi-image-outline mr-1 text-primary"></i> Photo Preview</h6>
|
|
<button type="button" class="close" data-dismiss="modal" style="position:absolute; right:15px; top:10px; color:#64748b;">×</button>
|
|
</div>
|
|
<div class="modal-body text-center" style="padding:0 20px 20px 20px; background:#f8fafc;">
|
|
<img id="previewModalImage" src="" style="width:100%; max-height:350px; object-fit:cover; border-radius:8px; border:2px solid #e2e8f0; margin-bottom:15px; box-shadow:0 4px 12px rgba(0,0,0,0.05);">
|
|
<a id="btnDownloadPreview" href="#" download="Occupant_Photo.jpg" class="btn btn-sm btn-primary w-100" style="border-radius:8px; font-weight:bold; letter-spacing:0.5px; padding:10px;">
|
|
<i class="mdi mdi-download mr-1"></i>Download Photo
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ==================== MODAL: CODE REGISTER ==================== -->
|
|
<div class="modal fade" id="modalCodeRegister" tabindex="-1" data-backdrop="static" data-keyboard="false">
|
|
<div class="modal-dialog " style="max-width:480px;">
|
|
<div class="modal-content mdl-card">
|
|
<div class="modal-header py-2 bg-light">
|
|
<h6 class="modal-title font-weight-bold" style="font-size:14px;"><i class="mdi mdi-key-variant mr-1 text-primary"></i> Registration Code</h6>
|
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
|
</div>
|
|
<div class="modal-body" style="padding: 15px 20px;">
|
|
<div class="info-alert mb-2">
|
|
<div class="info-alert-icon" style="width:20px; height:20px;"><i class="mdi mdi-check-circle-outline" style="font-size:12px;"></i></div>
|
|
<div class="info-alert-text" style="flex:1;display:flex;align-items:center;justify-content:space-between; font-size:11px;">
|
|
<span><strong>Success!</strong> Save this code.</span>
|
|
<span class="countdown-timer" id="codeRegisterCountdown" title="Modal akan tertutup otomatis" style="font-size:11px; padding:2px 6px;"><i class="mdi mdi-timer-outline mr-1"></i>05:00</span>
|
|
</div>
|
|
</div>
|
|
<label class="frm-lbl" style="font-size:11px; margin-bottom:4px;">Registration Code</label>
|
|
<div class="input-group code-input-group mb-2">
|
|
<input type="text" id="hasilCodeRegister" class="form-control text-center code-big" readonly style="font-size:20px; padding:4px; height:auto;">
|
|
<div class="input-group-append"><span class="input-group-text" style="background:#fff;border-left:none;cursor:pointer; padding:4px 10px;" id="btnCopy" onclick="copyCode()"><i class="fas fa-copy" style="color:#6c757d; font-size:12px;"></i></span></div>
|
|
</div>
|
|
<!-- QR Code Area -->
|
|
<div id="qrCodeArea" style="display:none;">
|
|
<label class="frm-lbl" style="font-size:11px; margin-bottom:2px;"><i class="mdi mdi-qrcode mr-1"></i>Photo Registration QR Code</label>
|
|
<div class="text-center">
|
|
<div id="qrCodeContainer" style="display:inline-block; padding:10px; background:#fff; border-radius:10px; border:2px solid #e0e7ff; box-shadow:0 4px 12px rgba(0,0,0,0.08); margin-bottom:10px;">
|
|
<div id="qrCodeCanvas"></div>
|
|
</div>
|
|
</div>
|
|
<div class="input-group mb-2 mt-2">
|
|
<input type="text" id="qrLinkText" class="form-control form-control-sm" readonly style="font-size:11px; padding:4px;">
|
|
<div class="input-group-append">
|
|
<button class="btn btn-outline-light text-dark btn-sm" type="button" onclick="copyQrCodeRegisterLink()" style="padding:4px 10px;"><i class="mdi mdi-content-copy mr-1"></i>Copy</button>
|
|
</div>
|
|
</div>
|
|
<button class="btn btn-primary btn-sm btn-block mt-2" onclick="downloadQrCodeRegister()" id="btnDownloadQrReg" style="padding:6px;"><i class="mdi mdi-download mr-1"></i>Download QR Code</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ==================== MODAL: QR FOTO ==================== -->
|
|
<div class="modal fade" id="modalQrFoto" tabindex="-1" data-backdrop="static" data-keyboard="false">
|
|
<div class="modal-dialog " style="max-width:420px;">
|
|
<div class="modal-content mdl-card">
|
|
<div class="modal-header py-2 bg-light">
|
|
<h6 class="modal-title font-weight-bold"><i class="mdi mdi-qrcode mr-1 text-primary"></i> Photo Registration QR</h6>
|
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
|
</div>
|
|
<div class="modal-body text-center">
|
|
<div class="mb-3">
|
|
<div style="width:56px;height:56px;border-radius:50%;background:linear-gradient(135deg,#e0e7ff,#c7d2fe);display:inline-flex;align-items:center;justify-content:center;margin-bottom:8px;">
|
|
<i class="mdi mdi-face-recognition" style="font-size:28px;color:#4b7bec;"></i>
|
|
</div>
|
|
<p style="font-size:13px;color:#555;margin:0;">Share this QR code with <strong id="qrFotoName"></strong></p>
|
|
<p style="font-size:11px;color:#999;margin:4px 0 0;"><i class="mdi mdi-alert-circle-outline mr-1"></i>QR can only be used <strong>once</strong></p>
|
|
</div>
|
|
<div id="qrFotoLoading" style="padding:30px;"><div class="spinner-border text-primary" role="status"></div><p class="mt-2" style="font-size:12px;color:#999;">Generating QR...</p></div>
|
|
<div id="qrFotoContainer" style="display:none; padding:16px; background:#fff; border-radius:12px; border:2px solid #e0e7ff; box-shadow:0 8px 24px rgba(0,0,0,0.12); margin-bottom:16px; margin-left:auto; margin-right:auto; width:fit-content;">
|
|
<div id="qrFotoCanvas"></div>
|
|
</div>
|
|
<div id="qrFotoActions" style="display:none;">
|
|
<div class="input-group mb-2">
|
|
<input type="text" id="qrFotoLinkText" class="form-control form-control-sm" readonly style="font-size:11px;">
|
|
<div class="input-group-append">
|
|
<button class="btn btn-outline-light text-dark btn-sm" type="button" onclick="copyQrFotoLink()"><i class="mdi mdi-content-copy mr-1"></i>Copy</button>
|
|
</div>
|
|
</div>
|
|
<button class="btn btn-primary btn-sm btn-block mt-2" onclick="downloadQrFoto()"><i class="mdi mdi-download mr-1"></i>Download QR Code</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ==================== MODAL: VERIFY PIN ==================== -->
|
|
<div class="modal fade" id="modalVerifyPin" tabindex="-1">
|
|
<div class="modal-dialog modal-sm">
|
|
<div class="modal-content mdl-card">
|
|
<div class="modal-header py-2">
|
|
<h6 class="modal-title font-weight-bold"><i class="mdi mdi-lock mr-1 text-warning"></i> Verify PIN</h6>
|
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
|
</div>
|
|
<div class="modal-body text-center">
|
|
<i class="mdi mdi-shield-lock-outline" style="font-size:40px;color:#f59e0b;"></i>
|
|
<p class="mt-2 mb-3" style="font-size:12px;color:#666;">Enter 6-digit PIN to view the original password.</p>
|
|
<input type="hidden" id="verifyPinOccupantId">
|
|
<input type="password" id="verifyPinInput" class="form-control text-center" maxlength="6" placeholder="● ● ● ● ● ●" style="letter-spacing:8px;font-size:18px;font-weight:700;">
|
|
<div id="verifyPinError" class="text-danger mt-2" style="display:none;font-size:12px;"><i class="mdi mdi-alert-circle mr-1"></i><span id="verifyPinErrorMsg"></span></div>
|
|
<button type="button" class="btn btn-primary btn-sm btn-block mt-3" id="btnVerifyPin" onclick="submitPinVerify()"><i class="mdi mdi-check mr-1"></i>Verify</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ==================== MODAL: SHOW PASSWORD ==================== -->
|
|
<div class="modal fade" id="modalShowPassword" tabindex="-1">
|
|
<div class="modal-dialog modal-sm">
|
|
<div class="modal-content mdl-card">
|
|
<div class="modal-header py-2">
|
|
<h6 class="modal-title font-weight-bold"><i class="mdi mdi-key mr-1 text-success"></i> Original Password</h6>
|
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="pin-warning mb-2" style="background:#fff3cd;border-color:#ffc107;">
|
|
<i class="mdi mdi-alert" style="color:#d97706;"></i>
|
|
<div style="color:#92400e;font-size:11px;">Do not share this password with anyone!</div>
|
|
</div>
|
|
<div class="input-group">
|
|
<input type="text" id="revealedPassword" class="form-control" readonly style="font-size:15px;font-weight:600;letter-spacing:1px;">
|
|
<div class="input-group-append"><span class="input-group-text" style="cursor:pointer;" onclick="copyRevealed()"><i class="fas fa-copy" id="btnCopyRevealed" style="color:#6c757d;"></i></span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ==================== MODAL: DELETE ==================== -->
|
|
<div class="modal fade" id="modalDeleteOccupant" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content mdl-card">
|
|
<form id="formDeleteOccupant">
|
|
<div class="modal-body p-4" style="text-align: left;">
|
|
<input type="hidden" name="id_occupant" id="delete_id_occupant">
|
|
<h5 class="font-weight-bold mb-3">Delete Occupant</h5>
|
|
<p style="font-size:14px;color:#666;margin-bottom:20px;">Are you sure you want to delete <strong id="delete_occupant_name"></strong>?</p>
|
|
|
|
<div class="custom-control custom-checkbox mb-4" style="display:flex; align-items:center;">
|
|
<input type="checkbox" class="custom-control-input delete-confirm-cb" id="deleteConfirmCheckOccupant" onchange="document.getElementById('btnDeleteOccupant').disabled = !this.checked" required>
|
|
<label class="custom-control-label" for="deleteConfirmCheckOccupant" style="font-size:14px; color:#1e1f23; padding-top:2px;">I understand that all of my data will be deleted</label>
|
|
</div>
|
|
|
|
<div class="d-flex justify-content-end" style="gap:8px;">
|
|
<button type="button" class="btn btn-light font-weight-bold" style="padding:8px 18px;font-size:13px;" data-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-danger font-weight-bold" style="padding:8px 18px;font-size:13px;" id="btnDeleteOccupant" disabled>Delete</button>
|
|
</div>
|
|
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ==================== MODAL: REGISTER FINGER CONFIRM ==================== -->
|
|
<div class="modal fade" id="modalRegisterFingerConfirm" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content mdl-card">
|
|
<div class="modal-body p-4" style="text-align: left;">
|
|
<input type="hidden" id="reg_finger_code_fast">
|
|
<h5 class="font-weight-bold mb-3">Register Fingerprint</h5>
|
|
<p style="font-size:14px;color:#666;margin-bottom:20px;">Please ensure that you are near the IoT Fingerprint Box before registering your fingerprint.</p>
|
|
|
|
<div class="custom-control custom-checkbox mb-4" style="display:flex; align-items:center;">
|
|
<input type="checkbox" class="custom-control-input register-confirm-cb" id="registerConfirmCheck" onchange="document.getElementById('btnNowRegisterFinger').disabled = !this.checked" required>
|
|
<label class="custom-control-label" for="registerConfirmCheck" style="font-size:14px; color:#1e1f23; padding-top:2px;">I am ready</label>
|
|
</div>
|
|
|
|
<div class="d-flex justify-content-end" style="gap:8px;">
|
|
<button type="button" class="btn btn-light font-weight-bold" style="padding:8px 18px;font-size:13px;" data-dismiss="modal">Later</button>
|
|
<button type="button" class="btn btn-primary font-weight-bold" style="padding:8px 18px;font-size:13px;" id="btnNowRegisterFinger" onclick="executeRegisterFinger()" disabled>Now</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ==================== MODAL: REGISTER FINGER PROGRESS ==================== -->
|
|
<div class="modal fade" id="modalRegisterFingerProgress" tabindex="-1" data-backdrop="static" data-keyboard="false">
|
|
<div class="modal-dialog modal-sm">
|
|
<div class="modal-content mdl-card">
|
|
<div class="modal-body p-4 text-center">
|
|
<input type="hidden" id="reg_finger_poll_code">
|
|
<!-- Waiting View -->
|
|
<div id="fingerWaitingView">
|
|
<div class="spinner-border text-primary mb-3" role="status"></div>
|
|
<h6 class="font-weight-bold mb-2">Awaiting Fingerprint</h6>
|
|
<p style="font-size:12px;color:#666;line-height:1.4;">Please place your finger on the Fingerprint Sensor to register.</p>
|
|
<div class="mt-4">
|
|
<button type="button" class="btn btn-danger font-weight-bold btn-sm w-100" onclick="cancelRegisterFinger()">Cancel</button>
|
|
</div>
|
|
</div>
|
|
<!-- Success View -->
|
|
<div id="fingerSuccessView" style="display:none;">
|
|
<div style="width:60px;height:60px;border-radius:50%;background:#e6f4ea;display:inline-flex;align-items:center;justify-content:center;margin-bottom:12px;">
|
|
<i class="mdi mdi-check-bold" style="font-size:30px;color:#137333;"></i>
|
|
</div>
|
|
<h6 class="font-weight-bold mb-2" style="color:#137333;">Fingerprint Successfully Registered</h6>
|
|
<p style="font-size:12px;color:#666;line-height:1.4;margin-bottom:8px;">The fingerprint has been enrolled and saved to the database.</p>
|
|
<div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:10px 14px;margin-bottom:16px;">
|
|
<div style="font-size:10px;color:#64748b;font-weight:600;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:4px;">Finger ID</div>
|
|
<div id="fingerSuccessId" style="font-family:'Courier New',monospace;font-size:22px;font-weight:bold;color:#1e293b;letter-spacing:2px;"></div>
|
|
</div>
|
|
<button type="button" class="btn btn-primary font-weight-bold btn-sm w-100" onclick="closeFingerSuccessModal()"><i class="mdi mdi-check-all mr-1"></i>Done</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- ========================== CSS ========================== -->
|
|
<style>
|
|
.ios-switch {
|
|
position: relative; display: inline-block; width: 32px; height: 18px; margin: 0;
|
|
}
|
|
.ios-switch input { opacity: 0; width: 0; height: 0; }
|
|
.ios-slider {
|
|
position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0;
|
|
background-color: #e5e5ea;
|
|
transition: .4s cubic-bezier(0.4, 0.0, 0.2, 1); border-radius: 26px;
|
|
box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
|
|
}
|
|
.ios-slider:before {
|
|
position: absolute; content: ""; height: 14px; width: 14px; left: 2px; bottom: 2px;
|
|
background-color: white; transition: .4s cubic-bezier(0.4, 0.0, 0.2, 1); border-radius: 50%;
|
|
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
|
}
|
|
.ios-switch input:checked + .ios-slider { background-color: #4b49ac; }
|
|
.ios-switch input:checked + .ios-slider:before { transform: translateX(14px); }
|
|
|
|
/* ── MANUAL STRIPES & BORDERS ── */
|
|
#myTable thead th { border-bottom: 1px solid #e2e8f0 !important; }
|
|
#myTable tbody tr td { border-bottom: none !important; /* Hapus garis horizontal bawah antar menu */ }
|
|
|
|
.pill { display: inline-flex; align-items: center; font-weight: 600; font-size: 11px; padding: 3px 12px; border-radius: 20px; }
|
|
.pill-green { background: #e8f5e9; color: #2e7d32; }
|
|
.pill-red { background: #ffebee; color: #c62828; }
|
|
.pill-amber { background: #fff8e1; color: #f57f17; }
|
|
.pill-gray { background: #f5f5f5; color: #757575; }
|
|
|
|
.tbl-avatar { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; border: 1.5px solid #eee; }
|
|
.expand-arrow { font-size: 18px; color: #bbb; transition: transform 0.25s ease, color 0.2s; }
|
|
tr.shown .expand-arrow { transform: rotate(180deg); color: #4b7bec; }
|
|
.details-control:hover .expand-arrow { color: #4b7bec; }
|
|
|
|
/* ══════════════ DROPDOWN — Seamless UI ══════════════ */
|
|
#myTable tbody tr.shown > td {
|
|
border-bottom: none !important;
|
|
border-top: none !important; /* Hapus garis atas */
|
|
}
|
|
#myTable tbody tr.shown + tr > td {
|
|
padding: 0 !important;
|
|
margin: 0 !important;
|
|
border-bottom: none !important;
|
|
border-top: 1px solid #e2e8f0 !important; /* Tambah garis horizontal pemisah antar Menu dengan Halaman Dropdown */
|
|
}
|
|
#myTable tbody tr.shown + tr > td > div,
|
|
#myTable tbody tr.shown + tr > td > * {
|
|
padding: 0 !important;
|
|
margin: 0 !important;
|
|
}
|
|
#myTable tbody tr.shown + tr > td:first-child {
|
|
border-left: 1px solid transparent !important; /* CHANGE DROP-DOWN COLOR HERE (Border) */
|
|
border-bottom-left-radius: 8px;
|
|
}
|
|
#myTable tbody tr.shown + tr > td:last-child {
|
|
border-right: 1px solid transparent !important; /* CHANGE DROP-DOWN COLOR HERE (Border) */
|
|
border-bottom-right-radius: 8px;
|
|
}
|
|
|
|
#myTable tbody tr.shown > td:first-child {
|
|
border-left: 1px solid #e2e8f0 !important;
|
|
border-top-left-radius: 8px;
|
|
}
|
|
#myTable tbody tr.shown > td:last-child {
|
|
border-right: 1px solid #e2e8f0 !important;
|
|
border-top-right-radius: 8px;
|
|
}
|
|
|
|
@keyframes ddSlideIn {
|
|
from { opacity: 0; transform: translateY(-4px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
/* ── MODALS ── */
|
|
.mdl-card { border: none; border-radius: 6px; box-shadow: 0 8px 30px rgba(0,0,0,0.1); }
|
|
.confirm-circle { width: 50px; height: 50px; border-radius: 50%; border: 2.5px solid #ef5350; display: flex; align-items: center; justify-content: center; margin: 0 auto 10px; }
|
|
.confirm-circle span { font-size: 28px; color: #ef5350; line-height: 1; }
|
|
.delete-ok-msg { display: none; margin-top: 12px; font-size: 13px; color: #43a047; font-weight: 500; }
|
|
.frm-lbl { font-size: 12px; font-weight: 600; color: #555; margin-bottom: 3px; }
|
|
.pin-warning { display: flex; align-items: flex-start; gap: 8px; background: #fffbeb; border: 1px solid #fcd34d; border-radius: 6px; padding: 10px 12px; font-size: 12px; color: #92400e; }
|
|
.pin-warning i { font-size: 18px; color: #f59e0b; flex-shrink: 0; margin-top: 1px; }
|
|
.foto-wrapper { position: relative; display: inline-block; cursor: pointer; width: 72px; height: 72px; }
|
|
.foto-circle { width: 72px; height: 72px; border-radius: 50%; object-fit: cover; border: 2px solid #e0e0e0; transition: border-color 0.2s; }
|
|
.foto-badge { position: absolute; bottom: 0; right: 0; width: 24px; height: 24px; border-radius: 50%; background: #4b7bec; display: flex; align-items: center; justify-content: center; border: 2px solid #fff; }
|
|
.foto-badge i { color: #fff; font-size: 12px; }
|
|
.foto-wrapper:hover .foto-circle { border-color: #4b7bec; }
|
|
.btn-custom-edit {
|
|
background: #e2e6ea;
|
|
border: none;
|
|
color: #212529;
|
|
transition: background 0.2s ease;
|
|
}
|
|
.btn-custom-edit:hover, .btn-custom-edit:focus {
|
|
background: #cbd3da;
|
|
color: #212529;
|
|
}
|
|
.eye-toggle { cursor: pointer; background: #fff; border-left: none; }
|
|
.eye-toggle:hover i { color: #4b7bec; }
|
|
.code-big { font-size: 26px; letter-spacing: 8px; color: #222; font-weight: 700; border-right: none; }
|
|
.code-input-group .form-control { border-right: none; }
|
|
.info-alert { background: #eff6ff; border: 1px solid #bfdbfe; border-radius: 8px; display: flex; align-items: center; gap: 8px; padding: 8px 12px; font-size: 13px; color: #1e293b; }
|
|
.info-alert-icon { width: 22px; height: 22px; border-radius: 50%; background: #1d4ed8; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
|
.info-alert-icon i { color: #e0ecff; font-size: 14px; line-height: 1; }
|
|
.countdown-timer { font-weight: 700; color: #dc2626; font-size: 12px; margin-left: 8px; display: inline-flex; align-items: center; background: #fef2f2; padding: 3px 10px; border-radius: 6px; border: 1px solid #fecaca; white-space: nowrap; }
|
|
@keyframes pulse-timer { 0%,100% { opacity: 1; } 50% { opacity: 0.5; } }
|
|
|
|
.btn-custom-dashboard {
|
|
background-color: #fff !important;
|
|
border: 1px solid #e5e9f0 !important;
|
|
color: #4b49ac !important;
|
|
}
|
|
.btn-custom-dashboard:hover,
|
|
.btn-custom-dashboard:focus,
|
|
.btn-custom-dashboard:active {
|
|
background-color: #f1f2f4 !important;
|
|
border-color: #d1d5db !important;
|
|
color: #3f3d91 !important;
|
|
box-shadow: none !important;
|
|
}
|
|
.delete-confirm-cb:checked ~ .custom-control-label::before {
|
|
background-color: #ff4747 !important;
|
|
border-color: #ff4747 !important;
|
|
}
|
|
.register-confirm-cb:checked ~ .custom-control-label::before {
|
|
background-color: #4b49ac !important;
|
|
border-color: #4b49ac !important;
|
|
}
|
|
</style>
|
|
|
|
<!-- content-wrapper ends -->
|
|
<?php include 'footer.php'; ?>
|
|
<?php include 'copy.php'; ?>
|
|
|
|
<!-- ========================== JS ========================== -->
|
|
<script src="https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js"></script>
|
|
<script src="js/qr_card_designer.js"></script>
|
|
<script>
|
|
document.querySelectorAll('.eye-toggle').forEach(function(b) { b.addEventListener('click', function() { var t = document.getElementById(this.dataset.target), i = this.querySelector('i'); if (t.type === 'password') { t.type = 'text'; i.classList.replace('mdi-eye-off','mdi-eye'); } else { t.type = 'password'; i.classList.replace('mdi-eye','mdi-eye-off'); } }); });
|
|
|
|
var currentQrUrl = '';
|
|
|
|
// Generate QR after tambah (in Code Register modal)
|
|
function generateQrInCodeModal(idOccupant) {
|
|
var area = document.getElementById('qrCodeArea');
|
|
var canvas = document.getElementById('qrCodeCanvas');
|
|
area.style.display = 'none';
|
|
canvas.innerHTML = '';
|
|
|
|
var fd = new FormData();
|
|
fd.append('id_occupant', idOccupant);
|
|
fetch('generate_foto_token.php', { method: 'POST', body: fd })
|
|
.then(function(r){ return r.json(); })
|
|
.then(function(d) {
|
|
if (d.status === 'success') {
|
|
area.style.display = 'block';
|
|
currentQrUrl = d.url;
|
|
document.getElementById('qrLinkText').value = d.url;
|
|
new QRCode(canvas, {
|
|
text: d.url,
|
|
width: 200,
|
|
height: 200,
|
|
colorDark: '#0f172a',
|
|
colorLight: '#ffffff',
|
|
correctLevel: QRCode.CorrectLevel.H
|
|
});
|
|
}
|
|
}).catch(function(){});
|
|
}
|
|
|
|
function copyQrCodeRegisterLink() {
|
|
navigator.clipboard.writeText(document.getElementById('qrLinkText').value);
|
|
showCustomAlert('Link copied successfully!');
|
|
}
|
|
|
|
|
|
|
|
function downloadQrCodeRegister() {
|
|
var qrCanvas = document.querySelector('#qrCodeCanvas canvas');
|
|
if (!qrCanvas) { alert("QR Code has not been generated yet."); return; }
|
|
|
|
var imgUrl = createCompositeQrImage(qrCanvas);
|
|
var dlLink = document.createElement('a');
|
|
dlLink.download = 'QR_Registrasi_Foto.png';
|
|
dlLink.href = imgUrl;
|
|
dlLink.click();
|
|
}
|
|
|
|
// Show QR modal from edit/dropdown
|
|
function showQrFoto(idOccupant) {
|
|
$('#modalEdit').modal('hide');
|
|
var loading = document.getElementById('qrFotoLoading');
|
|
var canvas = document.getElementById('qrFotoCanvas');
|
|
var actions = document.getElementById('qrFotoActions');
|
|
loading.style.display = 'block';
|
|
canvas.style.display = 'none';
|
|
canvas.innerHTML = '';
|
|
actions.style.display = 'none';
|
|
document.getElementById('qrFotoName').textContent = '';
|
|
$('#modalQrFoto').modal('show');
|
|
|
|
var fd = new FormData();
|
|
fd.append('id_occupant', idOccupant);
|
|
fetch('generate_foto_token.php', { method: 'POST', body: fd })
|
|
.then(function(r){ return r.json(); })
|
|
.then(function(d) {
|
|
loading.style.display = 'none';
|
|
if (d.status === 'success') {
|
|
document.getElementById('qrFotoContainer').style.display = 'block';
|
|
canvas.style.display = 'block';
|
|
actions.style.display = 'block';
|
|
document.getElementById('qrFotoName').textContent = d.name;
|
|
document.getElementById('qrFotoLinkText').value = d.url;
|
|
new QRCode(canvas, {
|
|
text: d.url,
|
|
width: 220,
|
|
height: 220,
|
|
colorDark: '#0f172a',
|
|
colorLight: '#ffffff',
|
|
correctLevel: QRCode.CorrectLevel.H
|
|
});
|
|
} else {
|
|
document.getElementById('qrFotoContainer').style.display = 'none';
|
|
loading.innerHTML = '<i class="mdi mdi-alert-circle" style="font-size:40px;color:#ef5350;"></i><p style="font-size:13px;color:#999;margin-top:8px;">' + (d.message || 'Failed to generate QR') + '</p>';
|
|
loading.style.display = 'block';
|
|
}
|
|
}).catch(function(){
|
|
loading.innerHTML = '<i class="mdi mdi-alert-circle" style="font-size:40px;color:#ef5350;"></i><p style="font-size:13px;color:#999;margin-top:8px;">Connection failed</p>';
|
|
loading.style.display = 'block';
|
|
});
|
|
}
|
|
|
|
function copyQrFotoLink() {
|
|
navigator.clipboard.writeText(document.getElementById('qrFotoLinkText').value);
|
|
showCustomAlert('Link copied successfully!');
|
|
}
|
|
|
|
function downloadQrFoto() {
|
|
var qrCanvas = document.querySelector('#qrFotoCanvas canvas');
|
|
if (!qrCanvas) { alert("QR Code has not been generated yet."); return; }
|
|
|
|
var imgUrl = createCompositeQrImage(qrCanvas);
|
|
var dlLink = document.createElement('a');
|
|
dlLink.download = 'QR_Edit_Foto.png';
|
|
dlLink.href = imgUrl;
|
|
dlLink.click();
|
|
}
|
|
</script>
|
|
|
|
<script>
|
|
// Tambah
|
|
document.getElementById("formTambahOccupant").addEventListener("submit", function(e) {
|
|
e.preventDefault();
|
|
var btn = document.getElementById("btnSubmitOccupant"); btn.disabled = true; btn.innerHTML = '<i class="mdi mdi-loading mdi-spin mr-1"></i>Saving...';
|
|
fetch("tambah_occupant.php", { method: "POST", body: new FormData(this) }).then(function(r){ return r.json(); }).then(function(d) {
|
|
btn.disabled = false; btn.innerHTML = '<i class="mdi mdi-content-save mr-1"></i>Save';
|
|
if (d.status === "success") { $("#modalTambah").modal("hide"); document.getElementById("formTambahOccupant").reset(); document.getElementById("previewFoto").src = "images/faces/user.jpg"; document.getElementById("hasilCodeRegister").value = d.code_register; $("#modalCodeRegister").modal({ backdrop:'static', keyboard:false }); if (d.id_occupant) { generateQrInCodeModal(d.id_occupant); } }
|
|
else { showCustomAlert(d.message || "Failed to save data."); }
|
|
}).catch(function(){ btn.disabled=false; btn.innerHTML='<i class="mdi mdi-content-save mr-1"></i>Save'; showCustomAlert("Connection to server failed."); });
|
|
});
|
|
|
|
// Edit
|
|
document.getElementById("formEditOccupant").addEventListener("submit", function(e) {
|
|
e.preventDefault();
|
|
// Removed edit password validation
|
|
var btn = document.getElementById("btnEditOccupant"); btn.disabled = true; btn.innerHTML = '<i class="mdi mdi-loading mdi-spin mr-1"></i>Saving...';
|
|
fetch("edit_occupant.php", { method: "POST", body: new FormData(this) }).then(function(r){ return r.json(); }).then(function(d) {
|
|
btn.disabled = false; btn.innerHTML = '<i class="mdi mdi-content-save mr-1"></i>Save';
|
|
if (d.status === "success") {
|
|
$("#modalEdit").modal("hide");
|
|
document.getElementById("editFotoOccupantId").value = document.getElementById("edit_id").value;
|
|
document.getElementById("editFotoPreview").src = currentEditFoto || 'images/faces/user.jpg';
|
|
document.getElementById("editFotoDefaultView").style.display = "block";
|
|
document.getElementById("editFotoLoadingView").style.display = "none";
|
|
document.getElementById("editFotoQrView").style.display = "none";
|
|
$("#modalEditFoto").modal("show");
|
|
}
|
|
else { showCustomAlert(d.message || "Failed to save changes."); }
|
|
}).catch(function(){ btn.disabled=false; btn.innerHTML='<i class="mdi mdi-content-save mr-1"></i>Save'; showCustomAlert("Connection to server failed."); });
|
|
});
|
|
|
|
function copyCode() { var v = document.getElementById("hasilCodeRegister").value, b = document.getElementById("btnCopy").querySelector('i'); navigator.clipboard.writeText(v).then(function() { b.classList.replace("fa-copy","fa-check"); showCustomAlert('Registration Code copied successfully!'); setTimeout(function(){ b.classList.replace("fa-check","fa-copy"); }, 2000); }); }
|
|
function copyRevealed() { var v = document.getElementById("revealedPassword").value, b = document.getElementById("btnCopyRevealed"); navigator.clipboard.writeText(v).then(function() { b.classList.replace("fa-copy","fa-check"); showCustomAlert('Password copied successfully!'); setTimeout(function(){ b.classList.replace("fa-check","fa-copy"); }, 2000); }); }
|
|
|
|
var currentEditFoto = '';
|
|
|
|
function openDeleteModal(id, name) { document.getElementById("delete_id_occupant").value = id; document.getElementById("delete_occupant_name").innerText = name; $("#modalDeleteOccupant").modal("show"); }
|
|
function openEditModal(id, name, nik, no_hp, foto) {
|
|
document.getElementById("edit_id").value = id;
|
|
document.getElementById("edit_name").value = name;
|
|
document.getElementById("edit_nik").value = nik || '';
|
|
document.getElementById("edit_no_hp").value = no_hp;
|
|
currentEditFoto = foto;
|
|
$("#modalEdit").modal("show");
|
|
}
|
|
|
|
function generateEditFotoQr() {
|
|
var id = document.getElementById("editFotoOccupantId").value;
|
|
document.getElementById("editFotoDefaultView").style.display = 'none';
|
|
document.getElementById("editFotoLoadingView").style.display = 'block';
|
|
var fd = new FormData();
|
|
fd.append('id_occupant', id);
|
|
fetch('generate_foto_token.php', { method: 'POST', body: fd })
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(d) {
|
|
document.getElementById("editFotoLoadingView").style.display = 'none';
|
|
if (d.status === 'success') {
|
|
document.getElementById("editFotoQrView").style.display = 'block';
|
|
var canvas = document.getElementById("editFotoQrCanvas");
|
|
canvas.innerHTML = '';
|
|
document.getElementById("editFotoLinkText").value = d.url;
|
|
new QRCode(canvas, {
|
|
text: d.url,
|
|
width: 180,
|
|
height: 180,
|
|
colorDark: '#1e1b4b',
|
|
colorLight: '#ffffff',
|
|
correctLevel: QRCode.CorrectLevel.H
|
|
});
|
|
} else {
|
|
showCustomAlert("Failed to generate QR");
|
|
document.getElementById("editFotoDefaultView").style.display = 'block';
|
|
}
|
|
}).catch(function(e) {
|
|
document.getElementById("editFotoLoadingView").style.display = 'none';
|
|
showCustomAlert("Connection failed while generating QR.");
|
|
document.getElementById("editFotoDefaultView").style.display = 'block';
|
|
});
|
|
}
|
|
function copyEditFotoLink() {
|
|
navigator.clipboard.writeText(document.getElementById('editFotoLinkText').value);
|
|
showCustomAlert('Edit Photo link copied successfully!');
|
|
}
|
|
function downloadEditFotoQr() {
|
|
var qrCanvas = document.querySelector('#editFotoQrCanvas canvas');
|
|
if (!qrCanvas) { showCustomAlert("QR Code has not been generated yet."); return; }
|
|
var imgUrl = createCompositeQrImage(qrCanvas);
|
|
var dlLink = document.createElement('a');
|
|
dlLink.download = 'QR_Perbarui_Foto.png';
|
|
dlLink.href = imgUrl;
|
|
dlLink.click();
|
|
}
|
|
function closeEditFotoModal() { $("#modalEditFoto").modal("hide"); location.reload(); }
|
|
|
|
function toggleStatus(id) { $.post("toggle_status_occupant.php", { id_occupant: id }, function(d) { if (d.status === "success") location.reload(); else showCustomAlert(d.message || "Failed to process."); }, "json"); }
|
|
|
|
function quickRegisterFinger(code) {
|
|
if (!code) { showCustomAlert("Error: Kode pendaftaran tidak ditemukan."); return; }
|
|
document.getElementById("reg_finger_code_fast").value = code;
|
|
var regCb = document.getElementById('registerConfirmCheck');
|
|
var regBtn = document.getElementById('btnNowRegisterFinger');
|
|
if(regCb) regCb.checked = false;
|
|
if(regBtn) regBtn.disabled = true;
|
|
$("#modalRegisterFingerConfirm").modal("show");
|
|
}
|
|
|
|
var fingerPollInterval = null;
|
|
|
|
function executeRegisterFinger() {
|
|
var code = document.getElementById("reg_finger_code_fast").value;
|
|
document.getElementById("reg_finger_poll_code").value = code;
|
|
// Reset modal to waiting state
|
|
document.getElementById("fingerWaitingView").style.display = "block";
|
|
document.getElementById("fingerSuccessView").style.display = "none";
|
|
$("#modalRegisterFingerConfirm").modal("hide");
|
|
$("#modalRegisterFingerProgress").modal("show");
|
|
$.post("api_wifi.php", { action: "set_tft_screen", screen: "SCR_REG_FINGER", form_code: code }, function(d) {
|
|
if (!d.success) {
|
|
showCustomAlert("Failed to start registration on device.");
|
|
}
|
|
}, "json");
|
|
// Start polling for finger_id every 2 seconds
|
|
startFingerPoll(code);
|
|
}
|
|
|
|
function startFingerPoll(code) {
|
|
stopFingerPoll();
|
|
fingerPollInterval = setInterval(function() {
|
|
$.get("api_wifi.php", { action: "check_finger_status", code: code }, function(d) {
|
|
if (d.status === "success" && d.finger_id) {
|
|
stopFingerPoll();
|
|
// Show success view
|
|
document.getElementById("fingerWaitingView").style.display = "none";
|
|
document.getElementById("fingerSuccessView").style.display = "block";
|
|
document.getElementById("fingerSuccessId").textContent = d.finger_id;
|
|
}
|
|
}, "json");
|
|
}, 2000);
|
|
}
|
|
|
|
function stopFingerPoll() {
|
|
if (fingerPollInterval) {
|
|
clearInterval(fingerPollInterval);
|
|
fingerPollInterval = null;
|
|
}
|
|
}
|
|
|
|
function cancelRegisterFinger() {
|
|
stopFingerPoll();
|
|
$("#modalRegisterFingerProgress").modal("hide");
|
|
$.post("api_wifi.php", { action: "set_tft_screen", screen: "SCR_MAIN" }, function(d) {
|
|
// Returned to main
|
|
}, "json");
|
|
}
|
|
|
|
function closeFingerSuccessModal() {
|
|
stopFingerPoll();
|
|
$("#modalRegisterFingerProgress").modal("hide");
|
|
location.reload();
|
|
}
|
|
|
|
// PIN verify QR
|
|
function closePinModal() {
|
|
$("#modalVerifikasiPin").modal("hide");
|
|
}
|
|
|
|
function openPinVerifikasiModal(id) {
|
|
document.getElementById("verifikasiPinOccupantId").value = id;
|
|
document.getElementById("verifikasiPinDefaultView").style.display = "block";
|
|
document.getElementById("verifikasiPinLoadingView").style.display = "none";
|
|
document.getElementById("verifikasiPinQrView").style.display = "none";
|
|
|
|
// Auto generate QR on open
|
|
$("#modalVerifikasiPin").modal("show");
|
|
generatePinQr();
|
|
}
|
|
|
|
function generatePinQr() {
|
|
var id = document.getElementById("verifikasiPinOccupantId").value;
|
|
document.getElementById("verifikasiPinDefaultView").style.display = 'none';
|
|
document.getElementById("verifikasiPinLoadingView").style.display = 'block';
|
|
|
|
var fd = new FormData();
|
|
fd.append('id_occupant', id);
|
|
|
|
fetch('generate_pin_token.php', { method: 'POST', body: fd })
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(d) {
|
|
document.getElementById("verifikasiPinLoadingView").style.display = 'none';
|
|
if (d.status === 'success') {
|
|
document.getElementById("verifikasiPinQrView").style.display = 'block';
|
|
document.getElementById("verifikasiPinLinkText").value = d.url;
|
|
var canvas = document.getElementById("verifikasiPinQrCanvas");
|
|
canvas.innerHTML = '';
|
|
new QRCode(canvas, {
|
|
text: d.url,
|
|
width: 200,
|
|
height: 200,
|
|
colorDark: '#0f172a',
|
|
colorLight: '#ffffff',
|
|
correctLevel: QRCode.CorrectLevel.H
|
|
});
|
|
} else {
|
|
alert("Gagal generate QR: " + (d.message || "Unknown error"));
|
|
document.getElementById("verifikasiPinDefaultView").style.display = 'block';
|
|
}
|
|
})
|
|
.catch(function(e) {
|
|
document.getElementById("verifikasiPinLoadingView").style.display = 'none';
|
|
alert("Terjadi kesalahan jaringan.");
|
|
document.getElementById("verifikasiPinDefaultView").style.display = 'block';
|
|
});
|
|
}
|
|
|
|
function copyPinQrLink() {
|
|
navigator.clipboard.writeText(document.getElementById('verifikasiPinLinkText').value);
|
|
showCustomAlert('Link verifikasi PIN berhasil disalin!');
|
|
}
|
|
|
|
function downloadPinQr() {
|
|
var qrCanvas = document.querySelector('#verifikasiPinQrCanvas canvas');
|
|
if (!qrCanvas) {
|
|
alert("QR Code has not been generated yet.");
|
|
return;
|
|
}
|
|
|
|
var imgUrl = createCompositeQrImage(qrCanvas);
|
|
var dlLink = document.createElement('a');
|
|
dlLink.download = 'QR_Verifikasi_PIN.png';
|
|
dlLink.href = imgUrl;
|
|
dlLink.click();
|
|
}
|
|
</script>
|
|
|
|
<script>
|
|
// ── 5-minute auto-close timer for Kode Registrasi modal ──
|
|
var codeRegTimerId = null;
|
|
var codeRegInterval = null;
|
|
|
|
function startCodeRegisterTimer() {
|
|
var remaining = 300; // 5 minutes in seconds
|
|
var countdownEl = document.getElementById('codeRegisterCountdown');
|
|
function updateDisplay() {
|
|
var m = Math.floor(remaining / 60);
|
|
var s = remaining % 60;
|
|
countdownEl.innerHTML = '<i class="mdi mdi-timer-outline mr-1"></i>' + String(m).padStart(2,'0') + ':' + String(s).padStart(2,'0');
|
|
// Change color when less than 1 minute
|
|
if (remaining <= 60) {
|
|
countdownEl.style.color = '#dc2626';
|
|
countdownEl.style.animation = 'pulse-timer 1s ease-in-out infinite';
|
|
} else if (remaining <= 120) {
|
|
countdownEl.style.color = '#f59e0b';
|
|
} else {
|
|
countdownEl.style.color = '#dc2626';
|
|
countdownEl.style.animation = 'none';
|
|
}
|
|
}
|
|
updateDisplay();
|
|
codeRegInterval = setInterval(function() {
|
|
remaining--;
|
|
updateDisplay();
|
|
if (remaining <= 0) {
|
|
clearInterval(codeRegInterval);
|
|
codeRegInterval = null;
|
|
$('#modalCodeRegister').modal('hide');
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
function stopCodeRegisterTimer() {
|
|
if (codeRegInterval) { clearInterval(codeRegInterval); codeRegInterval = null; }
|
|
if (codeRegTimerId) { clearTimeout(codeRegTimerId); codeRegTimerId = null; }
|
|
}
|
|
|
|
function openPhotoPreview(imgSrc) {
|
|
document.getElementById('previewModalImage').src = imgSrc;
|
|
document.getElementById('btnDownloadPreview').href = imgSrc;
|
|
$("#modalPhotoPreview").modal("show");
|
|
}
|
|
|
|
$(document).ready(function() {
|
|
window.isSelectMode = false;
|
|
$('#btnSelectMode').on('click', function() {
|
|
window.isSelectMode = !window.isSelectMode;
|
|
if(window.isSelectMode) {
|
|
$(this).html('<i class="mdi mdi-close"></i>').removeClass('btn-outline-secondary').addClass('btn-light').attr('title', 'Cancel Selection');
|
|
$('#btnBulkDelete').css('display', 'inline-block');
|
|
$('.btn-row-delete').hide();
|
|
$('.expand-arrow').hide();
|
|
$('.bulk-cb').css('display', 'inline-block').prop('checked', false);
|
|
} else {
|
|
$(this).html('<i class="mdi mdi-checkbox-multiple-marked-outline"></i>').removeClass('btn-light').addClass('btn-outline-secondary').attr('title', 'Select Multiple');
|
|
$('#btnBulkDelete').hide();
|
|
$('.btn-row-delete').css('display', 'inline-block');
|
|
$('.expand-arrow').css('display', 'inline-block');
|
|
$('.bulk-cb').hide().prop('checked', false);
|
|
}
|
|
});
|
|
|
|
$('#btnBulkDelete').on('click', function() {
|
|
const selected = $('.bulk-cb:checked').map(function() { return $(this).val(); }).get();
|
|
if(selected.length === 0) {
|
|
showCustomAlert('Pilih minimal satu data untuk dihapus!', 'warning');
|
|
return;
|
|
}
|
|
window.bulkDeleteIds = selected;
|
|
document.getElementById("delete_id_occupant").value = "BULK";
|
|
document.getElementById("delete_occupant_name").innerText = selected.length + " data terpilih";
|
|
$("#modalDeleteOccupant").modal("show");
|
|
});
|
|
$("#formDeleteOccupant").submit(function(e) {
|
|
e.preventDefault();
|
|
var val = $("#delete_id_occupant").val();
|
|
if(val === "BULK") {
|
|
$("#btnDeleteOccupant").prop("disabled",true);
|
|
$("#btnDeleteOccupant").html('<i class="mdi mdi-loading mdi-spin mr-1"></i>Menghapus...');
|
|
let promises = window.bulkDeleteIds.map(id => {
|
|
let fd = new FormData();
|
|
fd.append('id_occupant', id);
|
|
return fetch('delete_occupant.php', { method: 'POST', body: fd }).then(r => r.json());
|
|
});
|
|
Promise.all(promises).then(() => {
|
|
$("#deleteSuccessAlert").css("display","block");
|
|
setTimeout(function(){ $("#modalDeleteOccupant").modal("hide"); location.reload(); }, 1200);
|
|
});
|
|
} else {
|
|
$.post("delete_occupant.php", $(this).serialize(), function(d) {
|
|
if (d.status === "success") {
|
|
$("#deleteSuccessAlert").css("display","block");
|
|
$("#btnDeleteOccupant").prop("disabled",true);
|
|
setTimeout(function(){ $("#modalDeleteOccupant").modal("hide"); location.reload(); }, 1200);
|
|
} else {
|
|
showCustomAlert(d.message);
|
|
}
|
|
}, "json");
|
|
}
|
|
});
|
|
// Start timer when modal opens
|
|
$('#modalCodeRegister').on('shown.bs.modal', function(){ startCodeRegisterTimer(); });
|
|
// Stop timer + reload when modal closes
|
|
$('#modalCodeRegister').on('hidden.bs.modal', function(){ stopCodeRegisterTimer(); location.reload(); });
|
|
$('#modalTambah').on('hidden.bs.modal', function(){ document.getElementById("formTambahOccupant").reset(); document.getElementById("previewFoto").src = "images/faces/user.jpg"; document.getElementById("passwordMismatch").style.display = "none"; });
|
|
$('#modalEdit').on('hidden.bs.modal', function(){ document.getElementById("editPasswordMismatch").style.display = "none"; });
|
|
$('#modalEditFoto').on('hidden.bs.modal', function(){ location.reload(); });
|
|
|
|
// Modal Resets for Checkboxes
|
|
$('#modalDeleteOccupant').on('hidden.bs.modal', function() {
|
|
var cb = document.getElementById('deleteConfirmCheckOccupant');
|
|
var btn = document.getElementById('btnDeleteOccupant');
|
|
if(cb) cb.checked = false;
|
|
if(btn) btn.disabled = true;
|
|
});
|
|
|
|
$('#modalRegisterFingerConfirm').on('hidden.bs.modal', function() {
|
|
var cb = document.getElementById('registerConfirmCheck');
|
|
var btn = document.getElementById('btnNowRegisterFinger');
|
|
if(cb) cb.checked = false;
|
|
if(btn) btn.disabled = true;
|
|
});
|
|
|
|
// DataTable
|
|
if ($.fn.DataTable.isDataTable('#myTable')) { $('#myTable').DataTable().destroy(); }
|
|
var table = $('#myTable').DataTable({ "columnDefs": [{ "orderable": false, "targets": 5 }, { "width": "40px", "targets": 5 }], "order": [[0, 'asc']] });
|
|
|
|
// Child row
|
|
$('#myTable tbody').on('click', 'td.details-control', function(e) {
|
|
if(e.target.tagName === 'INPUT') return; // Let checkbox work
|
|
var tr = $(this).closest('tr'), row = table.row(tr);
|
|
if (row.child.isShown()) { row.child.hide(); tr.removeClass('shown'); return; }
|
|
table.rows('.shown').every(function() { this.child.hide(); $(this.node()).removeClass('shown'); });
|
|
|
|
var d = { id: $(this).data("id"), name: $(this).data("name"), nik: $(this).data("nik"), no_hp: $(this).data("no_hp"), foto: $(this).data("foto"), hasfoto: $(this).data("hasfoto"), fingerid: $(this).data("fingerid"), fingerok: $(this).data("fingerok"), code: $(this).data("code"), coderaw: $(this).data("coderaw"), progress: $(this).data("progress"), status: $(this).data("status"), access: $(this).data("access") };
|
|
|
|
var progPill = d.progress === 'used' ? '<span style="display:inline-flex; align-items:center; font-weight:700; font-size:12px; color:#10b981;"><i class="mdi mdi-check mr-1" style="font-size:14px;"></i>Used</span>' : '<span style="display:inline-flex; align-items:center; font-weight:500; font-size:12px; color:#111;"><i class="mdi mdi-clock-outline mr-1" style="font-size:14px;"></i>Unused</span>';
|
|
var accPill = '';
|
|
if (d.access === 'active') {
|
|
accPill = '<span style="display:inline-flex; align-items:center; background:#e6f4ea; padding:2px 10px; border-radius:12px; color:#137333; font-weight:700; font-size:11px;"><i class="mdi mdi-lock-open-check mr-1" style="color:#1e2223; font-size:14px;"></i>Active</span>';
|
|
} else if (d.access === 'suspend') {
|
|
accPill = '<span style="display:inline-flex; align-items:center; background:#f1f3f4; padding:2px 10px; border-radius:12px; color:#5f6368; font-weight:700; font-size:11px;"><i class="mdi mdi-lock mr-1" style="color:#1e2223; font-size:14px;"></i>Suspend</span>';
|
|
} else {
|
|
accPill = '<span style="display:inline-flex; align-items:center; background:#fef7e0; padding:2px 10px; border-radius:12px; color:#e37400; font-weight:700; font-size:11px;"><i class="mdi mdi-lock-clock mr-1" style="color:#1e2223; font-size:14px;"></i>Pending</span>';
|
|
}
|
|
|
|
var sN = String(d.name).replace(/'/g, "\\'");
|
|
var sNik = String(d.nik || '').replace(/'/g, "\\'");
|
|
var sHp = String(d.no_hp).replace(/'/g, "\\'");
|
|
var sFoto = String(d.foto || '').replace(/'/g, "\\'");
|
|
|
|
var html = '' +
|
|
'<div style="background:transparent; padding: 0; display: flex; align-items: stretch; justify-content: space-between; gap: 0; animation: ddSlideIn 0.3s ease; min-height: 50px;">' + /* CHANGE DROP-DOWN COLOR HERE (HTML Container) */
|
|
|
|
// Left Section (White Edge-to-Edge ID Card)
|
|
'<div style="background: #fff; border: 1px solid #e2e8f0; border-radius: 10px; margin: 6px 15px 6px 6px; width: 260px; display: flex; flex-direction: column; justify-content: center; padding: 8px 12px; box-sizing: border-box; flex-shrink: 0; box-shadow: 0 4px 10px rgba(0,0,0,0.02);">' +
|
|
// Top Row (Name & Avatar)
|
|
'<div style="display: flex; justify-content: space-between; align-items: flex-start;">' +
|
|
'<div style="padding-right: 10px; flex: 1; overflow: hidden;">' +
|
|
'<div style="font-size:14px; color:#212529; font-weight:600; line-height: 1.2; margin-bottom: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">' + (d.name || '') + '</div>' +
|
|
'<div style="font-size:11px; color:#6c757d; font-weight:500; display: flex; align-items: center;"><i class="mdi mdi-phone mr-1" style="font-size:12px; color:#a1a1aa;"></i>' + (d.no_hp || '—') + '</div>' +
|
|
'</div>' +
|
|
'<div style="display:flex; align-items:center; flex-shrink: 0;">' +
|
|
(d.hasfoto == '0' ? '<button class="btn btn-sm btn-primary p-1 mr-2" onclick="showQrFoto(\'' + d.id + '\')" style="border-radius:6px; font-weight:bold; font-size:10px; color:#fff; border:none; background:#4b49ac;"><i class="mdi mdi-qrcode"></i></button>' : '<img src="images/download.png" class="mr-2" style="width: 24px; height: 24px; object-fit: contain; opacity:0.8; border-radius: 2px;">') +
|
|
(d.hasfoto == '1' ? '<img src="' + d.foto + '" onclick="openPhotoPreview(\'' + sFoto + '\')" style="cursor: pointer; width: 36px; height: 36px; border-radius: 8px; object-fit: cover; border: 1px solid #e2e8f0; box-shadow: 0 2px 4px rgba(0,0,0,0.02); transition: transform 0.2s;" onmouseover="this.style.transform=\'scale(1.05)\'" onmouseout="this.style.transform=\'scale(1)\'">' : '<img src="' + d.foto + '" style="cursor: not-allowed; width: 36px; height: 36px; border-radius: 8px; object-fit: cover; border: 1px solid #e2e8f0; box-shadow: 0 2px 4px rgba(0,0,0,0.02); opacity: 0.7;">') +
|
|
'</div>' +
|
|
'</div>' +
|
|
|
|
// Bottom Row (NIK/NIP)
|
|
'<div style="margin-top: 12px; display: flex; justify-content: space-between; align-items: flex-end;">' +
|
|
'<div>' +
|
|
'<div style="font-size:10px; color:#a1a1aa; font-weight:500; text-transform:uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">NIK / NIP</div>' +
|
|
'<div style="font-size:13px; font-family:\'Courier New\', monospace; color:#495057; font-weight:bold; letter-spacing: 1.2px;">' + (d.nik || '—') + '</div>' +
|
|
'</div>' +
|
|
'<div style="font-size:9px; color:#9ca3af; font-weight:600;"><i class="mdi mdi-card-account-details mr-1"></i>ID CARD</div>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
|
|
// Middle Section (Details Grid)
|
|
'<div style="flex: 1; display: flex; align-items: center; justify-content: space-evenly; padding: 0 10px;">' +
|
|
'<div style="text-align:center; padding: 0 10px;"><div style="font-size:11px; color:#495057; font-weight:500; margin-bottom: 4px;">Code Reg</div><div style="font-family:\'Courier New\', monospace; font-size:13px; color:#4b49ac; display:inline-block; font-weight:bold; letter-spacing:1px;">' + (d.code || '—') + '</div></div>' +
|
|
'<div style="text-align:center; padding: 0 10px;"><div style="font-size:11px; color:#495057; font-weight:500; margin-bottom: 6px;">Progress</div><div>' + progPill + '</div></div>' +
|
|
'<div style="text-align:center; padding: 0 10px;"><div style="font-size:11px; color:#495057; font-weight:500; margin-bottom: 6px;">Access</div><div>' + accPill + '</div></div>' +
|
|
'<div style="text-align:center; padding: 0 10px;"><div style="font-size:11px; color:#495057; font-weight:500; margin-bottom: 4px;">PIN</div><div style="display:flex; align-items:center; justify-content:center;"><span style="font-size:14px; font-weight:bold; letter-spacing:2px; margin-right:8px; color:#212529;">••••••</span> ' + (d.hasfoto == '1' ? '<button class="btn btn-custom-dashboard font-weight-bold" style="padding:2px 8px; font-size:10px; border-radius:6px; font-family:sans-serif; height:24px; display:inline-flex; align-items:center; justify-content:center;" onclick="openPinVerifikasiModal(\'' + d.id + '\')"><i class="mdi mdi-qrcode-scan" style="font-size:13px; margin-right:4px;"></i>Lihat</button>' : '<button class="btn btn-secondary font-weight-bold" disabled style="padding:2px 8px; font-size:10px; border-radius:6px; font-family:sans-serif; height:24px; display:inline-flex; align-items:center; justify-content:center; border:none; cursor:not-allowed;" title="Foto belum didaftarkan. Harap daftar-kan foto terlebih dahulu."><i class="mdi mdi-qrcode-scan" style="font-size:13px; margin-right:4px;"></i>Lihat</button>') + '</div></div>' +
|
|
'</div>' +
|
|
|
|
// Right-Middle Section (Fingerprint Icon)
|
|
'<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100px; padding: 0 10px;">' +
|
|
'<div style="width: 36px; height: 36px; background: transparent; display: flex; align-items: center; justify-content: center; margin-bottom: 4px;">' +
|
|
'<i class="mdi mdi-fingerprint" style="font-size: 33px; color: #1e2223;"></i>' +
|
|
'</div>' +
|
|
'<div style="font-size:11px; color:#202124; margin-bottom: 2px; padding:0;">Fingerprint id</div>' +
|
|
(d.fingerok=='1' ?
|
|
'<div style="font-family:\'Courier New\', monospace; font-size:13px; color:#202124; font-weight:bold; padding:1px 4px;">' + d.fingerid + '</div>'
|
|
:
|
|
(d.hasfoto == '1' ?
|
|
'<div style="margin-bottom: 2px;"><button onclick="quickRegisterFinger(\'' + d.coderaw + '\')" style="font-size:10px; font-weight:bold; color:#202124; padding:0; border:none; border-bottom:1px solid #777; background:none; outline:none; text-transform:none; cursor:pointer;">Register</button></div>'
|
|
:
|
|
'<div style="margin-bottom: 2px;"><button disabled style="font-size:10px; font-weight:bold; color:#9ba3af; padding:0; border:none; border-bottom:1px solid #9ba3af; background:none; outline:none; text-transform:none; cursor:not-allowed;" title="Foto belum didaftarkan. Harap daftar-kan foto terlebih dahulu.">Register</button></div>'
|
|
)
|
|
) +
|
|
'</div>' +
|
|
|
|
// Far-Right Section (Actions)
|
|
'<div style="display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 6px; width: 100px;">' +
|
|
'<button class="btn btn-sm btn-custom-edit" onclick="openEditModal(\'' + d.id + '\',\'' + sN + '\',\'' + sNik + '\',\'' + sHp + '\',\'' + sFoto + '\')" style="border-radius:6px; font-weight:bold; letter-spacing:0.5px; width:44px; padding: 6px; font-size:14px;"><i class="mdi mdi-pencil"></i></button>' +
|
|
'<button class="btn btn-sm btn-danger text-white btn-row-delete" onclick="openDeleteModal(\'' + d.id + '\',\'' + sN + '\')" style="border-radius:6px; font-weight:bold; letter-spacing:0.5px; width:44px; padding: 6px; font-size:14px; ' + (window.isSelectMode ? 'display:none;' : 'display:inline-block;') + '"><i class="mdi mdi-delete"></i></button>' +
|
|
'</div>' +
|
|
|
|
'</div>';
|
|
|
|
row.child(html).show();
|
|
tr.addClass('shown');
|
|
});
|
|
});
|
|
</script>
|