1257 lines
49 KiB
JavaScript
1257 lines
49 KiB
JavaScript
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 = `
|
|
<div class="w-full max-w-sm rounded-3xl border border-gray-200 bg-white shadow-2xl px-6 py-7 text-center">
|
|
<div class="mx-auto mb-4 h-12 w-12 animate-spin rounded-full border-4 border-gray-200 border-t-upst"></div>
|
|
<h2 class="text-base font-black text-gray-800">Sedang memuat data</h2>
|
|
<p class="mt-2 text-sm text-gray-500">Mohon tunggu sebentar, data penjemputan sedang dipulihkan.</p>
|
|
</div>
|
|
`;
|
|
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
|
|
? `
|
|
<div class="flex items-center justify-center gap-2 rounded-xl border border-green-200 bg-green-50 px-4 py-3 text-sm">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 flex-shrink-0 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
<span class="font-medium text-green-700">
|
|
Data <strong class="font-bold text-green-800">${tps.name || DEFAULT_TPS_NAME} </strong> sudah disubmit
|
|
</span>
|
|
</div>`
|
|
: `
|
|
<div class="flex gap-3">
|
|
<a href="/upst/detail-penjemputan/batal" class="w-1/3 text-center bg-red-500 text-white py-3 rounded-xl font-bold text-sm">Batal</a>
|
|
<button type="submit" ${submitState.canSubmit ? "" : "disabled"} class="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"}">Submit</button>
|
|
</div>
|
|
${submitState.canSubmit ? '' : `<p class="submit-state-message text-[11px] text-center text-red-500 font-medium">${submitState.message}</p>`}
|
|
`;
|
|
|
|
tpsContentContainer.innerHTML = `
|
|
<form class="space-y-5 pb-8" data-tps-index="${tps.index}">
|
|
<input type="hidden" class="tps-lokasi-angkut-id" value="${tps.lokasiAngkutId || ""}" />
|
|
<input type="hidden" class="tps-spj-detail-id" value="${tps.spjDetailId || ""}" />
|
|
<input type="hidden" class="tps-latitude" value="${tps.latitude}" />
|
|
<input type="hidden" class="tps-longitude" value="${tps.longitude}" />
|
|
<input type="hidden" class="tps-alamat-jalan" value="${tps.alamatJalan}" />
|
|
|
|
<section class="bg-white border border-gray-100 rounded-3xl p-5 space-y-4">
|
|
<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>
|
|
<p class="text-xs text-gray-500">Upload foto kedatangan</p>
|
|
</div>
|
|
</div>
|
|
|
|
<label class="block text-xs font-semibold text-gray-600">Upload Foto Kedatangan</label>
|
|
<input type="file" class="tps-foto-kedatangan 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/*" />
|
|
<div class="hidden tps-preview-kedatangan-wrap relative rounded-xl overflow-hidden border border-gray-200 bg-black">
|
|
<img class="tps-preview-kedatangan-img w-full h-44 object-contain" alt="Preview foto kedatangan" />
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-2">
|
|
<div>
|
|
<label class="block text-xs font-semibold text-gray-600 mb-1">Latitude</label>
|
|
<input type="text" class="tps-display-latitude w-full rounded-xl border border-gray-200 bg-gray-50 px-3 py-2 text-xs" readonly value="${tps.latitude}" />
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-semibold text-gray-600 mb-1">Longitude</label>
|
|
<input type="text" class="tps-display-longitude w-full rounded-xl border border-gray-200 bg-gray-50 px-3 py-2 text-xs" readonly value="${tps.longitude}" />
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-xs font-semibold text-gray-600 mb-1">Waktu Kedatangan</label>
|
|
<input type="text" class="tps-waktu-kedatangan w-full rounded-xl border border-gray-200 bg-gray-50 px-3 py-2 text-xs" readonly value="${tps.waktuKedatangan}" />
|
|
</div>
|
|
|
|
<div class="kedatangan-upload-state">${getKedatanganUploadStateMarkup(tps)}</div>
|
|
</section>
|
|
|
|
<section class="bg-white border border-gray-100 rounded-3xl p-5 space-y-4">
|
|
<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 Petugas </h3>
|
|
<p class="text-xs text-gray-500">Upload dokumentasi petugas</p>
|
|
</div>
|
|
</div>
|
|
|
|
<label class="block text-xs font-semibold text-gray-600">Upload Foto Petugas</label>
|
|
<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/*" />
|
|
<div class="hidden tps-preview-petugas-wrap relative rounded-xl overflow-hidden border border-gray-200 bg-black">
|
|
<img class="tps-preview-petugas-img w-full h-44 object-contain" alt="Preview foto petugas" />
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-xs font-semibold text-gray-600 mb-1">Nama Petugas</label>
|
|
<input type="text" class="tps-nama-petugas w-full rounded-xl border border-gray-200 px-3 py-2 text-sm" placeholder="Masukkan nama petugas" value="${tps.namaPetugas}" />
|
|
</div>
|
|
|
|
<div class="petugas-upload-state">${getPetugasUploadStateMarkup(tps)}</div>
|
|
</section>
|
|
|
|
${actionMarkup}
|
|
<p id="auto-save-status" class="text-[11px] text-amber-500 text-center font-medium" style="opacity:0;transition:opacity 0.4s"></p>
|
|
</form>
|
|
`;
|
|
|
|
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 = `
|
|
<div class="h-44 bg-black/80">
|
|
<img src="${imageUrl}" alt="Preview ${index + 1}" class="w-full h-full object-contain preview-multi-image" />
|
|
</div>
|
|
`;
|
|
|
|
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 = `
|
|
<div class="h-44 bg-black/80">
|
|
<img src="${imageUrl}" alt="Preview ${index + 1}" class="w-full h-full object-contain preview-multi-image" />
|
|
</div>
|
|
`;
|
|
|
|
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 '<div class="text-center text-xs text-green-600 font-bold py-2">✓ Foto kedatangan sudah diupload</div>';
|
|
}
|
|
if (!tps.fotoKedatangan.length) {
|
|
return "";
|
|
}
|
|
return `<button type="button" class="tps-btn-upload-kedatangan w-full bg-blue-500 text-white py-2 rounded-xl font-bold text-xs hover:brightness-110">Upload ${tps.fotoKedatangan.length} Foto Kedatangan</button>`;
|
|
}
|
|
|
|
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 '<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);
|
|
}
|
|
|
|
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 = `
|
|
<div class="h-44 bg-black/80">
|
|
<img src="${url}" alt="Foto ${index + 1}" class="w-full h-full object-contain" loading="lazy" />
|
|
</div>
|
|
`;
|
|
} else {
|
|
item.className = 'rounded-xl border border-green-200 bg-green-50 h-44 flex items-center justify-center';
|
|
item.innerHTML = `<p class="text-xs text-green-700 font-semibold px-3">✓ Foto ${index + 1}</p>`;
|
|
}
|
|
container.appendChild(item);
|
|
});
|
|
}
|
|
});
|