document.addEventListener('DOMContentLoaded', async function() { const tpsContentContainer = document.getElementById('tps-content'); let activeTpsIndex = 0; let tpsData = []; let nomorSpj = 'SPJ/07-2025/PKM/000476'; const RECORD_DETAIL_ENDPOINT = '/upst/detail-penjemputan/api/records/detail'; let autoSaveTimer = null; let autoSaveStatusEl = null; let loadingOverlayEl = null; let isAutoSaving = false; let pendingAutoSave = false; let lastAutoSaveSignature = ""; function getLoadingOverlay() { if (loadingOverlayEl && document.body.contains(loadingOverlayEl)) { return loadingOverlayEl; } loadingOverlayEl = document.createElement('div'); loadingOverlayEl.id = 'detail-loading-overlay'; loadingOverlayEl.className = 'fixed inset-0 z-[9999] hidden bg-white/95 backdrop-blur-sm flex items-center justify-center px-6'; loadingOverlayEl.innerHTML = `

Sedang memuat data

Mohon tunggu sebentar, data penjemputan sedang dipulihkan.

`; document.body.appendChild(loadingOverlayEl); return loadingOverlayEl; } function showLoadingOverlay() { const overlay = getLoadingOverlay(); overlay.classList.remove('hidden'); document.body.style.overflow = 'hidden'; } function hideLoadingOverlay() { const overlay = getLoadingOverlay(); overlay.classList.add('hidden'); document.body.style.overflow = ''; } function scheduleAutoSave() { clearTimeout(autoSaveTimer); showAutoSaveStatus('menyimpan...'); autoSaveTimer = setTimeout(autoSaveRecord, 500); } function showAutoSaveStatus(msg, isOk = false) { if (!autoSaveStatusEl) { autoSaveStatusEl = document.getElementById('auto-save-status'); } if (!autoSaveStatusEl) return; autoSaveStatusEl.textContent = msg; autoSaveStatusEl.className = isOk ? 'text-[11px] text-green-600 text-center font-medium transition-opacity' : 'text-[11px] text-amber-500 text-center font-medium transition-opacity'; autoSaveStatusEl.style.opacity = '1'; if (isOk) setTimeout(() => { autoSaveStatusEl.style.opacity = '0'; }, 2500); } function buildAutoSavePayload(tps) { return { nomorSpj: nomorSpj || '', namaTps: tps.name || DEFAULT_TPS_NAME, lokasiAngkutId: tps.lokasiAngkutId || '', spjDetailId: tps.spjDetailId || '', latitude: tps.latitude || '', longitude: tps.longitude || '', alamatJalan: tps.alamatJalan || '', waktuKedatangan: tps.waktuKedatangan || '', fotoKedatanganFileNames: tps.fotoKedatanganFileNames || [], fotoKedatanganUploaded: tps.fotoKedatanganUploaded || false, fotoPetugasFileNames: tps.fotoPetugasFileNames || [], fotoPetugasUploaded: tps.fotoPetugasUploaded || false, namaPetugas: tps.namaPetugas || '' }; } async function autoSaveRecord() { const tps = tpsData[activeTpsIndex]; if (!tps) return; if (tps.submitted) return; if (isAutoSaving) { pendingAutoSave = true; return; } const payload = buildAutoSavePayload(tps); const payloadSignature = JSON.stringify(payload); if (payloadSignature === lastAutoSaveSignature) { showAutoSaveStatus('✓ Data tersimpan', true); return; } try { isAutoSaving = true; const res = await fetch('/upst/detail-penjemputan/save-record-jgc', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: payloadSignature }); const data = await res.json(); if (data.success) { lastAutoSaveSignature = payloadSignature; } showAutoSaveStatus(data.success ? '✓ Data tersimpan' : '✗ Gagal simpan', data.success); } catch { showAutoSaveStatus('✗ Gagal simpan data'); } finally { isAutoSaving = false; if (pendingAutoSave) { pendingAutoSave = false; scheduleAutoSave(); } } } function normalizeStringList(value) { if (!Array.isArray(value)) { return []; } return value.filter((item) => typeof item === 'string' && item.trim()); } function normalizeJenisSampahValue(value) { if (Array.isArray(value)) { return normalizeJenisSampahValue(value[0]); } if (typeof value === 'number') { return JENIS_SAMPAH[value] || DEFAULT_JENIS; } if (typeof value === 'string' && value.trim()) { const trimmed = value.trim(); const asNumber = Number(trimmed); if (!Number.isNaN(asNumber) && String(asNumber) === trimmed) { return JENIS_SAMPAH[asNumber] || DEFAULT_JENIS; } const matched = JENIS_SAMPAH.find( (item) => item.toLowerCase() === trimmed.toLowerCase(), ); return matched || DEFAULT_JENIS; } return DEFAULT_JENIS; } function applyServerRecordToTps(record) { if (!record) return; const tps = tpsData[activeTpsIndex]; if (!tps) return; const fotoKedatangan = normalizeStringList( record.fotoKedatanganFileNames || record.fotoKedatangan || record.FotoKedatangan, ); const fotoPetugas = normalizeStringList( record.fotoPetugasFileNames || record.fotoPetugas || record.FotoPetugas, ); tps.name = record.namaTps || record.name || record.Name || tps.name || DEFAULT_TPS_NAME; tps.lokasiAngkutId = record.lokasiAngkutId || record.LokasiAngkutID || tps.lokasiAngkutId; tps.spjDetailId = record.spjDetailId || record.SpjDetailID || tps.spjDetailId; tps.latitude = record.latitude || record.Latitude || tps.latitude; tps.longitude = record.longitude || record.Longitude || tps.longitude; tps.alamatJalan = record.alamatJalan || record.AlamatJalan || tps.alamatJalan; tps.waktuKedatangan = record.waktuKedatangan || record.WaktuKedatangan || tps.waktuKedatangan; tps.fotoKedatangan = []; tps.fotoKedatanganFileNames = fotoKedatangan; tps.fotoKedatanganUploaded = Boolean(record.fotoKedatanganUploaded ?? record.FotoKedatanganUploaded) || fotoKedatangan.length > 0; tps.fotoPetugas = []; tps.fotoPetugasFileNames = fotoPetugas; tps.fotoPetugasUploaded = Boolean(record.fotoPetugasUploaded ?? record.FotoPetugasUploaded) || fotoPetugas.length > 0; tps.namaPetugas = record.namaPetugas || record.NamaPetugas || tps.namaPetugas; tps.submitted = Boolean(record.isSubmit ?? record.IsSubmit ?? record.submitted ?? record.Submitted ?? tps.submitted); } async function loadRecordForCurrentSpj() { const tps = tpsData[activeTpsIndex]; if (!tps || !nomorSpj) return; const params = new URLSearchParams({ nomorSpj }); if (tps.spjDetailId) params.set('spjDetailId', tps.spjDetailId); if (tps.lokasiAngkutId) params.set('lokasiAngkutId', tps.lokasiAngkutId); if (tps.name) params.set('namaTps', tps.name); try { const res = await fetch(`${RECORD_DETAIL_ENDPOINT}?${params.toString()}`, { cache: 'no-store' }); if (!res.ok) return; const data = await res.json(); if (!data.success || !data.hasData || !data.item) return; applyServerRecordToTps(data.item); } catch (error) { console.warn('Gagal memuat data non-TPS:', error); } } const OCR_AREAS = [ { id: "A", x: 0.34, y: 0.35, w: 0.4, h: 0.11, color: "border-lime-400 bg-lime-500/15", }, { id: "B", x: 0.31, y: 0.33, w: 0.45, h: 0.14, color: "border-amber-300 bg-amber-400/10", }, { id: "C", x: 0.29, y: 0.31, w: 0.49, h: 0.17, color: "border-cyan-300 bg-cyan-400/10", }, ]; const JENIS_SAMPAH = ["Organik", "Anorganik", "Residu"]; const DEFAULT_JENIS = "Residu"; const DETAIL_DATA_URL = "/driver/json/detail-penjemputan-jgc.json"; const DEFAULT_TPS_NAME = "TPS JGC"; function isBrowserFile(file) { return file instanceof File; } function resolveStoredPhoto(file) { return isBrowserFile(file) ? file : null; } function getStoredPhotoUrl(file) { if (isBrowserFile(file)) return URL.createObjectURL(file); if (typeof file === 'string') return file; return ''; } function getStoredPhotoName(file, defaultName = 'Foto') { if (isBrowserFile(file)) return file.name; if (typeof file === 'string') return file.split('/').pop() || defaultName; return defaultName; } function getStoredPhotoSize(file) { if (isBrowserFile(file)) return file.size; return 0; } function initializeLocation() { tpsData = [{ name: DEFAULT_TPS_NAME, index: 0, lokasiAngkutId: '', spjDetailId: '', latitude: '', longitude: '', alamatJalan: '', waktuKedatangan: '', fotoKedatangan: [], fotoKedatanganFileNames: [], fotoKedatanganUploaded: false, fotoPetugas: [], fotoPetugasFileNames: [], fotoPetugasUploaded: false, namaPetugas: '', submitted: false }]; } async function loadDetailData() { try { const response = await fetch(DETAIL_DATA_URL, { cache: "no-store" }); if (!response.ok) { return; } const payload = await response.json(); const detail = payload.detailPenjemputan || payload; const namaTps = detail.namaTps || detail.tpsName || detail.name || DEFAULT_TPS_NAME; const namaPerusahaan = detail.namaPerusahaan || detail.companyName || ""; if (tpsData[0]) { tpsData[0].name = namaTps; tpsData[0].lokasiAngkutId = detail.lokasiAngkutId || detail.LokasiAngkutID || tpsData[0].lokasiAngkutId; tpsData[0].spjDetailId = detail.spjDetailId || detail.SpjDetailID || tpsData[0].spjDetailId; } nomorSpj = detail.nomorSpj || nomorSpj; applyDetailDataToView(detail, namaTps, namaPerusahaan); const _form = tpsContentContainer.querySelector('form'); if (_form) { const lokasiInput = _form.querySelector('.tps-lokasi-angkut-id'); const spjInput = _form.querySelector('.tps-spj-detail-id'); if (lokasiInput) lokasiInput.value = tpsData[0].lokasiAngkutId; if (spjInput) spjInput.value = tpsData[0].spjDetailId; } } catch (error) { console.warn('Gagal memuat detail penjemputan non-TPS:', error); } } function applyDetailDataToView(detail, namaTps, namaPerusahaan) { const titleEl = document.getElementById("detail-page-title"); const badgeEl = document.getElementById("detail-tps-badge"); const formBadgeEl = document.getElementById("detail-form-badge"); const companyEl = document.getElementById("detail-company-name"); const spjEl = document.getElementById("detail-spj-number"); const addressEl = document.getElementById("detail-address"); const platEl = document.getElementById("plat-nomor"); const doorEl = document.getElementById("detail-nomor-pintu"); if (titleEl) titleEl.textContent = namaTps; if (badgeEl) badgeEl.textContent = namaTps; if (formBadgeEl) formBadgeEl.textContent = namaTps; if (companyEl && namaPerusahaan) companyEl.textContent = namaPerusahaan; if (spjEl && detail.nomorSpj) spjEl.textContent = detail.nomorSpj; if (addressEl && detail.alamat) addressEl.textContent = detail.alamat; if (platEl && detail.platNomor) platEl.textContent = detail.platNomor; if (doorEl && detail.nomorPintu) doorEl.textContent = detail.nomorPintu; document.title = `Detail Penjemputan - ${namaTps}`; } function renderTpsForm() { const tps = tpsData[activeTpsIndex]; const submitState = getSubmitState(tps); const actionMarkup = tps.submitted ? `
Data ${tps.name || DEFAULT_TPS_NAME} sudah disubmit
` : `
Batal
${submitState.canSubmit ? '' : `

${submitState.message}

`} `; tpsContentContainer.innerHTML = `
1

Foto Kedatangan

Upload foto kedatangan

${getKedatanganUploadStateMarkup(tps)}
2

Foto Petugas

Upload dokumentasi petugas

${getPetugasUploadStateMarkup(tps)}
${actionMarkup}

`; attachTpsFormListeners(); restorePhotoPreview(); } function restorePhotoPreview() { const tps = tpsData[activeTpsIndex]; const form = tpsContentContainer.querySelector("form"); if (!form) return; const previewWrapKedatangan = form.querySelector('.tps-preview-kedatangan-wrap'); const previewImgKedatangan = form.querySelector('.tps-preview-kedatangan-img'); if (previewWrapKedatangan && previewImgKedatangan) { let imgSrc = ''; if (Array.isArray(tps.fotoKedatangan) && tps.fotoKedatangan.length > 0) { const localUrl = getStoredPhotoUrl(tps.fotoKedatangan[0]); if (localUrl) { imgSrc = localUrl; if (isBrowserFile(resolveStoredPhoto(tps.fotoKedatangan[0]))) { previewImgKedatangan.onload = () => URL.revokeObjectURL(localUrl); } } } else if (tps.fotoKedatanganUploaded && Array.isArray(tps.fotoKedatanganFileNames) && tps.fotoKedatanganFileNames.length > 0) { imgSrc = tps.fotoKedatanganFileNames[0]; } if (imgSrc) { previewImgKedatangan.src = imgSrc; previewWrapKedatangan.classList.remove('hidden'); } } const previewWrapPetugas = form.querySelector('.tps-preview-petugas-wrap'); const previewImgPetugas = form.querySelector('.tps-preview-petugas-img'); if (previewWrapPetugas && previewImgPetugas) { let imgSrc = ''; if (Array.isArray(tps.fotoPetugas) && tps.fotoPetugas.length > 0) { const localUrl = getStoredPhotoUrl(tps.fotoPetugas[0]); if (localUrl) { imgSrc = localUrl; if (isBrowserFile(resolveStoredPhoto(tps.fotoPetugas[0]))) { previewImgPetugas.onload = () => URL.revokeObjectURL(localUrl); } } } else if (tps.fotoPetugasUploaded && Array.isArray(tps.fotoPetugasFileNames) && tps.fotoPetugasFileNames.length > 0) { imgSrc = tps.fotoPetugasFileNames[0]; } if (imgSrc) { previewImgPetugas.src = imgSrc; previewWrapPetugas.classList.remove('hidden'); } } } function renderStoredPhotos(files, container) { container.innerHTML = ""; container.className = "space-y-2"; files.forEach((file, index) => { const item = document.createElement("div"); item.className = "rounded-xl border border-gray-200 overflow-hidden bg-black"; const imageUrl = getStoredPhotoUrl(file); const safeName = getStoredPhotoName(file, `Foto ${index + 1}`).replace( /"/g, """, ); const fileSize = getStoredPhotoSize(file); item.innerHTML = `
Preview ${index + 1}
`; const img = item.querySelector(".preview-multi-image"); if (img && isBrowserFile(resolveStoredPhoto(file))) { img.onload = function () { URL.revokeObjectURL(imageUrl); }; } container.appendChild(item); }); } function attachTpsFormListeners() { const form = tpsContentContainer.querySelector("form"); const tps = tpsData[activeTpsIndex]; if (tps.submitted) return; const fotoKedatanganInput = form.querySelector(".tps-foto-kedatangan"); const fotoPetugasInput = form.querySelector(".tps-foto-petugas"); const namaPetugasInput = form.querySelector(".tps-nama-petugas"); fotoKedatanganInput.addEventListener('change', function() { if (!this.files || !this.files[0]) return; const currentTps = tpsData[activeTpsIndex]; const file = this.files[0]; const previewWrap = form.querySelector('.tps-preview-kedatangan-wrap'); const previewImg = form.querySelector('.tps-preview-kedatangan-img'); if (previewWrap && previewImg) { const localUrl = URL.createObjectURL(file); previewImg.src = localUrl; previewWrap.classList.remove('hidden'); previewImg.onload = () => URL.revokeObjectURL(localUrl); } currentTps.fotoKedatangan = [file]; currentTps.fotoKedatanganFileNames = []; currentTps.fotoKedatanganUploaded = false; updateWaktuKedatangan(); refreshKedatanganUploadState(form); }); fotoPetugasInput.addEventListener('change', function() { if (!this.files || !this.files[0]) return; const currentTps = tpsData[activeTpsIndex]; const file = this.files[0]; const previewWrap = form.querySelector('.tps-preview-petugas-wrap'); const previewImg = form.querySelector('.tps-preview-petugas-img'); if (previewWrap && previewImg) { const localUrl = URL.createObjectURL(file); previewImg.src = localUrl; previewWrap.classList.remove('hidden'); previewImg.onload = () => URL.revokeObjectURL(localUrl); } currentTps.fotoPetugas = [file]; currentTps.fotoPetugasFileNames = []; currentTps.fotoPetugasUploaded = false; refreshPetugasUploadState(form); }); namaPetugasInput.addEventListener('input', function() { tpsData[activeTpsIndex].namaPetugas = this.value; refreshPetugasUploadState(form); scheduleAutoSave(); }); namaPetugasInput.addEventListener('blur', function() { tpsData[activeTpsIndex].namaPetugas = this.value; scheduleAutoSave(); }); const btnUploadKedatangan = form.querySelector( ".tps-btn-upload-kedatangan", ); if (btnUploadKedatangan) { btnUploadKedatangan.addEventListener("click", uploadFotoKedatangan); } const btnUploadPetugas = form.querySelector(".tps-btn-upload-petugas"); if (btnUploadPetugas) { btnUploadPetugas.addEventListener("click", uploadFotoPetugas); } form.addEventListener("submit", function (e) { e.preventDefault(); submitTpsData(); }); } function updateWaktuKedatangan() { const tps = tpsData[activeTpsIndex]; const now = new Date(); const formatted = now.toLocaleString("id-ID", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit", }); tps.waktuKedatangan = formatted; const form = tpsContentContainer.querySelector("form"); const displayWaktu = form.querySelector(".tps-waktu-kedatangan"); if (displayWaktu) displayWaktu.value = formatted; getLocationUpdate(); } function reverseGeocode(lat, lng) { const tps = tpsData[activeTpsIndex]; fetch( `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`, ) .then((res) => res.json()) .then((data) => { const address = data.display_name || `${lat}, ${lng}`; tps.latitude = lat; tps.longitude = lng; tps.alamatJalan = address; const form = tpsContentContainer.querySelector("form"); if (form) { const latInput = form.querySelector(".tps-display-latitude"); const lngInput = form.querySelector(".tps-display-longitude"); if (latInput) latInput.value = lat; if (lngInput) lngInput.value = lng; } }) .catch(() => { tps.latitude = lat; tps.longitude = lng; tps.alamatJalan = `${lat}, ${lng}`; const form = tpsContentContainer.querySelector("form"); if (form) { const latInput = form.querySelector(".tps-display-latitude"); const lngInput = form.querySelector(".tps-display-longitude"); if (latInput) latInput.value = lat; if (lngInput) lngInput.value = lng; } }); } function getLocationUpdate() { if (!("geolocation" in navigator)) return; navigator.geolocation.getCurrentPosition( function (position) { const lat = position.coords.latitude.toFixed(6); const lng = position.coords.longitude.toFixed(6); reverseGeocode(lat, lng); }, function () { console.log("Lokasi tidak diizinkan"); }, ); } function formatFileSize(bytes) { const mb = bytes / (1024 * 1024); return `${mb.toFixed(2)} MB`; } function updateMultiPreview(input, previewContainer) { if (!input || !previewContainer) return; previewContainer.innerHTML = ""; previewContainer.className = "space-y-2"; if (!input.files || input.files.length === 0) return; Array.from(input.files).forEach((file, index) => { const item = document.createElement("div"); item.className = "rounded-xl border border-gray-200 overflow-hidden bg-black"; const imageUrl = URL.createObjectURL(file); const safeName = file.name.replace(/"/g, """); item.innerHTML = `
Preview ${index + 1}
`; const img = item.querySelector(".preview-multi-image"); if (img) { img.onload = function () { URL.revokeObjectURL(imageUrl); }; } previewContainer.appendChild(item); }); } function formatWeightDisplay(value) { if (isNaN(value)) return "0,00"; return value.toFixed(2).replace(".", ","); } function parseWeightInput(value) { if (!value) return 0; const cleaned = value .toString() .trim() .replace(/\s/g, "") .replace(",", "."); const parsed = parseFloat(cleaned); return isNaN(parsed) ? 0 : parsed; } async function applyWatermark(file, photoNumber) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = function (e) { const img = new Image(); img.onload = function () { const canvas = document.createElement("canvas"); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0); const now = new Date(); const days = [ "Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu", ]; const months = [ "Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Agu", "Sep", "Okt", "Nov", "Des", ]; const timestamp = `${days[now.getDay()]}, ${now.getDate().toString().padStart(2, "0")} ${months[now.getMonth()]} ${now.getFullYear()} • ${now.getHours().toString().padStart(2, "0")}:${now.getMinutes().toString().padStart(2, "0")}:${now.getSeconds().toString().padStart(2, "0")}`; const baseFontSize = Math.max( 16, Math.min(img.width, img.height) * 0.025, ); const fontFamily = "'Montserrat', 'Segoe UI', 'Roboto', sans-serif"; const lines = [ { text: `FOTO TIMBANG #${photoNumber}`, size: baseFontSize * 1.1, weight: "900", color: "#FFD700", }, { text: `${nomorSpj}`, size: baseFontSize * 0.9, weight: "700", color: "#FFFFFF", }, { text: timestamp, size: baseFontSize * 0.75, weight: "500", color: "#E2E8F0", }, ]; const paddingX = baseFontSize * 1.2; const paddingY = baseFontSize * 1.0; const lineGap = baseFontSize * 0.4; let maxWidth = 0; let totalHeight = 0; lines.forEach((line) => { ctx.font = `${line.weight} ${line.size}px ${fontFamily}`; const metrics = ctx.measureText(line.text); maxWidth = Math.max(maxWidth, metrics.width); totalHeight += line.size + lineGap; }); totalHeight -= lineGap; const margin = baseFontSize * 1.5; const boxWidth = maxWidth + paddingX * 2; const boxHeight = totalHeight + paddingY * 2; const boxX = img.width - boxWidth - margin; const boxY = img.height - boxHeight - margin; ctx.beginPath(); if (ctx.roundRect) { ctx.roundRect(boxX, boxY, boxWidth, boxHeight, baseFontSize * 0.8); } else { ctx.rect(boxX, boxY, boxWidth, boxHeight); } ctx.fillStyle = "rgba(15, 23, 42, 0.85)"; ctx.fill(); const accentWidth = baseFontSize * 0.3; ctx.beginPath(); if (ctx.roundRect) { ctx.roundRect( boxX + boxWidth - accentWidth, boxY, accentWidth, boxHeight, [0, baseFontSize * 0.8, baseFontSize * 0.8, 0], ); } else { ctx.rect( boxX + boxWidth - accentWidth, boxY, accentWidth, boxHeight, ); } ctx.fillStyle = "#FFD700"; ctx.fill(); ctx.textAlign = "right"; ctx.textBaseline = "top"; let currentY = boxY + paddingY; const textRightLimit = boxX + boxWidth - paddingX - accentWidth; lines.forEach((line) => { ctx.font = `${line.weight} ${line.size}px ${fontFamily}`; ctx.fillStyle = line.color; ctx.fillText(line.text, textRightLimit, currentY); currentY += line.size + lineGap; }); canvas.toBlob( function (blob) { const watermarkedFile = new File([blob], file.name, { type: "image/jpeg", lastModified: Date.now(), }); resolve(watermarkedFile); }, "image/jpeg", 0.95, ); }; img.onerror = reject; img.src = e.target.result; }; reader.onerror = reject; reader.readAsDataURL(file); }); } function readFileAsImage(file) { return new Promise(function (resolve, reject) { const objectUrl = URL.createObjectURL(file); const img = new Image(); img.onload = function () { URL.revokeObjectURL(objectUrl); resolve(img); }; img.onerror = function () { URL.revokeObjectURL(objectUrl); reject(new Error("Gagal membaca gambar.")); }; img.src = objectUrl; }); } function createCropCanvas(img, area) { const sx = Math.max(0, Math.floor(img.width * area.x)); const sy = Math.max(0, Math.floor(img.height * area.y)); const sw = Math.max(1, Math.floor(img.width * area.w)); const sh = Math.max(1, Math.floor(img.height * area.h)); const canvas = document.createElement("canvas"); const scale = 2.2; canvas.width = Math.max(1, Math.floor(sw * scale)); canvas.height = Math.max(1, Math.floor(sh * scale)); const ctx = canvas.getContext("2d"); if (!ctx) return canvas; ctx.imageSmoothingEnabled = true; ctx.drawImage(img, sx, sy, sw, sh, 0, 0, canvas.width, canvas.height); return canvas; } function canvasToJpegFile(canvas, fileName) { return new Promise(function (resolve, reject) { canvas.toBlob( function (blob) { if (!blob) { reject(new Error("Gagal membuat crop image.")); return; } resolve(new File([blob], fileName, { type: "image/jpeg" })); }, "image/jpeg", 0.92, ); }); } function getKedatanganUploadStateMarkup(tps) { if (tps.fotoKedatanganUploaded) { return '
✓ Foto kedatangan sudah diupload
'; } if (!tps.fotoKedatangan.length) { return ""; } return ``; } function refreshKedatanganUploadState(form) { const stateContainer = form.querySelector(".kedatangan-upload-state"); if (!stateContainer) return; const tps = tpsData[activeTpsIndex]; stateContainer.innerHTML = getKedatanganUploadStateMarkup(tps); const btnUploadKedatangan = stateContainer.querySelector( ".tps-btn-upload-kedatangan", ); if (btnUploadKedatangan) { btnUploadKedatangan.addEventListener("click", uploadFotoKedatangan); } refreshSubmitButtonState(form); } function getPetugasUploadStateMarkup(tps) { if (tps.fotoPetugasUploaded) { return '
✓ Foto petugas sudah diupload
'; } if (!tps.fotoPetugas.length) { return ""; } if (!tps.namaPetugas.trim()) { return `

Isi nama petugas terlebih dahulu

`; } return ``; } function refreshPetugasUploadState(form) { const stateContainer = form.querySelector(".petugas-upload-state"); if (!stateContainer) return; const tps = tpsData[activeTpsIndex]; stateContainer.innerHTML = getPetugasUploadStateMarkup(tps); const btnUploadPetugas = stateContainer.querySelector( ".tps-btn-upload-petugas:not([disabled])", ); if (btnUploadPetugas) { btnUploadPetugas.addEventListener("click", uploadFotoPetugas); } refreshSubmitButtonState(form); } function getSubmitState(tps) { if (tps?.submitted) { return { canSubmit: false, message: '' }; } if (!tps.fotoKedatanganUploaded) { if (!tps.fotoKedatangan.length) return { canSubmit: false, message: 'Silakan pilih dan upload foto kedatangan.' }; return { canSubmit: false, message: 'Silakan upload foto kedatangan terlebih dahulu.' }; } if (!tps.fotoPetugasUploaded) { if (!tps.fotoPetugas.length) return { canSubmit: false, message: 'Silakan pilih dan upload foto petugas.' }; return { canSubmit: false, message: 'Silakan upload foto petugas terlebih dahulu.' }; } if (!tps.namaPetugas.trim()) { return { canSubmit: false, message: "Isi nama petugas dulu sebelum submit.", }; } return { canSubmit: true, message: "" }; } function refreshSubmitButtonState(form) { const submitButton = form.querySelector('button[type="submit"]'); const tps = tpsData[activeTpsIndex]; if (tps?.submitted || !submitButton) return; const helperText = form.querySelector(".submit-state-message"); const submitState = getSubmitState(tps); submitButton.disabled = !submitState.canSubmit; submitButton.className = `w-2/3 py-3 rounded-xl font-bold text-sm transition ${submitState.canSubmit ? "bg-upst text-white hover:brightness-110" : "bg-gray-300 text-gray-500 cursor-not-allowed"}`; let messageEl = helperText; if (!messageEl) { messageEl = document.createElement("p"); messageEl.className = "submit-state-message text-[11px] text-center text-red-500 font-medium"; submitButton .closest(".flex.gap-3") ?.insertAdjacentElement("afterend", messageEl); } if (submitState.canSubmit) { messageEl.textContent = ""; messageEl.classList.add("hidden"); } else { messageEl.textContent = submitState.message; messageEl.classList.remove("hidden"); } } function buildSubmitFormData(tps) { const formData = new FormData(); formData.append("NomorSpj", nomorSpj || ""); formData.append("TpsName", tps.name || DEFAULT_TPS_NAME); formData.append("LokasiAngkutId", tps.lokasiAngkutId || ""); formData.append("SpjDetailId", tps.spjDetailId || ""); formData.append("Latitude", tps.latitude); formData.append("Longitude", tps.longitude); formData.append("AlamatJalan", tps.alamatJalan); formData.append("WaktuKedatangan", tps.waktuKedatangan); formData.append("NamaPetugas", tps.namaPetugas); tps.fotoKedatangan.forEach((file) => { if (isBrowserFile(file)) { formData.append("FotoKedatangan", file); } }); tps.fotoPetugas.forEach((file) => { if (isBrowserFile(file)) { formData.append("FotoPetugas", file); } }); const antiForgeryTokenEl = document.querySelector( '#upst-antiforgery input[name="__RequestVerificationToken"]', ); if (antiForgeryTokenEl && antiForgeryTokenEl.value) { formData.append("__RequestVerificationToken", antiForgeryTokenEl.value); } return formData; } async function uploadFotoKedatangan() { const tps = tpsData[activeTpsIndex]; if (tps.fotoKedatangan.length === 0) { showToast('Belum ada foto kedatangan yang dipilih!', 'error'); return; } const form = tpsContentContainer.querySelector('form'); const btn = form ? form.querySelector('.tps-btn-upload-kedatangan') : null; if (btn) { btn.disabled = true; btn.textContent = 'Mengupload...'; } const formData = new FormData(); tps.fotoKedatangan.forEach(f => formData.append('FotoKedatangan', f)); formData.append('NomorSpj', nomorSpj || ''); formData.append('NamaTps', tps.name || DEFAULT_TPS_NAME); formData.append('SpjDetailId', tps.spjDetailId || ''); formData.append('LokasiAngkutId', tps.lokasiAngkutId || ''); formData.append('WaktuKedatangan', tps.waktuKedatangan || ''); formData.append('Latitude', tps.latitude || ''); formData.append('Longitude', tps.longitude || ''); formData.append('AlamatJalan', tps.alamatJalan || ''); try { const res = await fetch('/upst/detail-penjemputan/upload-foto-kedatangan-jgc', { method: 'POST', body: formData }); const data = await res.json(); if (res.ok && data.success) { tps.fotoKedatanganUploaded = true; tps.fotoKedatanganFileNames = data.fileUrls || data.fileNames || []; showToast(data.message || 'Foto kedatangan berhasil diupload.', 'success'); if (form) { const fotoInput = form.querySelector('.tps-foto-kedatangan'); if (fotoInput) fotoInput.value = ''; const previewWrap = form.querySelector('.tps-preview-kedatangan-wrap'); const previewImg = form.querySelector('.tps-preview-kedatangan-img'); if (previewWrap && previewImg && tps.fotoKedatanganFileNames.length > 0) { previewImg.src = tps.fotoKedatanganFileNames[0]; previewWrap.classList.remove('hidden'); } refreshKedatanganUploadState(form); } scheduleAutoSave(); } else { showToast(data.message || 'Gagal upload foto kedatangan.', 'error'); if (btn) { btn.disabled = false; btn.textContent = `Upload ${tps.fotoKedatangan.length} Foto Kedatangan`; } } } catch { showToast('Koneksi gagal saat upload foto kedatangan.', 'error'); if (btn) { btn.disabled = false; btn.textContent = `Upload ${tps.fotoKedatangan.length} Foto Kedatangan`; } } } async function uploadFotoPetugas() { const tps = tpsData[activeTpsIndex]; if (tps.fotoPetugas.length === 0) { showToast('Belum ada foto petugas yang dipilih!', 'error'); return; } if (!tps.namaPetugas.trim()) { showToast('Nama petugas wajib diisi sebelum upload foto petugas!', 'error'); return; } const form = tpsContentContainer.querySelector('form'); const btn = form ? form.querySelector('.tps-btn-upload-petugas:not([disabled])') : null; if (btn) { btn.disabled = true; btn.textContent = 'Mengupload...'; } const formData = new FormData(); tps.fotoPetugas.forEach(f => formData.append('FotoPetugas', f)); formData.append('NomorSpj', nomorSpj || ''); formData.append('NamaTps', tps.name || DEFAULT_TPS_NAME); formData.append('SpjDetailId', tps.spjDetailId || ''); formData.append('LokasiAngkutId', tps.lokasiAngkutId || ''); formData.append('NamaPetugas', tps.namaPetugas); try { const res = await fetch('/upst/detail-penjemputan/upload-foto-petugas-jgc', { method: 'POST', body: formData }); const data = await res.json(); if (res.ok && data.success) { tps.fotoPetugasUploaded = true; tps.fotoPetugasFileNames = data.fileUrls || data.fileNames || []; showToast(data.message || 'Foto petugas berhasil diupload.', 'success'); if (form) { const fotoInput = form.querySelector('.tps-foto-petugas'); if (fotoInput) fotoInput.value = ''; const previewWrap = form.querySelector('.tps-preview-petugas-wrap'); const previewImg = form.querySelector('.tps-preview-petugas-img'); if (previewWrap && previewImg && tps.fotoPetugasFileNames.length > 0) { previewImg.src = tps.fotoPetugasFileNames[0]; previewWrap.classList.remove('hidden'); } refreshPetugasUploadState(form); } scheduleAutoSave(); } else { showToast(data.message || 'Gagal upload foto petugas.', 'error'); if (btn) { btn.disabled = false; btn.textContent = `Upload ${tps.fotoPetugas.length} Foto Petugas`; } } } catch { showToast('Koneksi gagal saat upload foto petugas.', 'error'); if (btn) { btn.disabled = false; btn.textContent = `Upload ${tps.fotoPetugas.length} Foto Petugas`; } } } async function submitTpsData() { const tps = tpsData[activeTpsIndex]; const submitState = getSubmitState(tps); if (!submitState.canSubmit) { showToast(submitState.message, 'error'); return; } const form = tpsContentContainer.querySelector('form'); const submitBtn = form ? form.querySelector('button[type="submit"]') : null; if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'Menyimpan...'; } const formData = buildSubmitFormData(tps); try { const res = await fetch('/upst/detail-penjemputan/jgc-submit', { method: 'POST', headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' }, body: formData }); const result = await res.json().catch(() => null); if (res.ok && result?.success) { tps.submitted = true; showToast(result.message || 'Data berhasil disimpan!', 'success'); setTimeout(() => { window.location.href = '/upst/detail-penjemputan/detail-selesai-jgc'; }, 1500); } else { showToast(result?.message || 'Gagal submit data.', 'error'); if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Submit'; } } } catch { showToast('Koneksi gagal saat submit.', 'error'); if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Submit'; } } } function showToast(message, type = 'info') { let container = document.getElementById('espj-toast-container'); if (!container) { container = document.createElement('div'); container.id = 'espj-toast-container'; container.style.cssText = 'position:fixed;bottom:80px;left:50%;transform:translateX(-50%);z-index:9999;display:flex;flex-direction:column;align-items:center;gap:8px;pointer-events:none;'; document.body.appendChild(container); } const toast = document.createElement('div'); const bg = type === 'success' ? '#16a34a' : type === 'error' ? '#dc2626' : '#2563eb'; toast.style.cssText = `background:${bg};color:#fff;padding:10px 18px;border-radius:16px;font-size:13px;font-weight:600;box-shadow:0 4px 24px rgba(0,0,0,.18);opacity:0;transition:opacity .25s;max-width:320px;text-align:center;`; toast.textContent = message; container.appendChild(toast); requestAnimationFrame(() => { toast.style.opacity = '1'; }); setTimeout(() => { toast.style.opacity = '0'; setTimeout(() => toast.remove(), 300); }, 3200); } const nomorSpjEl = document.querySelector(".text-gray-600.font-mono"); if (nomorSpjEl && nomorSpjEl.textContent) { nomorSpj = nomorSpjEl.textContent.trim(); } showLoadingOverlay(); try { initializeLocation(); await loadDetailData(); await loadRecordForCurrentSpj(); renderTpsForm(); } finally { hideLoadingOverlay(); } function renderServerImagePreview(fileUrls, container) { container.innerHTML = ''; if (!fileUrls || fileUrls.length === 0) return; container.className = 'space-y-2'; fileUrls.forEach((url, index) => { const item = document.createElement('div'); item.className = 'rounded-xl border border-gray-200 overflow-hidden bg-black'; const isUrl = typeof url === 'string' && (url.startsWith('/') || url.startsWith('http')); if (isUrl) { item.innerHTML = `
Foto ${index + 1}
`; } else { item.className = 'rounded-xl border border-green-200 bg-green-50 h-44 flex items-center justify-center'; item.innerHTML = `

✓ Foto ${index + 1}

`; } container.appendChild(item); }); } });