update: non tps ganti jadi 1 TPS, fixing js non tps

main
muamars 2026-03-11 12:07:55 +07:00
parent 4e6675d782
commit 8a7ce4a228
6 changed files with 365 additions and 187 deletions

View File

@ -1,6 +1,6 @@
@{
Layout = "~/Views/Admin/Transport/SpjDriverUpst/Shared/_Layout.cshtml";
ViewData["Title"] = "Detail Penjemputan - Tanpa TPS";
ViewData["Title"] = "Detail Penjemputan - TPS A";
}
<div class="w-full lg:max-w-sm mx-auto min-h-screen bg-gray-50 pb-24">
@ -13,7 +13,7 @@
<a href="@Url.Action("Index", "Home")" class="w-10 h-10 flex items-center justify-center bg-white/10 rounded-xl backdrop-blur-md">
<i class="w-5 h-5" data-lucide="chevron-left"></i>
</a>
<h1 class="text-lg font-black uppercase tracking-tight">Detail Lokasi</h1>
<h1 id="detail-page-title" class="text-lg font-black uppercase tracking-tight">TPS A</h1>
<div class="w-10"></div>
</div>
</div>
@ -27,7 +27,7 @@
<div class="h-8 w-[1px] bg-gray-100"></div>
<div class="text-right">
<span class="text-[10px] font-black text-gray-400 uppercase tracking-widest">No. Pintu</span>
<p class="text-orange-500 font-bold text-sm">JRC 005</p>
<p id="detail-nomor-pintu" class="text-orange-500 font-bold text-sm">JRC 005</p>
</div>
</div>
</div>
@ -35,24 +35,24 @@
<div class="px-6 space-y-6">
<div class="bg-white rounded-[32px] p-6 border border-gray-100 relative overflow-hidden">
<div class="absolute top-0 right-0 p-4">
<span class="px-3 py-1 bg-gray-500 text-white rounded-full text-[10px] font-black tracking-tighter uppercase">
TANPA TPS
<span id="detail-tps-badge" class="px-3 py-1 bg-gray-500 text-white rounded-full text-[10px] font-black tracking-tighter uppercase">
TPS A
</span>
</div>
<div class="space-y-4">
<div>
<p class="text-[10px] text-gray-400 uppercase tracking-widest mb-1">Perusahaan</p>
<h2 class="text-xl font-black text-gray-800 leading-tight">CV Tri Berkah Sejahtera</h2>
<h2 id="detail-company-name" class="text-xl font-black text-gray-800 leading-tight">CV Tri Berkah Sejahtera</h2>
</div>
<div class="flex items-center justify-center gap-2 bg-gray-50 p-2 rounded-xl">
<p class="text-gray-600 font-mono text-xl font-bold uppercase tracking-tighter">SPJ/07-2025/PKM/000476</p>
<p id="detail-spj-number" class="text-gray-600 font-mono text-xl font-bold uppercase tracking-tighter">SPJ/07-2025/PKM/000476</p>
</div>
<div class="pt-2 border-t border-gray-50">
<div class="flex gap-2 items-start">
<p class="text-gray-500 text-xs font-medium leading-relaxed">
<p id="detail-address" class="text-gray-500 text-xs font-medium leading-relaxed">
Kp. Pertanian II Rt.004 Rw.001 Kel. Klender Kec, Duren Sawit, Kota Adm. Jakarta Timur 13470
</p>
</div>
@ -87,9 +87,8 @@
<div id="tps-tabs-container" class="bg-white rounded-[32px] p-6 border border-gray-100">
<div class="flex items-center gap-2 mb-4">
<div class="w-2 h-2 rounded-full bg-gray-400"></div>
<h3 class="font-black text-gray-800">Form Pengangkutan</h3>
<span class="ml-auto text-[10px] font-bold text-gray-500 bg-gray-100 px-3 py-1 rounded-full">Tanpa TPS</span>
<span id="detail-form-badge" class="ml-auto text-[10px] font-bold text-gray-500 bg-gray-100 px-3 py-1 rounded-full">TPS A</span>
</div>
<div id="tps-content">
</div>

View File

