TKK_E32230273/report.php

729 lines
29 KiB
PHP

<?php
$occ_id = isset($_GET['occ_id']) ? $_GET['occ_id'] : '';
$token = isset($_GET['token']) ? $_GET['token'] : '';
if (empty($occ_id) || empty($token)) {
header("Location: 404.php");
exit;
}
$usedTokensFile = __DIR__ . '/used_report_tokens.json';
$usedTokens = [];
if (file_exists($usedTokensFile)) {
$usedTokens = json_decode(file_get_contents($usedTokensFile), true) ?: [];
}
if (isset($usedTokens[$token])) {
header("Location: 404.php");
exit;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=no">
<title>Activity Report — Identia</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="vendors/mdi/css/materialdesignicons.min.css">
<style>
:root {
--primary: #4747a1;
--primary-light: #e6e6f2;
--primary-glow: rgba(71,71,161,0.3);
--success: #10b981;
--danger: #ef4444;
--warning: #f59e0b;
--bg: #f8fafc;
--card: #ffffff;
--text: #0f172a;
--muted: #64748b;
--border: #e2e8f0;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
-webkit-font-smoothing: antialiased;
}
body {
display: flex;
justify-content: center;
align-items: flex-start;
padding: 20px 16px 40px;
}
/* ── MAIN CARD ── */
.card {
background: var(--card);
border-radius: 20px;
width: 100%; max-width: 460px;
padding: 32px 28px 36px;
border: 1px solid var(--border);
}
/* ── HEADER ── */
.card-header {
display: flex; align-items: center; gap: 16px;
margin-bottom: 28px;
}
.header-icon {
width: 52px; height: 52px; border-radius: 16px;
background: var(--primary-light);
display: flex; align-items: center; justify-content: center;
flex-shrink: 0;
}
.header-icon i { font-size: 26px; color: var(--primary); }
.header-text h1 { font-size: 20px; font-weight: 800; color: var(--text); letter-spacing: -0.3px; }
.header-text p { font-size: 12px; color: var(--muted); margin-top: 3px; font-weight: 500; }
/* ── SECTION LABEL ── */
.section-label {
font-size: 11px; font-weight: 700; color: var(--primary);
text-transform: uppercase; letter-spacing: 1px;
margin-bottom: 10px; display: flex; align-items: center; gap: 6px;
}
.section-label::after { content: ''; flex: 1; height: 1px; background: var(--primary-light); }
/* ── CAMERA AREA ── */
.cam-wrapper {
width: 100%; position: relative;
border-radius: 16px; overflow: hidden;
background: #1a1e20;
aspect-ratio: 3/4;
margin-bottom: 8px;
border: 1px solid var(--border);
}
#cam-video {
width: 100%; height: 100%;
object-fit: cover; transform: scaleX(-1);
display: block;
}
#cam-canvas { display: none; }
#cam-photo {
display: none; width: 100%; height: 100%;
object-fit: cover; position: absolute; inset: 0; z-index: 3;
}
/* Scan Line — moves up & down */
#scan-line {
position: absolute; top: 0; left: 0;
width: 100%; height: 3px;
background: #ffffff;
box-shadow: 0 0 10px #ffffff, 0 0 20px rgba(255,255,255,0.5);
z-index: 4;
animation: scanMove 2.8s ease-in-out infinite;
transition: background 0.4s ease, box-shadow 0.4s ease;
}
#scan-line.face-ok {
background: #a855f7;
box-shadow: 0 0 10px #a855f7, 0 0 24px rgba(168,85,247,0.4);
}
@keyframes scanMove {
0% { top: 0%; }
50% { top: calc(100% - 3px); }
100% { top: 0%; }
}
/* Cam loading overlay */
#cam-loading {
position: absolute; inset: 0; background: #ffffff;
display: flex; flex-direction: column; align-items: center;
justify-content: center; gap: 16px; z-index: 10;
}
.ld-ring {
width: 44px; height: 44px; border-radius: 50%;
border: 3px solid var(--primary-light); border-top-color: var(--primary);
animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
#cam-loading p { font-size: 13px; color: var(--muted); font-weight: 600; }
/* Status pill */
#face-pill {
position: absolute; top: 16px; left: 50%; transform: translateX(-50%);
display: none; align-items: center; gap: 8px;
padding: 8px 18px; border-radius: 24px;
font-size: 12px; font-weight: 700; white-space: nowrap; z-index: 5;
background: rgba(255,255,255,0.9); color: var(--text);
backdrop-filter: blur(8px); border: 1px solid rgba(255,255,255,0.5);
transition: all 0.3s;
}
#face-pill.detected { background: #a855f7; color: #fff; border-color: #a855f7; }
#face-pill.detected i { color: #fff; }
#face-pill.warn { background: var(--danger); color: #fff; border-color: var(--danger); }
#face-pill i { font-size: 16px; }
/* Live metadata at bottom-left corner of camera */
#live-meta {
position: absolute; bottom: 0; left: 0; z-index: 5;
padding: 10px 12px;
background: linear-gradient(to top right, rgba(0,0,0,0.7) 0%, transparent 100%);
display: flex; flex-direction: column; gap: 2px;
pointer-events: none;
text-align: left;
border-bottom-left-radius: 12px;
}
#live-meta span {
font-size: 10px; color: rgba(255,255,255,0.95); font-weight: 600;
text-shadow: 0 1px 3px rgba(0,0,0,0.8); letter-spacing: 0.1px;
}
/* Capture / retake controls */
#cam-controls {
position: absolute; bottom: 20px; left: 0; width: 100%;
display: flex; justify-content: center; z-index: 6;
}
.btn-shutter {
width: 68px; height: 68px; border-radius: 50%;
border: 4px solid rgba(255,255,255,0.5);
background: rgba(255,255,255,0.15);
display: flex; align-items: center; justify-content: center;
cursor: pointer; transition: all 0.3s;
backdrop-filter: blur(4px);
}
.btn-shutter::after {
content: ''; width: 50px; height: 50px; border-radius: 50%;
background: #fff; transition: all 0.2s;
}
.btn-shutter:hover::after { transform: scale(0.95); }
.btn-shutter:active::after { transform: scale(0.8); background: var(--primary); }
.btn-shutter:disabled { opacity: 0.4; cursor: not-allowed; pointer-events: none; }
.btn-retake {
display: none; position: absolute; bottom: 20px; left: 50%;
transform: translateX(-50%);
background: rgba(239,68,68,0.9); color: #fff;
border: none; border-radius: 24px; padding: 10px 28px;
font-size: 13px; font-weight: 700; cursor: pointer;
font-family: 'Inter', sans-serif; z-index: 6;
backdrop-filter: blur(4px);
transition: background 0.2s;
}
.btn-retake i { margin-right: 6px; }
.btn-retake:hover { background: rgba(220,38,38,0.95); }
/* Hint below camera */
.cam-hint {
font-size: 11px; color: var(--muted); text-align: center;
font-weight: 500; margin-top: 6px; margin-bottom: 20px;
}
/* ── FORM FIELDS ── */
.form-group { margin-bottom: 20px; }
.form-group label {
display: block; font-size: 12px; font-weight: 700;
color: var(--text); margin-bottom: 8px;
}
.form-group label span { color: var(--danger); }
.input-wrap { position: relative; }
.input-wrap i {
position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
color: var(--muted); font-size: 18px;
}
textarea {
width: 100%; padding: 12px 14px 12px 42px;
background: #f8fafc; border: 1.5px solid var(--border);
border-radius: 12px; color: var(--text);
font-size: 14px; font-family: 'Inter', sans-serif;
font-weight: 500; transition: all 0.2s; resize: vertical;
min-height: 90px; line-height: 1.6;
}
textarea:focus {
outline: none; border-color: var(--primary);
background: #fff; box-shadow: 0 0 0 3px var(--primary-glow);
}
textarea::placeholder { color: #94a3b8; }
/* ── SUBMIT BTN ── */
.btn-submit {
width: 100%; padding: 16px;
background: var(--primary); color: #fff;
border: none; border-radius: 14px;
font-size: 15px; font-weight: 700;
cursor: pointer; font-family: 'Inter', sans-serif;
display: flex; align-items: center; justify-content: center; gap: 10px;
transition: all 0.2s;
margin-top: 8px;
}
.btn-submit:hover { transform: translateY(-2px); }
.btn-submit:active { transform: translateY(0); }
.btn-submit:disabled { opacity: 0.6; cursor: not-allowed; transform: none; }
/* ── CUSTOM ALERT ── */
.custom-alert {
position: fixed; top: 20px; right: 20px;
background: #fff; border-left: 4px solid #fcd34d;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
padding: 14px 18px; border-radius: 8px; z-index: 99999;
display: none; align-items: center; gap: 12px;
min-width: 280px; max-width: 380px;
animation: slideIn 0.3s ease-out;
font-family: 'Inter', sans-serif;
}
.custom-alert.error { border-left-color: var(--danger); }
.custom-alert.success { border-left-color: var(--success); }
@keyframes slideIn { from { transform: translateX(110%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
@keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } }
.ca-icon { font-size: 22px; }
.custom-alert.error .ca-icon { color: var(--danger); }
.custom-alert.success .ca-icon { color: var(--success); }
.custom-alert.warning .ca-icon { color: var(--warning); }
.ca-content { flex: 1; }
.ca-title { font-weight: 700; font-size: 13px; color: #1e293b; }
.ca-msg { font-size: 12px; color: #64748b; margin-top: 2px; line-height: 1.4; }
.ca-close { cursor: pointer; color: #94a3b8; font-size: 16px; }
.ca-close:hover { color: #475569; }
@media (max-width: 480px) {
body { padding: 12px 10px 32px; }
.card { padding: 24px 18px 28px; border-radius: 16px; }
.custom-alert { top: 10px; right: 10px; left: 10px; }
}
</style>
</head>
<body>
<!-- Main Form -->
<div class="card">
<div class="card-header">
<div class="header-icon"><i class="mdi mdi-clipboard-text"></i></div>
<div class="header-text">
<h1>Activity Report</h1>
<p>Face verification is required</p>
</div>
</div>
<div class="section-label"><i class="mdi mdi-camera-account"></i> Face Verification</div>
<!-- Camera Wrapper -->
<div class="cam-wrapper" id="cam-wrapper">
<!-- Loading -->
<div id="cam-loading">
<div class="ld-ring"></div>
<p id="cam-load-text">Initializing camera...</p>
</div>
<!-- Scan Line -->
<div id="scan-line"></div>
<!-- Video feed -->
<video id="cam-video" autoplay playsinline muted></video>
<!-- Canvas (hidden, used for capture) -->
<canvas id="cam-canvas"></canvas>
<!-- Face status pill -->
<div id="face-pill">
<i class="mdi mdi-face-recognition" id="face-pill-icon"></i>
<span id="face-pill-text">Searching for face...</span>
</div>
<!-- Live metadata overlay (hidden during capture) -->
<div id="live-meta">
<span id="meta-time">--</span>
</div>
<!-- Captured photo -->
<img id="cam-photo" alt="Captured Photo">
<!-- Controls -->
<div id="cam-controls">
<button class="btn-shutter" id="btn-shutter" disabled title="Take Photo"></button>
</div>
<button class="btn-retake" id="btn-retake"><i class="mdi mdi-refresh"></i>Retake</button>
</div>
<div class="cam-hint" id="cam-hint"><i class="mdi mdi-information-outline"></i> Position your face and wait for detection before capturing</div>
<form id="reportForm" action="#" method="POST">
<input type="hidden" name="photo_data" id="photo_data">
<div class="section-label"><i class="mdi mdi-text-box-edit"></i> Activity Details</div>
<div class="form-group">
<label for="activity">Activity Description <span>*</span></label>
<div class="input-wrap">
<i class="mdi mdi-pencil-outline"></i>
<textarea id="activity" name="activity" rows="3"
placeholder="Describe your current activity (e.g., AC Maintenance, Safety Inspection)..." required></textarea>
</div>
</div>
<button type="submit" class="btn-submit" id="btn-submit">
<i class="mdi mdi-send"></i> Submit Report
</button>
</form>
</div>
<!-- Custom Alert -->
<div class="custom-alert" id="customAlert">
<i class="mdi mdi-alert-circle ca-icon" id="ca-icon"></i>
<div class="ca-content">
<div class="ca-title" id="ca-title">Notice</div>
<div class="ca-msg" id="ca-msg"></div>
</div>
<i class="mdi mdi-close ca-close" onclick="closeAlert()"></i>
</div>
<script src="js/face-api/face-api.min.js"></script>
<script>
(function() {
// ── DOM refs ──
const camLoading = document.getElementById('cam-loading');
const camLoadText = document.getElementById('cam-load-text');
const camVideo = document.getElementById('cam-video');
const camCanvas = document.getElementById('cam-canvas');
const camPhoto = document.getElementById('cam-photo');
const scanLine = document.getElementById('scan-line');
const facePill = document.getElementById('face-pill');
const facePillIcon = document.getElementById('face-pill-icon');
const facePillText = document.getElementById('face-pill-text');
const liveMeta = document.getElementById('live-meta');
const metaTime = document.getElementById('meta-time');
const camControls = document.getElementById('cam-controls');
const btnShutter = document.getElementById('btn-shutter');
const btnRetake = document.getElementById('btn-retake');
const camHint = document.getElementById('cam-hint');
const photoDataInput= document.getElementById('photo_data');
// ── State ──
let stream = null;
let detectLoop = null;
let faceDetected = false;
let modelsLoaded = false;
let timeInterval = null;
// ── Custom Alert ──
function showAlert(title, msg, type = 'warning') {
const box = document.getElementById('customAlert');
const icon = document.getElementById('ca-icon');
box.className = 'custom-alert ' + type;
if (type === 'error') icon.className = 'mdi mdi-close-circle ca-icon';
else if (type === 'success') icon.className = 'mdi mdi-check-circle ca-icon';
else icon.className = 'mdi mdi-alert-circle ca-icon';
document.getElementById('ca-title').textContent = title;
document.getElementById('ca-msg').textContent = msg;
box.style.display = 'flex';
setTimeout(closeAlert, 5000);
}
function closeAlert() {
const box = document.getElementById('customAlert');
box.style.animation = 'fadeOut 0.3s ease-out forwards';
setTimeout(() => { box.style.display = 'none'; box.style.animation = ''; }, 300);
}
window.closeAlert = closeAlert;
// ── Time ticker ──
function updateTime() {
const now = new Date();
const opts = { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' };
metaTime.textContent = '🕐 ' + now.toLocaleDateString('en-US', opts);
}
updateTime();
timeInterval = setInterval(updateTime, 1000);
// Start camera immediately
initCamera();
// ── Init Camera ──
async function initCamera() {
camLoading.style.display = 'flex';
camLoadText.textContent = 'Loading face detection models...';
try {
if (!modelsLoaded) {
await Promise.all([
faceapi.nets.tinyFaceDetector.loadFromUri('js/face-api/models'),
faceapi.nets.faceLandmark68Net.loadFromUri('js/face-api/models')
]);
modelsLoaded = true;
}
camLoadText.textContent = 'Starting camera...';
const constraints = {
video: { facingMode: 'user', width: { ideal: 720 }, height: { ideal: 960 } },
audio: false
};
stream = await navigator.mediaDevices.getUserMedia(constraints);
camVideo.srcObject = stream;
await new Promise((resolve) => {
camVideo.onloadedmetadata = () => camVideo.play().then(resolve).catch(resolve);
setTimeout(resolve, 8000);
});
camLoading.style.display = 'none';
facePill.style.display = 'flex';
startDetection();
} catch (err) {
camLoading.style.display = 'none';
let msg = 'Camera failed to start.';
if (err.name === 'NotAllowedError') msg = 'Camera permission denied. Please allow camera access.';
else if (err.name === 'NotFoundError') msg = 'No camera found on this device.';
showAlert('Camera Error', msg, 'error');
}
}
// ── Face Detection ──
function startDetection() {
const opts = new faceapi.TinyFaceDetectorOptions({ inputSize: 224, scoreThreshold: 0.45 });
detectLoop = setInterval(async () => {
if (!camVideo.videoWidth || camVideo.paused) return;
const results = await faceapi.detectAllFaces(camVideo, opts).run();
if (results.length === 1) {
const box = results[0].box;
const vw = camVideo.videoWidth, vh = camVideo.videoHeight;
const area = (box.width * box.height) / (vw * vh);
const cx = box.x + box.width / 2, cy = box.y + box.height / 2;
const dx = Math.abs(cx - vw / 2) / vw, dy = Math.abs(cy - vh / 2) / vh;
if (area < 0.04) {
setFacePill('warn', 'mdi-arrow-up-bold', 'Move closer');
setFaceState(false);
} else if (area > 0.65) {
setFacePill('warn', 'mdi-arrow-down-bold', 'Move back');
setFaceState(false);
} else if (dx > 0.22 || dy > 0.22) {
setFacePill('warn', 'mdi-arrow-all', 'Center your face');
setFaceState(false);
} else {
setFacePill('detected', 'mdi-check-circle', 'Face Detected — Ready');
setFaceState(true);
}
} else if (results.length > 1) {
setFacePill('warn', 'mdi-account-multiple', 'Only 1 face allowed');
setFaceState(false);
} else {
setFacePill(null, 'mdi-face-recognition', 'Searching for face...');
setFaceState(false);
}
}, 350);
}
function setFacePill(type, icon, text) {
facePill.className = 'face-pill';
if (type) facePill.classList.add(type);
facePillIcon.className = 'mdi ' + icon;
facePillText.textContent = text;
// Also style the id-based pill
facePill.id = 'face-pill';
if (type === 'detected') {
facePill.style.cssText = 'display:flex; position:absolute; top:16px; left:50%; transform:translateX(-50%); align-items:center; gap:8px; padding:8px 18px; border-radius:24px; font-size:12px; font-weight:700; white-space:nowrap; z-index:5; background:#a855f7; color:#fff; backdrop-filter:blur(8px); border:1px solid #a855f7; transition:all 0.3s;';
} else if (type === 'warn') {
facePill.style.cssText = 'display:flex; position:absolute; top:16px; left:50%; transform:translateX(-50%); align-items:center; gap:8px; padding:8px 18px; border-radius:24px; font-size:12px; font-weight:700; white-space:nowrap; z-index:5; background:var(--danger); color:#fff; backdrop-filter:blur(8px); border:1px solid var(--danger); transition:all 0.3s;';
} else {
facePill.style.cssText = 'display:flex; position:absolute; top:16px; left:50%; transform:translateX(-50%); align-items:center; gap:8px; padding:8px 18px; border-radius:24px; font-size:12px; font-weight:700; white-space:nowrap; z-index:5; background:rgba(255,255,255,0.9); color:#0f172a; backdrop-filter:blur(8px); border:1px solid rgba(255,255,255,0.5); transition:all 0.3s;';
}
}
function setFaceState(ok) {
faceDetected = ok;
if (ok) {
scanLine.classList.add('face-ok');
btnShutter.disabled = false;
} else {
scanLine.classList.remove('face-ok');
btnShutter.disabled = true;
}
}
// ── Capture ──
btnShutter.addEventListener('click', () => {
if (!faceDetected) return;
clearInterval(detectLoop);
// Hide alert pill immediately on capture
facePill.style.display = 'none';
const vw = camVideo.videoWidth, vh = camVideo.videoHeight;
camCanvas.width = vw; camCanvas.height = vh;
const ctx = camCanvas.getContext('2d');
// Mirror flip (match camera display)
ctx.save();
ctx.translate(vw, 0);
ctx.scale(-1, 1);
ctx.drawImage(camVideo, 0, 0, vw, vh);
ctx.restore();
// Overlay: dark gradient at bottom
const gradH = Math.max(120, Math.floor(vh * 0.22));
const grad = ctx.createLinearGradient(0, vh - gradH, 0, vh);
grad.addColorStop(0, 'rgba(0,0,0,0)');
grad.addColorStop(1, 'rgba(0,0,0,0.75)');
ctx.fillStyle = grad;
ctx.fillRect(0, vh - gradH, vw, gradH);
// Overlay: date/time + location text
const now = new Date();
const dateStr = now.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
const timeStr = now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
const baseFontSize = Math.max(12, Math.floor(vw * 0.030)); // slightly smaller
const smallFont = Math.max(10, Math.floor(vw * 0.024));
// Calculate total text block height to bottom-align
const textBlockHeight = baseFontSize + 4;
const startY = vh - textBlockHeight - 16; // 16px bottom padding
ctx.textBaseline = 'alphabetic';
// Draw time
ctx.font = `bold ${baseFontSize}px Inter, Arial, sans-serif`;
ctx.fillStyle = '#ffffff';
ctx.fillText(`${dateStr} ${timeStr}`, 26, startY);
const dataURL = camCanvas.toDataURL('image/jpeg', 0.88);
camPhoto.src = dataURL;
camPhoto.style.display = 'block';
photoDataInput.value = dataURL;
// UI updates
camVideo.style.display = 'none';
scanLine.style.display = 'none';
liveMeta.style.display = 'none';
camControls.style.display = 'none';
facePill.style.display = 'none';
btnRetake.style.display = 'block';
camHint.innerHTML = '<i class="mdi mdi-check-circle" style="color:#10b981;"></i> Photo captured! Review and retake if needed.';
if (stream) stream.getTracks().forEach(t => t.stop());
});
function wrapText(ctx, text, maxWidth, lineWidth) {
const words = text.split(' ');
const lines = [];
let current = '';
for (const w of words) {
const test = current ? current + ' ' + w : w;
if (ctx.measureText(test).width > lineWidth && current) {
lines.push(current);
current = w;
} else {
current = test;
}
}
if (current) lines.push(current);
return lines;
}
// ── Retake ──
btnRetake.addEventListener('click', async () => {
camPhoto.style.display = 'none';
camPhoto.src = '';
photoDataInput.value = '';
btnRetake.style.display = 'none';
camControls.style.display = 'flex';
scanLine.style.display = 'block';
liveMeta.style.display = 'flex';
facePill.style.display = 'flex';
camHint.innerHTML = '<i class="mdi mdi-information-outline"></i> Position your face and wait for detection before capturing';
faceDetected = false;
scanLine.classList.remove('face-ok');
btnShutter.disabled = true;
// Restart camera
camLoading.style.display = 'flex';
camLoadText.textContent = 'Restarting camera...';
camVideo.style.display = 'block';
try {
stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'user', width: { ideal: 720 }, height: { ideal: 960 } },
audio: false
});
camVideo.srcObject = stream;
await new Promise(resolve => {
camVideo.onloadedmetadata = () => camVideo.play().then(resolve).catch(resolve);
setTimeout(resolve, 6000);
});
camLoading.style.display = 'none';
startDetection();
} catch (e) {
camLoading.style.display = 'none';
showAlert('Camera Error', 'Failed to restart camera.', 'error');
}
});
// ── Submit ──
document.getElementById('reportForm').addEventListener('submit', async (e) => {
e.preventDefault();
if (!photoDataInput.value) {
showAlert('Photo Required', 'Please capture a photo before submitting.', 'warning');
return;
}
const activity = document.getElementById('activity').value.trim();
if (!activity) {
showAlert('Activity Required', 'Please describe your activity.', 'warning');
return;
}
// If all good — submit
const btnSubmit = document.getElementById('btn-submit');
btnSubmit.disabled = true;
btnSubmit.innerHTML = '<i class="mdi mdi-loading mdi-spin"></i> Submitting...';
const fd = new FormData();
fd.append('activity', activity);
fd.append('fotoData', photoDataInput.value);
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('occ_id')) {
fd.append('occ_id', urlParams.get('occ_id'));
}
if (urlParams.has('token')) {
fd.append('token', urlParams.get('token'));
}
try {
const response = await fetch('save_report.php', { method: 'POST', body: fd });
const result = await response.json();
if (result.status === 'success') {
const overlay = document.createElement('div');
overlay.style.cssText = 'position:fixed; inset:0; z-index:9999; background:#ffffff; display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center; animation:fadeIn 0.3s ease; padding: 20px;';
overlay.innerHTML = `
<div style="width:80px; height:80px; border-radius:50%; background:#10b981; display:flex; align-items:center; justify-content:center; margin-bottom:24px; color:#fff; font-size:40px;"><i class="mdi mdi-check"></i></div>
<h2 style="font-weight:800; color:#0f172a; font-size:24px; margin-bottom:8px;">Report Submitted Successfully</h2>
<p style="color:#64748b; font-size:15px; margin-bottom:0;">Your activity has been safely recorded in the system.</p>
`;
document.body.appendChild(overlay);
} else {
showAlert('Submit Error', result.message || 'Unknown error occurred.', 'error');
btnSubmit.disabled = false;
btnSubmit.innerHTML = '<i class="mdi mdi-send"></i> Submit Report';
}
} catch (err) {
showAlert('Network Error', 'Failed to reach the server. Please try again.', 'error');
btnSubmit.disabled = false;
btnSubmit.innerHTML = '<i class="mdi mdi-send"></i> Submit Report';
}
});
// ── Prevent right-click ──
document.addEventListener('contextmenu', e => e.preventDefault());
})();
</script>
</body>
</html>