document.addEventListener('DOMContentLoaded', function() { const grandTotalDisplay = document.getElementById('grand-total-timbangan'); const grandTotalOrganikDisplay = document.getElementById('grand-total-organik'); const grandTotalAnorganikDisplay = document.getElementById('grand-total-anorganik'); const grandTotalResiduDisplay = document.getElementById('grand-total-residu'); const tpsContentContainer = document.getElementById('tps-content'); let activeTpsIndex = 0; let tpsData = []; let nomorSpj = 'SPJ/07-2025/PKM/000476'; const OCR_AREAS = [ { id: 'A', x: 0.34, y: 0.35, w: 0.40, 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'; function initializeLocation() { tpsData = [{ name: '', index: 0, lokasiAngkutId: '', spjDetailId: '', latitude: '', longitude: '', alamatJalan: '', waktuKedatangan: '', fotoKedatangan: [], fotoKedatanganUploaded: false, timbangan: [], totalOrganik: 0, totalAnorganik: 0, totalResidu: 0, totalTimbangan: 0, fotoPetugas: [], fotoPetugasUploaded: false, namaPetugas: '', submitted: false }]; renderTpsForm(); } function renderTpsForm() { const tps = tpsData[activeTpsIndex]; tpsContentContainer.innerHTML = `
1

Foto Kedatangan

Upload foto kedatangan

${tps.fotoKedatangan.length > 0 && !tps.fotoKedatanganUploaded ? ` ` : tps.fotoKedatanganUploaded ? `
✓ Foto kedatangan sudah diupload
` : ''}
2

Foto Timbang Sampah

Upload foto timbangan, berat auto terisi

3

Foto Petugas

Upload dokumentasi petugas

${tps.fotoPetugas.length > 0 && !tps.fotoPetugasUploaded ? ` ` : tps.fotoPetugasUploaded ? `
✓ Foto petugas sudah diupload
` : ''}
Batal
`; attachTpsFormListeners(); restoreTpsTimbanganItems(); restorePhotoPreview(); } function restorePhotoPreview() { const tps = tpsData[activeTpsIndex]; const form = tpsContentContainer.querySelector('form'); if (!form) return; const previewKedatangan = form.querySelector('.tps-preview-kedatangan'); if (previewKedatangan && tps.fotoKedatangan.length > 0) { renderStoredPhotos(tps.fotoKedatangan, previewKedatangan); } const previewPetugas = form.querySelector('.tps-preview-petugas'); if (previewPetugas && tps.fotoPetugas.length > 0) { renderStoredPhotos(tps.fotoPetugas, previewPetugas); } } 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 = URL.createObjectURL(file); const safeName = file.name.replace(/"/g, '"'); item.innerHTML = `
Preview ${index + 1}

${index + 1}. ${safeName}

${formatFileSize(file.size)}

`; const img = item.querySelector('.preview-multi-image'); if (img) { img.onload = function() { URL.revokeObjectURL(imageUrl); }; } container.appendChild(item); }); } function attachTpsFormListeners() { const form = tpsContentContainer.querySelector('form'); const tps = tpsData[activeTpsIndex]; const fotoKedatanganInput = form.querySelector('.tps-foto-kedatangan'); const fotoPetugasInput = form.querySelector('.tps-foto-petugas'); const namaPetugasInput = form.querySelector('.tps-nama-petugas'); const btnAddTimbangan = form.querySelector('.tps-btn-add-timbangan'); fotoKedatanganInput.addEventListener('change', function() { tps.fotoKedatangan = Array.from(this.files); tps.fotoKedatanganUploaded = false; updateWaktuKedatangan(); updateMultiPreview(this, form.querySelector('.tps-preview-kedatangan')); renderTpsForm(); }); fotoPetugasInput.addEventListener('change', function() { tps.fotoPetugas = Array.from(this.files); tps.fotoPetugasUploaded = false; updateMultiPreview(this, form.querySelector('.tps-preview-petugas')); renderTpsForm(); }); namaPetugasInput.addEventListener('input', function() { tps.namaPetugas = this.value; }); btnAddTimbangan.addEventListener('click', function() { createTimbanganItem(form.querySelector('.tps-timbangan-repeater')); }); 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 restoreTpsTimbanganItems() { const tps = tpsData[activeTpsIndex]; const form = tpsContentContainer.querySelector('form'); const repeater = form.querySelector('.tps-timbangan-repeater'); if (tps.timbangan.length === 0) { createTimbanganItem(repeater); } else { tps.timbangan.forEach(timb => createTimbanganItem(repeater, timb)); } } 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}

${index + 1}. ${safeName}

${formatFileSize(file.size)}

`; 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); }); } async function requestOpenRouterWeight(imageFile) { const formData = new FormData(); formData.append('Foto', imageFile); const response = await fetch('/upst/detail-penjemputan/ocr-timbangan', { method: 'POST', body: formData }); const result = await response.json(); if (!response.ok) throw new Error(result.message || 'Request OCR gagal.'); return result; } async function autoFillWeight(file, weightInput, ocrInfoEl) { let guessedWeight = 0; weightInput.placeholder = 'Membaca angka dari foto...'; if (ocrInfoEl) ocrInfoEl.textContent = 'AI OCR: memproses gambar...'; try { const img = await readFileAsImage(file); let bestRawText = ''; let isSuccess = false; for (const area of OCR_AREAS) { const cropCanvas = createCropCanvas(img, area); const cropFile = await canvasToJpegFile(cropCanvas, `crop-${area.id}.jpg`); const aiResult = await requestOpenRouterWeight(cropFile); if (aiResult && aiResult.success && aiResult.weight) { guessedWeight = parseWeightInput(aiResult.weight); bestRawText = aiResult.raw || aiResult.weight; isSuccess = guessedWeight > 0; if (isSuccess) break; } if (aiResult && aiResult.raw) bestRawText = aiResult.raw; } if (ocrInfoEl) { const cleaned = (bestRawText || '').replace(/\s+/g, ' ').trim(); ocrInfoEl.textContent = isSuccess ? `AI OCR terbaca: ${cleaned}` : (cleaned ? `AI OCR tidak valid: ${cleaned}` : 'AI OCR tidak menemukan angka valid.'); } } catch (_) { guessedWeight = 0; if (ocrInfoEl) ocrInfoEl.textContent = 'AI OCR gagal diproses.'; } if (guessedWeight > 0) { weightInput.value = formatWeightDisplay(guessedWeight); weightInput.placeholder = 'Berat terdeteksi otomatis'; } else { weightInput.placeholder = 'Tidak terbaca otomatis, isi manual'; } updateTpsTotalTimbangan(); } function updateTpsTotalTimbangan() { const tps = tpsData[activeTpsIndex]; const form = tpsContentContainer.querySelector('form'); if (!form) return; let totalOrganik = 0.0; let totalAnorganik = 0.0; let totalResidu = 0.0; const repeater = form.querySelector('.tps-timbangan-repeater'); const items = repeater.querySelectorAll('.timbangan-item'); items.forEach(function(item) { const weightInput = item.querySelector('.input-berat-timbangan-value'); const jenisSampahSelect = item.querySelector('.input-jenis-sampah'); if (weightInput && jenisSampahSelect) { const value = parseWeightInput(weightInput.value || '0'); const jenis = jenisSampahSelect.value; if (jenis === 'Organik') totalOrganik += value; else if (jenis === 'Anorganik') totalAnorganik += value; else totalResidu += value; } }); tps.totalOrganik = totalOrganik; tps.totalAnorganik = totalAnorganik; tps.totalResidu = totalResidu; tps.totalTimbangan = totalOrganik + totalAnorganik + totalResidu; const displayTotal = form.querySelector('.tps-display-total'); if (displayTotal) displayTotal.textContent = formatWeightDisplay(tps.totalTimbangan); if (grandTotalDisplay) grandTotalDisplay.textContent = formatWeightDisplay(tps.totalTimbangan); if (grandTotalOrganikDisplay) grandTotalOrganikDisplay.textContent = formatWeightDisplay(totalOrganik); if (grandTotalAnorganikDisplay) grandTotalAnorganikDisplay.textContent = formatWeightDisplay(totalAnorganik); if (grandTotalResiduDisplay) grandTotalResiduDisplay.textContent = formatWeightDisplay(totalResidu); } function createTimbanganItem(repeater, existingData = null) { const item = document.createElement('div'); item.className = 'timbangan-item rounded-2xl border border-gray-200 p-3 space-y-2 bg-gray-50'; const weight = existingData ? (existingData.berat && existingData.berat.length > 0 ? existingData.berat[0] : 0) : 0; const jenisSampah = existingData ? (existingData.jenisSampah && existingData.jenisSampah.length > 0 ? existingData.jenisSampah[0] : DEFAULT_JENIS) : DEFAULT_JENIS; const hasFile = existingData && existingData.file; const isUploaded = existingData && existingData.uploaded; item.innerHTML = `

Item Timbangan

Preview foto timbangan

${hasFile ? 'OCR: diproses.' : 'OCR: belum diproses.'}

${hasFile && !isUploaded ? ` ` : isUploaded ? `
✓ Foto timbangan sudah diupload
` : ''}
`; const fileInput = item.querySelector('.input-foto-timbangan'); const previewWrap = item.querySelector('.input-preview-wrap'); const previewImage = item.querySelector('.input-preview-image'); const ocrInfoEl = item.querySelector('.input-ocr-info'); const weightInputDisplay = item.querySelector('.input-berat-timbangan-display'); const weightInputValue = item.querySelector('.input-berat-timbangan-value'); const jenisSampahSelect = item.querySelector('.input-jenis-sampah'); const removeBtn = item.querySelector('.btn-remove-timbangan'); if (existingData && existingData.file) { const localUrl = URL.createObjectURL(existingData.file); previewImage.src = localUrl; previewImage.onload = function() { URL.revokeObjectURL(localUrl); }; } fileInput.addEventListener('change', async function() { if (fileInput.files && fileInput.files[0]) { const originalFile = fileInput.files[0]; const photoNumber = Array.from(repeater.children).indexOf(item) + 1; const watermarkedFile = await applyWatermark(originalFile, photoNumber); const dataTransfer = new DataTransfer(); dataTransfer.items.add(watermarkedFile); fileInput.files = dataTransfer.files; const localUrl = URL.createObjectURL(watermarkedFile); previewImage.src = localUrl; previewWrap.classList.remove('hidden'); previewImage.onload = function() { URL.revokeObjectURL(localUrl); }; await autoFillWeight(watermarkedFile, weightInputDisplay, ocrInfoEl); const parsed = parseWeightInput(weightInputDisplay.value); weightInputValue.value = parsed.toFixed(2); updateTpsTotalTimbangan(); syncTimbanganToTpsData(); const tps = tpsData[activeTpsIndex]; const itemIndex = Array.from(repeater.children).indexOf(item); if (itemIndex >= 0 && tps.timbangan[itemIndex]) { tps.timbangan[itemIndex].uploaded = false; const existingUploadBtn = item.querySelector('.btn-upload-timbangan'); if (!existingUploadBtn) { const ocrInfo = item.querySelector('.input-ocr-info'); const uploadBtn = document.createElement('button'); uploadBtn.type = 'button'; uploadBtn.className = 'btn-upload-timbangan w-full bg-blue-500 text-white py-2 rounded-xl font-bold text-xs hover:brightness-110'; uploadBtn.textContent = 'Upload Foto Timbangan Ini'; uploadBtn.addEventListener('click', function() { uploadSingleFotoTimbangan(itemIndex); }); ocrInfo.parentNode.insertBefore(uploadBtn, ocrInfo.nextSibling); } } } }); weightInputDisplay.addEventListener('input', function() { const cleaned = this.value.replace(/[^0-9.,]/g, ''); this.value = cleaned; const parsed = parseWeightInput(cleaned); weightInputValue.value = parsed.toFixed(2); updateTpsTotalTimbangan(); syncTimbanganToTpsData(); }); weightInputDisplay.addEventListener('blur', function() { const parsed = parseWeightInput(this.value); if (parsed > 0) { this.value = formatWeightDisplay(parsed); weightInputValue.value = parsed.toFixed(2); } else { this.value = ''; weightInputValue.value = '0.00'; } updateTpsTotalTimbangan(); syncTimbanganToTpsData(); }); jenisSampahSelect.addEventListener('change', function() { updateTpsTotalTimbangan(); syncTimbanganToTpsData(); }); removeBtn.addEventListener('click', function() { item.remove(); const form = tpsContentContainer.querySelector('form'); const rep = form ? form.querySelector('.tps-timbangan-repeater') : null; if (rep && rep.children.length === 0) createTimbanganItem(rep); updateTpsTotalTimbangan(); syncTimbanganToTpsData(); }); const btnUploadTimbangan = item.querySelector('.btn-upload-timbangan'); if (btnUploadTimbangan) { btnUploadTimbangan.addEventListener('click', function() { const itemIndex = Array.from(repeater.children).indexOf(item); uploadSingleFotoTimbangan(itemIndex); }); } repeater.appendChild(item); return item; } function syncTimbanganToTpsData() { const tps = tpsData[activeTpsIndex]; const form = tpsContentContainer.querySelector('form'); if (!form) return; const repeater = form.querySelector('.tps-timbangan-repeater'); const items = repeater.querySelectorAll('.timbangan-item'); tps.timbangan = []; items.forEach(item => { const fileInput = item.querySelector('.input-foto-timbangan'); const weightValue = item.querySelector('.input-berat-timbangan-value'); const weight = parseWeightInput(weightValue.value); const jenisSampah = item.querySelector('.input-jenis-sampah').value; tps.timbangan.push({ file: fileInput.files[0] || null, berat: [weight], jenisSampah: [jenisSampah], lokasiAngkut: [], uploaded: false }); }); } function buildSubmitFormData(tps) { const formData = new FormData(); 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('TotalTimbangan', tps.totalTimbangan); formData.append('TotalOrganik', tps.totalOrganik); formData.append('TotalAnorganik', tps.totalAnorganik); formData.append('TotalResidu', tps.totalResidu); formData.append('NamaPetugas', tps.namaPetugas); tps.fotoKedatangan.forEach((file) => formData.append('FotoKedatangan', file)); tps.timbangan.forEach((timb) => { if (timb.file) formData.append('FotoTimbangan', timb.file); const weight = timb.berat && timb.berat.length > 0 ? timb.berat[0] : 0; const jenis = timb.jenisSampah && timb.jenisSampah.length > 0 ? timb.jenisSampah[0] : DEFAULT_JENIS; formData.append('BeratTimbangan', weight); formData.append('JenisSampahList', jenis); }); tps.fotoPetugas.forEach((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; } function uploadSingleFotoTimbangan(itemIndex) { const tps = tpsData[activeTpsIndex]; if (!tps.timbangan[itemIndex] || !tps.timbangan[itemIndex].file) { alert('Belum ada foto timbangan yang dipilih!'); return; } alert(`Upload foto timbangan #${itemIndex + 1}\nBerat: ${tps.timbangan[itemIndex].weight} kg\n(Implementasi upload ke server)`); tps.timbangan[itemIndex].uploaded = true; } function uploadFotoKedatangan() { const tps = tpsData[activeTpsIndex]; if (tps.fotoKedatangan.length === 0) { alert('Belum ada foto kedatangan yang dipilih!'); return; } alert(`Upload ${tps.fotoKedatangan.length} foto kedatangan\n(Implementasi upload ke server)`); tps.fotoKedatanganUploaded = true; renderTpsForm(); } function uploadFotoPetugas() { const tps = tpsData[activeTpsIndex]; if (tps.fotoPetugas.length === 0) { alert('Belum ada foto petugas yang dipilih!'); return; } alert(`Upload ${tps.fotoPetugas.length} foto petugas\n(Implementasi upload ke server)`); tps.fotoPetugasUploaded = true; renderTpsForm(); } function submitTpsData() { const tps = tpsData[activeTpsIndex]; if (!tps.fotoKedatangan.length) return alert('Foto kedatangan belum diupload!'); if (!tps.timbangan.length) return alert('Belum ada data timbangan!'); if (!tps.fotoPetugas.length) return alert('Foto petugas belum diupload!'); if (!tps.namaPetugas.trim()) return alert('Nama petugas belum diisi!'); alert(`Validasi OK (Tanpa TPS).\n- Organik: ${formatWeightDisplay(tps.totalOrganik)} kg\n- Anorganik: ${formatWeightDisplay(tps.totalAnorganik)} kg\n- Residu: ${formatWeightDisplay(tps.totalResidu)} kg\n- Total: ${formatWeightDisplay(tps.totalTimbangan)} kg\n- Petugas: ${tps.namaPetugas}`); tps.submitted = true; /* const formData = buildSubmitFormData(tps); fetch('/upst/detail-penjemputan', { method: 'POST', body: formData }); */ } const nomorSpjEl = document.querySelector('.text-gray-600.font-mono'); if (nomorSpjEl && nomorSpjEl.textContent) { nomorSpj = nomorSpjEl.textContent.trim(); } initializeLocation(); });