@ -134,7 +134,25 @@
<p class="text-[10px] truncate italic">Jakarta Timur 13470</p>
</div>
<div class="mt-2">
<span class="text-[9px] font-bold text-blue-600 bg-blue-50 px-2 py-1 rounded-lg">Ada 3 TPS</span>
<span class="text-[9px] font-bold text-blue-600 bg-blue-50 px-2 py-1 rounded-lg">3 TPS</span>
</div>
</div>
</a>
<a href="@Url.Action("TanpaTps", "DetailPenjemputan")" class="block relative pl-12 group">
<div class="absolute left-4 top-1 w-4 h-4 bg-white border-4 border-gray-400 rounded-full z-10"></div>
<div class="bg-white p-4 rounded-3xl border border-gray-100 shadow-sm group-active:bg-gray-50 transition-colors">
<div class="flex justify-between items-start mb-2">
<span class="text-[9px] font-black text-gray-400 uppercase tracking-widest">Proses</span>
<i class="w-4 h-4 text-gray-300" data-lucide="loader"></i>
</div>
<h4 class="font-bold text-gray-800 text-sm leading-tight">CV Tri Berkah Sejahtera</h4>
<div class="flex items-center gap-1 mt-2 text-gray-500">
<i class="w-3 h-3" data-lucide="map"></i>
<p class="text-[10px] truncate italic">Duren Sawit, Jakarta Timur</p>
</div>
<div class="mt-2">
<span class="text-[9px] font-bold text-gray-600 bg-gray-100 px-2 py-1 rounded-lg">1 TPS</span>
</div>
</div>
</a>
@ -149,7 +167,7 @@
<h4 class="font-bold text-gray-800 text-sm leading-tight">CV Tri Mitra Utama - Shell Radio Dalam</h4>
<p class="text-[10px] text-green-700/60 mt-2">Jakarta Selatan</p>
<div class="mt-2">
<span class="text-[9px] font-bold text-blue-600 bg-blue-50 px-2 py-1 rounded-lg">Ada 3 TPS</span>
<span class="text-[9px] font-bold text-blue-600 bg-blue-50 px-2 py-1 rounded-lg">3 TPS</span>
</div>
</div>
</a>
@ -164,7 +182,7 @@
<h4 class="font-bold text-gray-800 text-sm leading-tight">CV Tri Berkah Sejahtera</h4>
<p class="text-[10px] text-green-700/60 mt-2">Duren Sawit, Jakarta Timur</p>
<div class="mt-2">
<span class="text-[9px] font-bold text-gray-600 bg-gray-100 px-2 py-1 rounded-lg">Tidak ada TPS</span>
<span class="text-[9px] font-bold text-gray-600 bg-gray-100 px-2 py-1 rounded-lg">1 TPS</span>
</div>
</div>
</a>

View File

