Spaces:
Running
Running
| if (window.location.pathname.endsWith('/index.html')) { | |
| const cleanUrl = window.location.pathname.replace('/index.html', '/') + window.location.search + window.location.hash; | |
| window.history.replaceState(null, '', cleanUrl); | |
| } | |
| let state = { | |
| user: null, role: null, | |
| options: { items: [] }, | |
| data: [], | |
| currentMode: 'ADD', | |
| currentRowIndex: null, | |
| currentPage: 1, | |
| rowsPerPage: 10 | |
| }; | |
| const ENC = "aHR0cHM6Ly9zY3JpcHQuZ29vZ2xlLmNvbS9tYWNyb3Mvcy9BS2Z5Y2J4MG5tbDVJc1d0NXh1VWVNTGZpZjY5OU9MSEQ5NF9rdGZneExUWnRoQ1VpUDIyMFJhbE5kUDdfaFh1WGUyREpyMWhBQS9leGVj"; | |
| const GWP = atob(ENC); | |
| async function apiCall(action, payload = {}) { | |
| const reqPayload = { action, username: state.user, password: state.password, ...payload }; | |
| const response = await fetch(GWP, { method: 'POST', body: JSON.stringify(reqPayload) }); | |
| const res = await response.json(); | |
| if (res.status === 'error') throw new Error(res.message); | |
| return res.data; | |
| } | |
| async function login() { | |
| const u = document.getElementById('username').value; | |
| const p = document.getElementById('password').value; | |
| try { | |
| const data = await apiCall('login', { username: u, password: p }); | |
| state.user = u; state.password = p; state.role = data.role; | |
| document.getElementById('login-view').classList.add('d-none'); | |
| document.getElementById('app-view').classList.remove('d-none'); | |
| document.getElementById('nav-username').innerText = state.user; | |
| document.getElementById('drop-username').innerText = state.user; | |
| document.getElementById('drop-role').innerText = state.role; | |
| const btnAdd = document.getElementById('btn-add-new'); | |
| btnAdd.classList.remove('d-none'); | |
| if (state.role === 'Viewer') { | |
| btnAdd.disabled = true; | |
| } else { | |
| btnAdd.disabled = false; | |
| } | |
| await loadInitialData(); | |
| } catch (e) { | |
| document.getElementById('login-error').classList.remove('d-none'); | |
| } | |
| } | |
| function logout() { | |
| state.user = null; state.password = null; state.role = null; | |
| document.getElementById('app-view').classList.add('d-none'); | |
| document.getElementById('login-view').classList.remove('d-none'); | |
| document.getElementById('username').value = ''; | |
| document.getElementById('password').value = ''; | |
| } | |
| async function loadInitialData() { | |
| document.getElementById('tableBody').innerHTML = `<tr><td colspan="17" class="text-center py-4"><span class="spinner-border spinner-border-sm text-warning me-2"></span>Loading data...</td></tr>`; | |
| const result = await apiCall('getData'); | |
| state.options = result.options; | |
| state.data = result.data; | |
| populateDropdowns(); | |
| renderTable(); | |
| } | |
| function populateDropdowns() { | |
| const buildOptions = (arr, valKey, labelKey = null) => | |
| arr.map(i => `<option value="${labelKey ? i[labelKey] : i}">`).join(''); | |
| document.getElementById('dl_supplier').innerHTML = buildOptions(state.options.suppliers); | |
| document.getElementById('dl_item').innerHTML = state.options.items.map(i => `<option value="${i.display}">`).join(''); | |
| document.getElementById('f_subInv').innerHTML = `<option value="">Select...</option>` + | |
| state.options.subinv.map(i => `<option value="${i}">${i}</option>`).join(''); | |
| } | |
| function updateUOM() { | |
| const selectedItemDisplay = document.getElementById('f_item').value; | |
| const itemData = state.options.items.find(i => i.display === selectedItemDisplay); | |
| document.getElementById('f_uom').value = itemData ? itemData.uom : ''; | |
| checkFormValidity(); | |
| } | |
| function resetPageAndRender() { | |
| state.rowsPerPage = parseInt(document.getElementById('rowsPerPage').value); | |
| state.currentPage = 1; | |
| renderTable(); | |
| } | |
| function changePage(page) { | |
| state.currentPage = page; | |
| renderTable(); | |
| } | |
| function displayDate(dateStr) { | |
| if (!dateStr) return ''; | |
| return new Date(dateStr).toLocaleDateString('id-ID', { year: 'numeric', month: '2-digit', day: '2-digit' }); | |
| } | |
| function renderTable() { | |
| const search = document.getElementById('searchInput').value.toLowerCase(); | |
| const filtered = state.data.filter(r => Object.values(r).some(v => String(v).toLowerCase().includes(search))); | |
| const total = filtered.length; | |
| const totalPages = Math.ceil(total / state.rowsPerPage); | |
| if (state.currentPage > totalPages) state.currentPage = totalPages || 1; | |
| const startIdx = (state.currentPage - 1) * state.rowsPerPage; | |
| const paginated = filtered.slice(startIdx, startIdx + state.rowsPerPage); | |
| document.getElementById('totalRows').innerText = total; | |
| document.getElementById('pageStart').innerText = total === 0 ? 0 : startIdx + 1; | |
| document.getElementById('pageEnd').innerText = Math.min(startIdx + state.rowsPerPage, total); | |
| let pageHTML = `<li class="page-item ${state.currentPage === 1 ? 'disabled' : ''}"><button class="page-link" onclick="changePage(${state.currentPage - 1})">‹</button></li>`; | |
| let pStart = Math.max(1, state.currentPage - 2); | |
| let pEnd = Math.min(totalPages, state.currentPage + 2); | |
| for(let i = pStart; i <= pEnd; i++) { | |
| pageHTML += `<li class="page-item ${state.currentPage === i ? 'active' : ''}"><button class="page-link" onclick="changePage(${i})">${i}</button></li>`; | |
| } | |
| pageHTML += `<li class="page-item ${state.currentPage === totalPages || totalPages === 0 ? 'disabled' : ''}"><button class="page-link" onclick="changePage(${state.currentPage + 1})">›</button></li>`; | |
| document.getElementById('paginationNav').innerHTML = pageHTML; | |
| let headHTML = `<tr> | |
| <th>Nama Supplier</th><th>Tgl Kedatangan</th><th>No. SJ Supplier</th> | |
| <th>Item</th><th>Qty Awal</th><th>Qty Reject</th><th>Qty Nett</th> | |
| <th>UOM</th><th>Sub Inv</th><th>No. STG Manual</th><th>Ekspedisi PIC</th> | |
| <th>Nomer PO</th><th>Tanggal PO</th><th>No. Receive</th><th>Notes</th> | |
| <th>Status</th><th class="text-center sticky-right">Action</th> | |
| </tr>`; | |
| document.getElementById('tableHead').innerHTML = headHTML; | |
| if (paginated.length === 0) { | |
| document.getElementById('tableBody').innerHTML = `<tr><td colspan="17" class="text-center py-4 text-muted">No data found</td></tr>`; | |
| return; | |
| } | |
| let bodyHTML = ''; | |
| paginated.forEach(row => { | |
| let statColor = row.stat === 'OK' ? 'bg-success' : (row.stat === 'EDITED' ? 'bg-primary' : 'bg-secondary'); | |
| // Disable edit button if role is Viewer OR if status is OK | |
| let isViewer = state.role === 'Viewer'; | |
| let btnDisabled = (isViewer || row.stat === 'OK') ? 'disabled' : ''; | |
| let btnClass = btnDisabled ? 'btn-secondary opacity-50' : 'btn-warning'; | |
| bodyHTML += `<tr> | |
| <td>${row.supplier}</td> | |
| <td>${displayDate(row.tglDatang)}</td> | |
| <td>${row.noSj}</td> | |
| <td>${row.item}</td> | |
| <td>${row.qtyAwal}</td> | |
| <td>${row.qtyReject}</td> | |
| <td>${row.qtyNett}</td> | |
| <td>${row.uom}</td> | |
| <td>${row.subInv}</td> | |
| <td>${row.stgManual}</td> | |
| <td>${displayDate(row.ekspedisi)}</td> | |
| <td>${row.noPo}</td> | |
| <td>${displayDate(row.tglPo)}</td> | |
| <td>${row.noReceive}</td> | |
| <td>${row.notes}</td> | |
| <td><span class="badge ${statColor}">${row.stat}</span></td> | |
| <td class="text-center sticky-right"> | |
| <button class="btn ${btnClass} btn-sm text-white py-0 px-2" onclick="openEditModal(${row._rowIndex})" title="Edit" ${btnDisabled}> | |
| <i class="bi bi-pencil-square"></i> | |
| </button> | |
| </td> | |
| </tr>`; | |
| }); | |
| document.getElementById('tableBody').innerHTML = bodyHTML; | |
| } | |
| function openAddModal() { | |
| if (state.role === 'Viewer') return; | |
| state.currentMode = 'ADD'; | |
| document.getElementById('recordForm').reset(); | |
| document.getElementById('modalTitle').innerText = "Kedatangan Baru"; | |
| document.getElementById('section-edit').classList.add('d-none'); | |
| document.getElementById('divider-edit').classList.add('d-none'); | |
| lockFields(false); | |
| document.getElementById('btnActionSave').innerText = "Save"; | |
| document.getElementById('btnActionSave').disabled = true; | |
| new coreui.Modal(document.getElementById('dataModal')).show(); | |
| } | |
| function openEditModal(rowIndex) { | |
| if (state.role === 'Viewer') return; | |
| state.currentMode = 'EDIT'; | |
| state.currentRowIndex = rowIndex; | |
| const rowData = state.data.find(r => r._rowIndex === rowIndex); | |
| document.getElementById('modalTitle').innerText = "Edit Row"; | |
| document.getElementById('section-edit').classList.remove('d-none'); | |
| document.getElementById('divider-edit').classList.remove('d-none'); | |
| document.getElementById('f_supplier').value = rowData.supplier; | |
| document.getElementById('f_tglDatang').value = formatDateForInput(rowData.tglDatang); | |
| document.getElementById('f_noSj').value = rowData.noSj; | |
| document.getElementById('f_item').value = rowData.item; | |
| document.getElementById('f_qtyAwal').value = rowData.qtyAwal; | |
| document.getElementById('f_qtyReject').value = rowData.qtyReject; | |
| document.getElementById('f_qtyNett').value = rowData.qtyNett; | |
| document.getElementById('f_uom').value = rowData.uom; | |
| document.getElementById('f_subInv').value = rowData.subInv; | |
| document.getElementById('f_stgManual').value = rowData.stgManual; | |
| document.getElementById('f_ekspedisi').value = formatDateForInput(rowData.ekspedisi); | |
| document.getElementById('f_notes').value = rowData.notes; | |
| const cleanPo = rowData.noPo ? String(rowData.noPo).replace('PO/SKL/', '') : ''; | |
| document.getElementById('f_noPo').value = cleanPo; | |
| document.getElementById('f_tglPo').value = formatDateForInput(rowData.tglPo); | |
| document.getElementById('f_noReceive').value = rowData.noReceive; | |
| lockFields(true); | |
| document.getElementById('btnActionSave').innerText = "Update"; | |
| document.getElementById('btnActionSave').disabled = true; | |
| new coreui.Modal(document.getElementById('dataModal')).show(); | |
| } | |
| function formatDateForInput(dateString) { | |
| if (!dateString) return ''; | |
| const d = new Date(dateString); | |
| return d.toISOString().split('T')[0]; | |
| } | |
| function lockFields(isEdit) { | |
| const toLock = ['f_supplier', 'f_tglDatang', 'f_noSj', 'f_item']; | |
| toLock.forEach(id => document.getElementById(id).disabled = isEdit); | |
| } | |
| function checkFormValidity() { | |
| let isValid = true; | |
| if (state.currentMode === 'ADD') { | |
| document.querySelectorAll('.req-add').forEach(el => { | |
| if (!el.value.trim()) isValid = false; | |
| }); | |
| } else { | |
| document.querySelectorAll('.req-edit').forEach(el => { | |
| if (!el.value.trim()) isValid = false; | |
| }); | |
| const noPoVal = document.getElementById('f_noPo').value.trim(); | |
| const tglPoVal = document.getElementById('f_tglPo').value.trim(); | |
| if ((noPoVal && !tglPoVal) || (!noPoVal && tglPoVal)) { | |
| isValid = false; | |
| } | |
| } | |
| document.getElementById('btnActionSave').disabled = !isValid; | |
| } | |
| async function submitData() { | |
| const btn = document.getElementById('btnActionSave'); | |
| btn.disabled = true; | |
| btn.innerHTML = `<span class="spinner-border spinner-border-sm"></span> Processing...`; | |
| const payload = { | |
| qtyAwal: document.getElementById('f_qtyAwal').value, | |
| qtyReject: document.getElementById('f_qtyReject').value, | |
| qtyNett: document.getElementById('f_qtyNett').value, | |
| subInv: document.getElementById('f_subInv').value, | |
| stgManual: document.getElementById('f_stgManual').value.toUpperCase(), | |
| ekspedisi: document.getElementById('f_ekspedisi').value, | |
| notes: document.getElementById('f_notes').value | |
| }; | |
| if (state.currentMode === 'ADD') { | |
| payload.supplier = document.getElementById('f_supplier').value; | |
| payload.tglDatang = document.getElementById('f_tglDatang').value; | |
| payload.noSj = document.getElementById('f_noSj').value; | |
| payload.item = document.getElementById('f_item').value; | |
| payload.uom = document.getElementById('f_uom').value; | |
| await apiCall('saveRow', { rowData: payload }); | |
| } else { | |
| payload.noPo = document.getElementById('f_noPo').value; | |
| payload.tglPo = document.getElementById('f_tglPo').value; | |
| payload.noReceive = document.getElementById('f_noReceive').value; | |
| await apiCall('updateRow', { rowData: payload, rowIndex: state.currentRowIndex }); | |
| } | |
| closeModal('dataModal'); | |
| await loadInitialData(); | |
| } | |
| function closeModal(id) { | |
| const modal = coreui.Modal.getInstance(document.getElementById(id)); | |
| if (modal) modal.hide(); | |
| } |