@ -109,8 +109,6 @@
--text-xl--line-height: calc(1.75 / 1.25);
--text-2xl: 1.5rem;
--text-2xl--line-height: calc(2 / 1.5);
--text-5xl: 3rem;
--text-5xl--line-height: 1;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
@ -569,12 +567,6 @@
.col-auto {
grid-column: auto;
}
.col-span-1 {
grid-column: span 1 / span 1;
}
.col-span-2 {
grid-column: span 2 / span 2;
}
.float-end {
float: inline-end;
}
@ -806,12 +798,6 @@
.ml-auto {
margin-left: auto;
}
.line-clamp-2 {
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.line-clamp-3 {
overflow: hidden;
display: -webkit-box;
@ -875,9 +861,6 @@
.h-3 {
height: calc(var(--spacing) * 3);
}
.h-3\.5 {
height: calc(var(--spacing) * 3.5);
}
.h-4 {
height: calc(var(--spacing) * 4);
}
@ -986,9 +969,6 @@
.w-3 {
width: calc(var(--spacing) * 3);
}
.w-3\.5 {
width: calc(var(--spacing) * 3.5);
}
.w-4 {
width: calc(var(--spacing) * 4);
}
@ -1235,9 +1215,6 @@
.gap-1 {
gap: calc(var(--spacing) * 1);
}
.gap-1\.5 {
gap: calc(var(--spacing) * 1.5);
}
.gap-2 {
gap: calc(var(--spacing) * 2);
}
@ -1691,6 +1668,9 @@
.bg-gray-200 {
background-color: var(--color-gray-200);
}
.bg-gray-300 {
background-color: var(--color-gray-300);
}
.bg-gray-400 {
background-color: var(--color-gray-400);
}
@ -2138,9 +2118,6 @@
.py-1 {
padding-block: calc(var(--spacing) * 1);
}
.py-1\.5 {
padding-block: calc(var(--spacing) * 1.5);
}
.py-2 {
padding-block: calc(var(--spacing) * 2);
}
@ -2159,9 +2136,6 @@
.py-6 {
padding-block: calc(var(--spacing) * 6);
}
.py-10 {
padding-block: calc(var(--spacing) * 10);
}
.py-12 {
padding-block: calc(var(--spacing) * 12);
}
@ -2436,12 +2410,6 @@
.text-blue-700 {
color: var(--color-blue-700);
}
.text-blue-700\/60 {
color: color-mix(in srgb, oklch(48.8% 0.243 264.376) 60%, transparent);
@supports (color: color-mix(in lab, red, red)) {
color: color-mix(in oklab, var(--color-blue-700) 60%, transparent);
}
}
.text-blue-800 {
color: var(--color-blue-800);
}
@ -2859,13 +2827,6 @@
}
}
}
.group-hover\:border-gray-200 {
&:is(:where(.group):hover *) {
@media (hover: hover) {
border-color: var(--color-gray-200);
}
}
}
.group-hover\:text-gray-500 {
&:is(:where(.group):hover *) {
@media (hover: hover) {
@ -2887,14 +2848,6 @@
}
}
}
.group-hover\:shadow {
&:is(:where(.group):hover *) {
@media (hover: hover) {
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
}
}
.group-hover\:shadow-md {
&:is(:where(.group):hover *) {
@media (hover: hover) {
@ -3021,13 +2974,6 @@
}
}
}
.hover\:border-gray-200 {
&:hover {
@media (hover: hover) {
border-color: var(--color-gray-200);
}
}
}
.hover\:border-orange-200 {
&:hover {
@media (hover: hover) {
@ -3346,11 +3292,6 @@
scale: var(--tw-scale-x) var(--tw-scale-y);
}
}
.sm\:grid-cols-4 {
@media (width >= 40rem) {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
}
.lg\:max-w-sm {
@media (width >= 64rem) {
max-width: var(--container-sm);

View File

@ -16,10 +16,12 @@ document.addEventListener('DOMContentLoaded', function() {
];
const JENIS_SAMPAH = ['Organik', 'Anorganik', 'Residu'];
const DEFAULT_JENIS = 'Residu';
const DETAIL_DATA_URL = '/driver/json/detail-penjemputan-non-tps.json';
const DEFAULT_TPS_NAME = 'Lokasi Pengangkutan 1';
function initializeLocation() {
tpsData = [{
name: '',
name: DEFAULT_TPS_NAME,
index: 0,
lokasiAngkutId: '',
spjDetailId: '',
@ -43,6 +45,53 @@ document.addEventListener('DOMContentLoaded', function() {
renderTpsForm();
}
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);
renderTpsForm();
} 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];
@ -62,7 +111,7 @@ document.addEventListener('DOMContentLoaded', function() {
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-full bg-upst text-white font-black text-sm flex items-center justify-center">1</div>
<div>
<h3 class="font-black text-gray-800">Foto Kedatangan</h3>
<h3 class="font-black text-gray-800">Foto Kedatangan </h3>
<p class="text-xs text-gray-500">Upload foto kedatangan</p>
</div>
</div>
@ -102,7 +151,7 @@ document.addEventListener('DOMContentLoaded', function() {
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-full bg-upst text-white font-black text-sm flex items-center justify-center">2</div>
<div>
<h3 class="font-black text-gray-800">Foto Timbang Sampah</h3>
<h3 class="font-black text-gray-800">Foto Timbang Sampah </h3>
<p class="text-xs text-gray-500">Upload foto timbangan, berat auto terisi</p>
</div>
</div>
@ -118,7 +167,7 @@ document.addEventListener('DOMContentLoaded', function() {
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-full bg-upst text-white font-black text-sm flex items-center justify-center">3</div>
<div>
<h3 class="font-black text-gray-800">Foto Petugas</h3>
<h3 class="font-black text-gray-800">Foto Petugas </h3>
<p class="text-xs text-gray-500">Upload dokumentasi petugas</p>
</div>
</div>
@ -127,15 +176,7 @@ document.addEventListener('DOMContentLoaded', function() {
<input type="file" class="tps-foto-petugas block w-full text-sm text-gray-700 border border-gray-200 rounded-xl p-2 file:mr-3 file:rounded-lg file:border-0 file:bg-upst file:px-3 file:py-2 file:text-xs file:font-bold file:text-white" accept="image/*" multiple />
<div class="tps-preview-petugas space-y-2"></div>
${tps.fotoPetugas.length > 0 && !tps.fotoPetugasUploaded ? `
<button type="button" class="tps-btn-upload-petugas w-full bg-blue-500 text-white py-2 rounded-xl font-bold text-xs hover:brightness-110">
Upload ${tps.fotoPetugas.length} Foto Petugas
</button>
` : tps.fotoPetugasUploaded ? `
<div class="text-center text-xs text-green-600 font-bold py-2">
Foto petugas sudah diupload
</div>
` : ''}
<div class="petugas-upload-state">${getPetugasUploadStateMarkup(tps)}</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Nama Petugas</label>
@ -227,6 +268,7 @@ document.addEventListener('DOMContentLoaded', function() {
namaPetugasInput.addEventListener('input', function() {
tps.namaPetugas = this.value;
refreshPetugasUploadState(form);
});
btnAddTimbangan.addEventListener('click', function() {
@ -526,7 +568,7 @@ document.addEventListener('DOMContentLoaded', function() {
async function autoFillWeight(file, weightInput, ocrInfoEl) {
let guessedWeight = 0;
weightInput.placeholder = 'Membaca angka dari foto...';
if (ocrInfoEl) ocrInfoEl.textContent = 'AI OCR: memproses gambar...';
if (ocrInfoEl) ocrInfoEl.textContent = 'AI: memproses gambar...';
try {
const img = await readFileAsImage(file);
@ -551,12 +593,12 @@ document.addEventListener('DOMContentLoaded', function() {
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.');
? `AI terbaca: ${cleaned}`
: (cleaned ? `AI tidak valid: ${cleaned}` : 'AI tidak menemukan angka valid.');
}
} catch (_) {
guessedWeight = 0;
if (ocrInfoEl) ocrInfoEl.textContent = 'AI OCR gagal diproses.';
if (ocrInfoEl) ocrInfoEl.textContent = 'AI gagal diproses.';
}
if (guessedWeight > 0) {
@ -606,18 +648,119 @@ document.addEventListener('DOMContentLoaded', function() {
if (grandTotalResiduDisplay) grandTotalResiduDisplay.textContent = formatWeightDisplay(totalResidu);
}
function getTimbanganUploadStateMarkup(hasFile, isUploaded, hasValidWeight) {
if (!hasFile) {
return '<p class="text-[11px] text-gray-400">Pilih foto timbangan terlebih dahulu</p>';
}
if (isUploaded) {
return `
<div class="text-center text-xs text-green-600 font-bold py-2"> Foto timbangan sudah diupload</div>
<p class="text-[11px] text-gray-500 text-center">Kalau mau revisi, pilih file baru di atas. Status upload akan direset otomatis.</p>
`;
}
if (!hasValidWeight) {
return `
<button type="button" disabled class="btn-upload-timbangan w-full bg-gray-300 text-gray-500 py-2 rounded-xl font-bold text-xs cursor-not-allowed">Upload Foto Timbangan Ini</button>
<p class="text-[11px] text-red-500 text-center">Isi berat manual dulu sebelum upload jika berat tidak terbaca.</p>
`;
}
return `
<button type="button" class="btn-upload-timbangan w-full bg-blue-500 text-white py-2 rounded-xl font-bold text-xs hover:brightness-110">Upload Foto Timbangan Ini</button>
<p class="text-[11px] text-amber-600 text-center">Foto siap diupload.</p>
`;
}
function getPetugasUploadStateMarkup(tps) {
if (tps.fotoPetugasUploaded) {
return '<div class="text-center text-xs text-green-600 font-bold py-2">✓ Foto petugas sudah diupload</div>';
}
if (!tps.fotoPetugas.length) {
return '';
}
if (!tps.namaPetugas.trim()) {
return `
<button type="button" disabled class="tps-btn-upload-petugas w-full bg-gray-300 text-gray-500 py-2 rounded-xl font-bold text-xs cursor-not-allowed">Upload ${tps.fotoPetugas.length} Foto Petugas</button>
<p class="text-[11px] text-red-500 text-center">Isi nama petugas terlebih dahulu</p>
`;
}
return `<button type="button" class="tps-btn-upload-petugas w-full bg-blue-500 text-white py-2 rounded-xl font-bold text-xs hover:brightness-110">Upload ${tps.fotoPetugas.length} Foto Petugas</button>`;
}
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);
}
}
function refreshTimbanganUploadState(item) {
const stateContainer = item.querySelector('.timbangan-upload-state');
if (!stateContainer) return;
const repeater = item.parentElement;
const itemIndex = repeater ? Array.from(repeater.children).indexOf(item) : -1;
const tps = tpsData[activeTpsIndex];
const currentData = itemIndex >= 0 ? tps.timbangan[itemIndex] : null;
const fileInput = item.querySelector('.input-foto-timbangan');
const hasFile = Boolean(currentData?.file || fileInput?.files?.[0]);
const isUploaded = Boolean(currentData?.uploaded);
const weightInputValue = item.querySelector('.input-berat-timbangan-value');
const currentWeight = currentData?.berat && currentData.berat.length > 0
? currentData.berat[0]
: parseWeightInput(weightInputValue?.value || '0');
const hasValidWeight = currentWeight > 0;
stateContainer.innerHTML = getTimbanganUploadStateMarkup(hasFile, isUploaded, hasValidWeight);
const uploadBtn = stateContainer.querySelector('.btn-upload-timbangan');
if (uploadBtn) {
uploadBtn.addEventListener('click', function() {
const latestIndex = repeater ? Array.from(repeater.children).indexOf(item) : -1;
uploadSingleFotoTimbangan(latestIndex, item);
});
}
}
function renumberTimbanganItems(repeater) {
const items = repeater.querySelectorAll('.timbangan-item');
items.forEach((item, index) => {
const newNumber = index + 1;
item.dataset.photoNumber = newNumber;
const label = item.querySelector('.text-xs.font-bold.text-gray-600');
if (label) {
label.textContent = `Item Timbangan #${newNumber}`;
}
});
}
function createTimbanganItem(repeater, existingData = null) {
const photoNumber = repeater.children.length + 1;
const item = document.createElement('div');
item.className = 'timbangan-item rounded-2xl border border-gray-200 p-3 space-y-2 bg-gray-50';
item.dataset.photoNumber = photoNumber;
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;
const ocrInfoText = existingData && existingData.ocrInfo ? existingData.ocrInfo : (hasFile ? 'OCR: diproses.' : 'OCR: belum diproses.');
item.innerHTML = `
<div class="flex items-center justify-between">
<p class="text-xs font-bold text-gray-600">Item Timbangan</p>
<p class="text-xs font-bold text-gray-600">Item Timbangan #${photoNumber}</p>
<button type="button" class="btn-remove-timbangan text-[11px] font-bold text-red-500">Hapus</button>
</div>
<input type="file" name="FotoTimbangan" accept="image/*" class="input-foto-timbangan block w-full text-sm text-gray-700 border border-gray-200 rounded-xl p-2 file:mr-3 file:rounded-lg file:border-0 file:bg-upst file:px-3 file:py-2 file:text-xs file:font-bold file:text-white" />
@ -625,10 +768,8 @@ document.addEventListener('DOMContentLoaded', function() {
<img class="input-preview-image w-full h-44 object-contain" alt="Preview foto timbangan" />
<div class="input-crop-overlay absolute inset-0 pointer-events-none"></div>
</div>
<p class="text-[11px] text-gray-500 input-ocr-info">${hasFile ? 'OCR: diproses.' : 'OCR: belum diproses.'}</p>
${hasFile && !isUploaded ? `
<button type="button" class="btn-upload-timbangan w-full bg-blue-500 text-white py-2 rounded-xl font-bold text-xs hover:brightness-110">Upload Foto Timbangan Ini</button>
` : isUploaded ? `<div class="text-center text-xs text-green-600 font-bold py-2"> Foto timbangan sudah diupload</div>` : ''}
<p class="text-[11px] text-gray-500 input-ocr-info">${ocrInfoText}</p>
<div class="timbangan-upload-state">${getTimbanganUploadStateMarkup(hasFile, isUploaded, weight > 0)}</div>
<div class="grid grid-cols-1 gap-2">
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Jenis Sampah</label>
@ -662,7 +803,7 @@ document.addEventListener('DOMContentLoaded', function() {
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 photoNumber = Number(item.dataset.photoNumber || (Array.from(repeater.children).indexOf(item) + 1));
const watermarkedFile = await applyWatermark(originalFile, photoNumber);
const dataTransfer = new DataTransfer();
@ -684,16 +825,7 @@ document.addEventListener('DOMContentLoaded', function() {
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);
}
refreshTimbanganUploadState(item);
}
}
});
@ -705,6 +837,7 @@ document.addEventListener('DOMContentLoaded', function() {
weightInputValue.value = parsed.toFixed(2);
updateTpsTotalTimbangan();
syncTimbanganToTpsData();
refreshTimbanganUploadState(item);
});
weightInputDisplay.addEventListener('blur', function() {
@ -718,6 +851,7 @@ document.addEventListener('DOMContentLoaded', function() {
}
updateTpsTotalTimbangan();
syncTimbanganToTpsData();
refreshTimbanganUploadState(item);
});
jenisSampahSelect.addEventListener('change', function() {
@ -729,20 +863,16 @@ document.addEventListener('DOMContentLoaded', 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);
if (rep) {
renumberTimbanganItems(rep);
if (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);
refreshTimbanganUploadState(item);
return item;
}
@ -753,20 +883,23 @@ document.addEventListener('DOMContentLoaded', function() {
const repeater = form.querySelector('.tps-timbangan-repeater');
const items = repeater.querySelectorAll('.timbangan-item');
const previousTimbangan = [...tps.timbangan];
tps.timbangan = [];
items.forEach(item => {
items.forEach((item, index) => {
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;
const ocrInfo = item.querySelector('.input-ocr-info')?.textContent || 'OCR: belum diproses.';
tps.timbangan.push({
file: fileInput.files[0] || null,
berat: [weight],
jenisSampah: [jenisSampah],
lokasiAngkut: [],
uploaded: false
uploaded: previousTimbangan[index]?.uploaded ?? false,
ocrInfo
});
});
}
@ -803,14 +936,32 @@ document.addEventListener('DOMContentLoaded', function() {
return formData;
}
function uploadSingleFotoTimbangan(itemIndex) {
function uploadSingleFotoTimbangan(itemIndex, targetItem = null) {
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)`);
const weight = tps.timbangan[itemIndex].berat && tps.timbangan[itemIndex].berat.length > 0
? tps.timbangan[itemIndex].berat[0]
: 0;
if (weight <= 0) {
alert('Berat belum valid. Isi manual dulu sebelum upload foto timbangan.');
return;
}
alert(`Upload foto timbangan #${itemIndex + 1}\nBerat: ${formatWeightDisplay(weight)} kg\n(Implementasi upload ke server)`);
tps.timbangan[itemIndex].uploaded = true;
if (!targetItem) {
const form = tpsContentContainer.querySelector('form');
const repeater = form ? form.querySelector('.tps-timbangan-repeater') : null;
const items = repeater ? repeater.querySelectorAll('.timbangan-item') : [];
targetItem = items[itemIndex] || null;
}
if (targetItem) {
refreshTimbanganUploadState(targetItem);
}
}
function uploadFotoKedatangan() {
@ -830,7 +981,11 @@ document.addEventListener('DOMContentLoaded', function() {
alert('Belum ada foto petugas yang dipilih!');
return;
}
alert(`Upload ${tps.fotoPetugas.length} foto petugas\n(Implementasi upload ke server)`);
if (!tps.namaPetugas.trim()) {
alert('Nama petugas wajib diisi sebelum upload foto petugas!');
return;
}
alert(`Upload ${tps.fotoPetugas.length} foto petugas untuk ${tps.name}\n(Implementasi upload ke server)`);
tps.fotoPetugasUploaded = true;
renderTpsForm();
}
@ -842,7 +997,7 @@ document.addEventListener('DOMContentLoaded', function() {
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}`);
alert(`Validasi OK (${tps.name}).\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;
/*
@ -857,4 +1012,5 @@ document.addEventListener('DOMContentLoaded', function() {
}
initializeLocation();
loadDetailData();
});

View File

@ -312,15 +312,7 @@ const DetailPenjemputan = (function() {
<input type="file" class="tps-foto-petugas block w-full text-sm text-gray-700 border border-gray-200 rounded-xl p-2 file:mr-3 file:rounded-lg file:border-0 file:bg-upst file:px-3 file:py-2 file:text-xs file:font-bold file:text-white" accept="image/*" multiple />
<div class="tps-preview-petugas space-y-2"></div>
${tps.fotoPetugas.length > 0 && !tps.fotoPetugasUploaded ? `
<button type="button" class="tps-btn-upload-petugas w-full bg-blue-500 text-white py-2 rounded-xl font-bold text-xs hover:brightness-110">
Upload ${tps.fotoPetugas.length} Foto Petugas
</button>
` : tps.fotoPetugasUploaded ? `
<div class="text-center text-xs text-green-600 font-bold py-2">
Foto petugas sudah diupload
</div>
` : ''}
<div class="petugas-upload-state">${getPetugasUploadStateMarkup(tps)}</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Nama Petugas</label>
@ -356,6 +348,7 @@ const DetailPenjemputan = (function() {
namaPetugasInput.addEventListener('input', function() {
tps.namaPetugas = this.value;
refreshPetugasUploadState(form);
});
btnAddTimbangan.addEventListener('click', function() {
@ -546,6 +539,7 @@ const DetailPenjemputan = (function() {
const jenisSampah = existingData ? existingData.jenisSampah : CONFIG.DEFAULT_JENIS;
const hasFile = existingData && existingData.file;
const isUploaded = existingData && existingData.uploaded;
const ocrInfoText = existingData && existingData.ocrInfo ? existingData.ocrInfo : (hasFile ? 'OCR: diproses.' : 'OCR: belum diproses.');
item.innerHTML = `
<div class="flex items-center justify-between">
@ -556,16 +550,8 @@ const DetailPenjemputan = (function() {
<div class="${hasFile ? '' : 'hidden'} input-preview-wrap relative rounded-xl overflow-hidden border border-gray-200 bg-black">
<img class="input-preview-image w-full h-44 object-contain" alt="Preview foto timbangan" />
</div>
<p class="text-[11px] text-gray-500 input-ocr-info">${hasFile ? 'OCR: diproses.' : 'OCR: belum diproses.'}</p>
${hasFile && !isUploaded ? `
<button type="button" class="btn-upload-timbangan w-full bg-blue-500 text-white py-2 rounded-xl font-bold text-xs hover:brightness-110">
Upload Foto Timbangan Ini
</button>
` : isUploaded ? `
<div class="text-center text-xs text-green-600 font-bold py-2">
Foto timbangan sudah diupload
</div>
` : ''}
<p class="text-[11px] text-gray-500 input-ocr-info">${ocrInfoText}</p>
<div class="timbangan-upload-state">${getTimbanganUploadStateMarkup(hasFile, isUploaded, weight > 0)}</div>
<div class="grid grid-cols-1 gap-2">
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Jenis Sampah</label>
@ -625,19 +611,7 @@ const DetailPenjemputan = (function() {
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);
}
refreshTimbanganUploadState(item);
}
}
});
@ -649,6 +623,7 @@ const DetailPenjemputan = (function() {
weightInputValue.value = parsed.toFixed(2);
updateTpsTotalTimbangan();
syncTimbanganToTpsData();
refreshTimbanganUploadState(item);
});
weightInputDisplay.addEventListener('blur', function() {
@ -662,6 +637,7 @@ const DetailPenjemputan = (function() {
}
updateTpsTotalTimbangan();
syncTimbanganToTpsData();
refreshTimbanganUploadState(item);
});
jenisSampahSelect.addEventListener('change', function() {
@ -685,16 +661,92 @@ const DetailPenjemputan = (function() {
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);
refreshTimbanganUploadState(item);
return item;
}
function getTimbanganUploadStateMarkup(hasFile, isUploaded, hasValidWeight) {
if (!hasFile) {
return '<p class="text-[11px] text-gray-400">Pilih foto timbangan terlebih dahulu</p>';
}
repeater.appendChild(item);
return item;
if (isUploaded) {
return `
<div class="text-center text-xs text-green-600 font-bold py-2 upload-success-message"> Foto timbangan sudah diupload</div>
<p class="text-[11px] text-gray-500 text-center">Kalau mau revisi, pilih file baru di atas. Status upload akan direset otomatis.</p>
`;
}
if (!hasValidWeight) {
return `
<button type="button" disabled class="btn-upload-timbangan w-full bg-gray-300 text-gray-500 py-2 rounded-xl font-bold text-xs cursor-not-allowed">Upload Foto Timbangan Ini</button>
<p class="text-[11px] text-red-500 text-center">Isi berat manual dulu sebelum upload jika berat tidak terbaca.</p>
`;
}
return `
<button type="button" class="btn-upload-timbangan w-full bg-blue-500 text-white py-2 rounded-xl font-bold text-xs hover:brightness-110">Upload Foto Timbangan Ini</button>
<p class="text-[11px] text-amber-600 text-center">Foto siap diupload.</p>
`;
}
function getPetugasUploadStateMarkup(tps) {
if (tps.fotoPetugasUploaded) {
return '<div class="text-center text-xs text-green-600 font-bold py-2">✓ Foto petugas sudah diupload</div>';
}
if (!tps.fotoPetugas.length) {
return '';
}
if (!tps.namaPetugas.trim()) {
return `
<button type="button" disabled class="tps-btn-upload-petugas w-full bg-gray-300 text-gray-500 py-2 rounded-xl font-bold text-xs cursor-not-allowed">Upload ${tps.fotoPetugas.length} Foto Petugas</button>
<p class="text-[11px] text-red-500 text-center">Isi nama petugas terlebih dahulu</p>
`;
}
return `<button type="button" class="tps-btn-upload-petugas w-full bg-blue-500 text-white py-2 rounded-xl font-bold text-xs hover:brightness-110">Upload ${tps.fotoPetugas.length} Foto Petugas</button>`;
}
function refreshPetugasUploadState(form) {
const stateContainer = form.querySelector('.petugas-upload-state');
if (!stateContainer) return;
const tps = state.tpsData[state.activeTpsIndex];
stateContainer.innerHTML = getPetugasUploadStateMarkup(tps);
const btnUploadPetugas = stateContainer.querySelector('.tps-btn-upload-petugas:not([disabled])');
if (btnUploadPetugas) {
btnUploadPetugas.addEventListener('click', uploadFotoPetugas);
}
}
function refreshTimbanganUploadState(item) {
const stateContainer = item.querySelector('.timbangan-upload-state');
if (!stateContainer) return;
const repeater = item.parentElement;
const itemIndex = repeater ? Array.from(repeater.children).indexOf(item) : -1;
const tps = state.tpsData[state.activeTpsIndex];
const currentData = itemIndex >= 0 ? tps.timbangan[itemIndex] : null;
const fileInput = item.querySelector('.input-foto-timbangan');
const hasFile = Boolean(currentData?.file || fileInput?.files?.[0]);
const isUploaded = Boolean(currentData?.uploaded);
const weightInputValue = item.querySelector('.input-berat-timbangan-value');
const currentWeight = currentData?.weight ?? parseWeightInput(weightInputValue?.value || '0');
const hasValidWeight = currentWeight > 0;
stateContainer.innerHTML = getTimbanganUploadStateMarkup(hasFile, isUploaded, hasValidWeight);
const uploadBtn = stateContainer.querySelector('.btn-upload-timbangan');
if (uploadBtn) {
uploadBtn.addEventListener('click', function() {
const latestIndex = repeater ? Array.from(repeater.children).indexOf(item) : -1;
uploadSingleFotoTimbangan(latestIndex, item);
});
}
}
function renumberTimbanganItems(repeater) {
@ -1023,26 +1075,27 @@ async function applyWatermark(file, photoNumber) {
const repeater = form.querySelector('.tps-timbangan-repeater');
const items = repeater.querySelectorAll('.timbangan-item');
const previousTimbangan = [...tps.timbangan];
tps.timbangan = [];
items.forEach(item => {
items.forEach((item, index) => {
const fileInput = item.querySelector('.input-foto-timbangan');
const weightValue = item.querySelector('.input-berat-timbangan-value');
const jenisSampahSelect = item.querySelector('.input-jenis-sampah');
const existingIndex = tps.timbangan.length;
const existingData = tps.timbangan[existingIndex];
const ocrInfo = item.querySelector('.input-ocr-info')?.textContent || 'OCR: belum diproses.';
const existingData = previousTimbangan[index];
tps.timbangan.push({
file: fileInput.files[0] || (existingData ? existingData.file : null),
weight: parseWeightInput(weightValue.value),
jenisSampah: jenisSampahSelect.value,
uploaded: existingData ? existingData.uploaded : false
uploaded: existingData ? existingData.uploaded : false,
ocrInfo
});
});
}
function uploadSingleFotoTimbangan(itemIndex) {
function uploadSingleFotoTimbangan(itemIndex, targetItem = null) {
const tps = state.tpsData[state.activeTpsIndex];
if (!tps.timbangan[itemIndex] || !tps.timbangan[itemIndex].file) {
@ -1051,29 +1104,24 @@ async function applyWatermark(file, photoNumber) {
}
const timbanganItem = tps.timbangan[itemIndex];
if (timbanganItem.weight <= 0) {
alert('Berat belum valid. Isi manual dulu sebelum upload foto timbangan.');
return;
}
alert(`Upload foto timbangan #${itemIndex + 1} untuk ${tps.name}\nJenis: ${timbanganItem.jenisSampah}\nBerat: ${timbanganItem.weight} kg\n(Implementasi upload ke server)`);
timbanganItem.uploaded = true;
const form = elements.tpsContentContainer.querySelector('form');
const repeater = form.querySelector('.tps-timbangan-repeater');
const items = repeater.querySelectorAll('.timbangan-item');
const targetItem = items[itemIndex];
if (!targetItem) {
const form = elements.tpsContentContainer.querySelector('form');
const repeater = form ? form.querySelector('.tps-timbangan-repeater') : null;
const items = repeater ? repeater.querySelectorAll('.timbangan-item') : [];
targetItem = items[itemIndex] || null;
}
if (targetItem) {
const uploadBtn = targetItem.querySelector('.btn-upload-timbangan');
if (uploadBtn) {
uploadBtn.remove();
}
const ocrInfo = targetItem.querySelector('.input-ocr-info');
if (ocrInfo && !targetItem.querySelector('.upload-success-message')) {
const successMsg = document.createElement('div');
successMsg.className = 'text-center text-xs text-green-600 font-bold py-2 upload-success-message';
successMsg.textContent = '✓ Foto timbangan sudah diupload';
ocrInfo.parentNode.insertBefore(successMsg, ocrInfo.nextSibling);
}
refreshTimbanganUploadState(targetItem);
}
}
@ -1096,6 +1144,10 @@ async function applyWatermark(file, photoNumber) {
alert('Belum ada foto petugas yang dipilih!');
return;
}
if (!tps.namaPetugas.trim()) {
alert('Nama petugas wajib diisi sebelum upload foto petugas!');
return;
}
alert(`Upload ${tps.fotoPetugas.length} foto petugas untuk ${tps.name}\n(Implementasi upload ke server)`);

View File

@ -0,0 +1,12 @@
{
"detailPenjemputan": {
"namaTps": "TPS A",
"namaPerusahaan": "CV Tri Berkah Sejahtera",
"nomorSpj": "SPJ/07-2025/PKM/000476",
"platNomor": "B 9632 TOR",
"nomorPintu": "JRC 005",
"alamat": "Kp. Pertanian II Rt.004 Rw.001 Kel. Klender Kec, Duren Sawit, Kota Adm. Jakarta Timur 13470",
"lokasiAngkutId": "",
"spjDetailId": ""
}
}