diff --git a/coretabs.user.js b/coretabs.user.js index 0504748..299740c 100644 --- a/coretabs.user.js +++ b/coretabs.user.js @@ -1,11 +1,12 @@ // ==UserScript== // @name CoreTabs -// @namespace https://git.diasbaskara.id -// @version 0.1 +// @namespace https://git.diasbaskara.id/diasbaskara/userscripts/ +// @version 0.2 // @description Manage your cases easily. // @author Dias Baskara // @match https://coretax.intranet.pajak.go.id/* // @grant GM_addStyle +// @require https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js // @run-at document-idle // ==/UserScript== @@ -15,22 +16,44 @@ // --- SCRIPT CONFIGURATION --- const AUTH_STORAGE_KEY = 'cats-angular-clientuser:https://coretax.intranet.pajak.go.id/identityprovider:cats-angular-client'; const DEFAULT_CASES_FILTER = 'In Progress'; + const REFUND_CASE_TYPE_NAME = 'Pengembalian Melalui Pelaporan Surat Pemberitahuan (SPT)'; // ---------------------------- // --- State Management --- - let allMyCases = [], allCaseDocuments = [], allCaseUsers = []; + let allMyCases = [], allCaseDocuments = [], allCaseUsers = [], refundReviewData = []; + let filteredRefundData = []; let selectedCaseId = null; let loadedDocsForCaseId = null; let loadedUsersForCaseId = null; function addStyles() { GM_addStyle(` - #ct-sidebar{position:fixed;top:100px;right:-850px;width:850px;height:80vh;max-height:800px;background-color:#f9f9f9;border:1px solid #ccc;border-radius:8px 0 0 8px;box-shadow:-3px 0 8px rgba(0,0,0,.15);z-index:9999;transition:right .4s ease-in-out;display:flex;flex-direction:column;font-family:sans-serif}#ct-sidebar.open{right:0}#ct-sidebar-toggle{position:absolute;top:50%;right:850px;transform:translateY(-50%);width:30px;height:80px;background-color:#0056b3;color:#fff;border:none;border-radius:8px 0 0 8px;cursor:pointer;writing-mode:vertical-rl;text-orientation:mixed;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:700;letter-spacing:1px}#ct-tab-bar{display:flex;background-color:#e9ecef;border-bottom:1px solid #ccc;flex-shrink:0}.ct-tab-button{padding:10px 15px;border:none;background-color:transparent;cursor:pointer;font-size:14px;border-bottom:3px solid transparent;transition:background-color .2s,border-color .2s}.ct-tab-button:hover{background-color:#dcdcdc}.ct-tab-button.active{border-bottom:3px solid #0056b3;font-weight:700;background-color:#fff}#ct-tab-content-area{padding:15px;flex-grow:1;overflow:hidden;display:flex;flex-direction:column}.ct-tab-panel{display:none;flex-grow:1;overflow:hidden;flex-direction:column}.ct-tab-panel.active{display:flex}.filter-container{margin-bottom:10px;flex-shrink:0}.filter-container label{font-weight:700;margin-right:5px}.filter-container select{padding:5px;border-radius:4px;border:1px solid #ccc}.results-container{flex-grow:1;overflow-y:auto;border:1px solid #ddd}.ct-results-table{width:100%;border-collapse:collapse;font-size:12px}.ct-results-table td,.ct-results-table th{border:1px solid #ddd;padding:8px;text-align:left}.ct-results-table th{background-color:#f2f2f2;font-weight:700;position:sticky;top:-1px} + #ct-sidebar{position:fixed;top:100px;right:-950px;width:950px;height:80vh;max-height:800px;background-color:#f9f9f9;border:1px solid #ccc;border-radius:8px 0 0 8px;box-shadow:-3px 0 8px rgba(0,0,0,.15);z-index:9999;transition:right .4s ease-in-out;display:flex;flex-direction:column;font-family:sans-serif}#ct-sidebar.open{right:0}#ct-sidebar-toggle{position:absolute;top:50%;right:950px;transform:translateY(-50%);width:30px;height:80px;background-color:#0056b3;color:#fff;border:none;border-radius:8px 0 0 8px;cursor:pointer;writing-mode:vertical-rl;text-orientation:mixed;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:700;letter-spacing:1px} + #ct-header-area { padding: 12px 15px; background-color: #343a40; color: white; flex-shrink: 0; display: flex; align-items: center; gap: 15px; border-bottom: 1px solid #495057; } + #ct-header-icon { flex-shrink: 0; } + #ct-header-icon svg { width: 32px; height: 32px; fill: #e9ecef; } + #ct-header-text { flex-grow: 1; min-width: 0; } + #ct-header-title { font-size: 16px; font-weight: bold; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } + #ct-header-subtitle { font-size: 12px; color: #adb5bd; } + #ct-tab-bar{display:flex;background-color:#e9ecef;border-bottom:1px solid #ccc;flex-shrink:0}.ct-tab-button{padding:10px 15px;border:none;background-color:transparent;cursor:pointer;font-size:14px;border-bottom:3px solid transparent;transition:background-color .2s,border-color .2s}.ct-tab-button:hover{background-color:#dcdcdc}.ct-tab-button.active{border-bottom:3px solid #0056b3;font-weight:700;background-color:#fff}#ct-tab-content-area{padding:15px;flex-grow:1;overflow:hidden;display:flex;flex-direction:column}.ct-tab-panel{display:none;flex-grow:1;overflow:hidden;flex-direction:column}.ct-tab-panel.active{display:flex} + .filter-container{margin-bottom:10px;flex-shrink:0;display:flex;gap:15px;align-items:center;} + .filter-container label{font-weight:700;margin-right:5px}.filter-container select{padding:5px;border-radius:4px;border:1px solid #ccc} + .results-container{flex-grow:1;overflow-y:auto;overflow-x:auto;border:1px solid #ddd} + .ct-results-table{width:100%;border-collapse:collapse;font-size:12px}.ct-results-table td,.ct-results-table th{border:1px solid #ddd;padding:8px;text-align:left;vertical-align:middle}.ct-results-table th{background-color:#f2f2f2;font-weight:700;position:sticky;top:-1px} .ct-results-table tbody tr:not(.group-header) { cursor: pointer; } .ct-results-table tbody tr:not(.group-header):hover{background-color:#e9ecef}.ct-results-table tr.selected{background-color:#dbeafe!important;font-weight:700}.group-header td{background-color:#343a40;color:#fff;font-weight:700;padding:6px 8px} - .actions-cell{text-align:center!important;white-space:nowrap}.action-btn{display:inline-block;padding:4px 8px;margin:0 2px;border-radius:4px;text-decoration:none;cursor:pointer;border:1px solid #ccc;font-size:11px}.action-btn:disabled{background-color:#e9ecef;color:#6c757d;cursor:not-allowed;border-color:#ddd}.action-btn.open-case{background-color:#6c757d;color:#fff}.action-btn.view-docs{background-color:#007bff;color:#fff;border-color:#007bff}.action-btn.view-users{background-color:#17a2b8;color:#fff;border-color:#17a2b8} + .actions-cell{text-align:left!important;white-space:nowrap}.action-btn{display:inline-block;padding:4px 8px;margin:0 2px;border-radius:4px;text-decoration:none;cursor:pointer;border:1px solid #ccc;font-size:11px}.action-btn:disabled{background-color:#e9ecef;color:#6c757d;cursor:not-allowed;border-color:#ddd}.action-btn.open-case{background-color:#6c757d;color:#fff}.action-btn.view-docs{background-color:#007bff;color:#fff;border-color:#007bff}.action-btn.view-users{background-color:#17a2b8;color:#fff;border-color:#17a2b8} .action-btn.download-doc { background-color: #28a745; color: white; border-color: #28a745; } + .action-btn.review-refund-case { background-color: #6f42c1; color: white; border-color: #6f42c1; } .refresh-btn{margin-top:10px;padding:8px 12px;font-weight:700;cursor:pointer;border:1px solid #007bff;background-color:#007bff;color:#fff;border-radius:4px;transition:background-color .2s}.refresh-btn:hover{background-color:#0056b3} + .group-header .group-title { font-weight: bold; font-size: 14px; color: white; } + .group-header .group-subtitle { font-size: 11px; color: #ccc; margin-top: 2px; font-weight: normal; } + .reported-cell { text-align: center !important; font-size: 16px; } + .toggle-icon { display: inline-block; width: 1em; } + .group-header.expanded .toggle-icon::before { content: '▼'; } + .group-header.collapsed .toggle-icon::before { content: '►'; } + .currency-wrapper { display: flex; justify-content: space-between; } + .currency-num { font-variant-numeric: tabular-nums; } `); } @@ -39,15 +62,33 @@ sidebarContainer.innerHTML = `
`; @@ -55,169 +96,56 @@ } // --- RENDER FUNCTIONS --- - function renderMyCasesTable() { - const responseArea = document.querySelector('#tab-my-cases .results-container'); - const filterValue = document.getElementById('cases-status-filter').value; - const filteredCases = filterValue === 'all' ? allMyCases : allMyCases.filter(c => c.CaseStatus === filterValue); - if (filteredCases.length === 0) { - responseArea.innerHTML = `No cases match the selected filter.
`; - return; - } - filteredCases.sort((a, b) => { - const typeCompare = (a.CaseTypeName || '').localeCompare(b.CaseTypeName || ''); - if (typeCompare !== 0) return typeCompare; - return (b.CaseNumber || '').localeCompare(a.CaseNumber || '', undefined, { numeric: true }); - }); - const table = createTable(['Case Number', 'Taxpayer Name', 'Case Type', 'Status', 'Created Date', 'Actions']); - const tbody = document.createElement('tbody'); - let currentGroup = ''; - filteredCases.forEach(caseItem => { - if (caseItem.CaseTypeName !== currentGroup) { - currentGroup = caseItem.CaseTypeName; - tbody.innerHTML += `No cases match the selected filter.
');filteredCases.sort((e,t)=>{const o=(e.CaseTypeName||"").localeCompare(t.CaseTypeName||"");return 0!==o?o:(t.CaseNumber||"").localeCompare(e.CaseNumber||"",void 0,{numeric:!0})});const table=createTable(["Case Number","Taxpayer Name","Case Type","Status","Created Date","Actions"]),tbody=document.createElement("tbody");let currentGroup="";let groupIndex=0;filteredCases.forEach(e=>{if(e.CaseTypeName!==currentGroup){currentGroup=e.CaseTypeName;groupIndex++;tbody.innerHTML+=`No documents found or match the selected filter.
`; - return; - } - filteredDocs.sort((a, b) => (a.DocumentTypeCode || '').localeCompare(b.DocumentTypeCode)); - - // --- CHANGE 1 of 3: Updated the table headers --- - const table = createTable(['Letter Number', 'File Name', 'Status', 'Date', 'Actions']); - const tbody = document.createElement('tbody'); - let currentGroup = ''; - filteredDocs.forEach(doc => { - if (doc.DocumentTypeCode !== currentGroup) { - currentGroup = doc.DocumentTypeCode; - // --- CHANGE 2 of 3: Updated the colspan for the group header --- - tbody.innerHTML += `No documents found or match the selected filter.
');filteredDocs.sort((e,t)=>(e.DocumentTypeCode||"").localeCompare(t.DocumentTypeCode||""));const table=createTable(["Letter Number","File Name","Status","Date","Actions"]),tbody=document.createElement("tbody");let currentGroup="";let groupIndex=0;filteredDocs.forEach(e=>{if(e.DocumentTypeCode!==currentGroup){currentGroup=e.DocumentTypeCode;groupIndex++;tbody.innerHTML+=`No users found or match the selected filter.
`; - return; - } - filteredUsers.sort((a, b) => (a.FullName || '').localeCompare(b.FullName || '')); - const table = createTable(['Full Name', 'NIP', 'Position', 'Office', 'Case Role']); - const tbody = document.createElement('tbody'); - filteredUsers.forEach(user => { - tbody.innerHTML += ` + `}),table.appendChild(tbody),responseArea.innerHTML="",responseArea.appendChild(table),tbody.addEventListener("click",handleGroupToggle),tbody.addEventListener("click",handleDocumentAction)} + function renderCaseUsersTable(){const responseArea=document.querySelector("#tab-users .results-container"),filterValue=document.getElementById("users-role-filter").value,filteredUsers="all"===filterValue?allCaseUsers:allCaseUsers.filter(e=>e.CaseRoleType===filterValue);if(0===filteredUsers.length)return void(responseArea.innerHTML='No users found or match the selected filter.
');filteredUsers.sort((e,t)=>(e.FullName||"").localeCompare(t.FullName||""));const table=createTable(["Full Name","NIP","Position","Office","Case Role"]),tbody=document.createElement("tbody");filteredUsers.forEach(e=>{tbody.innerHTML+=`No refund review data matches the filter.
');dataToRender.sort((e,t)=>{const o=(e.Tin||"")+(e.Name||""),a=(t.Tin||"")+(t.Name||"");return o.localeCompare(a)});const table=createTable(["Doc Number","Date","Selling Price","VAT Paid","STLG Paid","Trans Code","Reported"]),tbody=document.createElement("tbody");let currentGroupKey="";let groupIndex=0;responseArea.innerHTML="",table.appendChild(tbody),responseArea.appendChild(table),dataToRender.forEach(e=>{const t=(e.Tin||"")+(e.Name||"");if(t!==currentGroupKey){currentGroupKey=t,groupIndex++;const o=tbody.insertRow();o.className="group-header expanded",o.dataset.groupId=`refund-group-${groupIndex}`;const a=o.insertCell();a.colSpan=7,a.innerHTML=` + + +Loading documents...
';try{if(!caseId)throw new Error("No Case ID provided.");const authToken=getAuthToken(),apiUrl="https://coretax.intranet.pajak.go.id/casemanagement/api/casedocument/list",fetchOptions={method:"POST",headers:getHeaders(caseId),body:JSON.stringify({AggregateIdentifier:caseId})},response=await fetch(apiUrl,fetchOptions);if(!response.ok){const errorData=await response.json();throw new Error(`API Error: ${errorData.Message||response.statusText}`)}const data=await response.json();allCaseDocuments=data?.Payload?.Data||[],loadedDocsForCaseId=caseId,populateFilter("docs-status-filter",allCaseDocuments,"DocumentStatus"),renderCaseDocumentsTable()}catch(error){handleError(error,responseArea)}} - async function fetchCaseUsers(caseId){const responseArea=document.querySelector("#tab-users .results-container");responseArea.innerHTML='Loading users...
';try{if(!caseId)throw new Error("No Case ID provided.");const authToken=getAuthToken(),apiUrl="https://coretax.intranet.pajak.go.id/casemanagement/api/caseuser/list",payload={AggregateIdentifier:caseId,First:0,Rows:200,SortField:"",SortOrder:1,Filters:[],LanguageId:"id-ID"},fetchOptions={method:"POST",headers:getHeaders(caseId),body:JSON.stringify(payload)},response=await fetch(apiUrl,fetchOptions);if(!response.ok){const errorData=await response.json();throw new Error(`API Error: ${errorData.Message||response.statusText}`)}const data=await response.json();allCaseUsers=data?.Payload?.Data||[],loadedUsersForCaseId=caseId,populateFilter("users-role-filter",allCaseUsers,"CaseRoleType"),renderCaseUsersTable()}catch(error){handleError(error,responseArea)}} - async function downloadDocument(docId, filename, button) { - const originalText = button.textContent; - button.textContent = 'Downloading...'; - button.disabled = true; - try { - const authToken = getAuthToken(); - const apiUrl = 'https://coretax.intranet.pajak.go.id/documentmanagement/api/download'; - const payload = { DocumentAggregateIdentifier: docId, IsDocumentCases: false, IsNeedWatermark: null }; - const fetchOptions = { - method: 'POST', - headers: getHeaders(), // Using generic headers should be fine - body: JSON.stringify(payload) - }; - const response = await fetch(apiUrl, fetchOptions); - if (!response.ok) { - try { - const errorData = await response.json(); - throw new Error(`API Error: ${errorData.Message || response.statusText}`); - } catch (e) { - throw new Error(`API request failed! Status: ${response.statusText}`); - } - } - const blob = await response.blob(); - const url = window.URL.createObjectURL(blob); - const a = document.createElement('a'); - a.style.display = 'none'; - a.href = url; - a.download = filename || 'download.pdf'; - document.body.appendChild(a); - a.click(); - window.URL.revokeObjectURL(url); - a.remove(); - } catch (error) { - alert(`Download failed: ${error.message}`); - } finally { - button.textContent = originalText; - button.disabled = false; - } - } - + async function fetchCaseDocuments(caseId){const responseArea=document.querySelector("#tab-docs .results-container");responseArea.innerHTML='Loading documents...
';try{if(!caseId)throw new Error("No Case ID provided.");const apiUrl="https://coretax.intranet.pajak.go.id/casemanagement/api/casedocument/list",fetchOptions={method:"POST",headers:getHeaders(caseId),body:JSON.stringify({AggregateIdentifier:caseId})},response=await fetch(apiUrl,fetchOptions);if(!response.ok){const errorData=await response.json();throw new Error(`API Error: ${errorData.Message||response.statusText}`)}const data=await response.json();allCaseDocuments=data?.Payload?.Data||[],loadedDocsForCaseId=caseId,populateFilter("docs-status-filter",allCaseDocuments,"DocumentStatus"),renderCaseDocumentsTable()}catch(error){handleError(error,responseArea)}} + async function fetchCaseUsers(caseId){const responseArea=document.querySelector("#tab-users .results-container");responseArea.innerHTML='Loading users...
';try{if(!caseId)throw new Error("No Case ID provided.");const apiUrl="https://coretax.intranet.pajak.go.id/casemanagement/api/caseuser/list",payload={AggregateIdentifier:caseId,First:0,Rows:200,SortField:"",SortOrder:1,Filters:[],LanguageId:"id-ID"},fetchOptions={method:"POST",headers:getHeaders(caseId),body:JSON.stringify(payload)},response=await fetch(apiUrl,fetchOptions);if(!response.ok){const errorData=await response.json();throw new Error(`API Error: ${errorData.Message||response.statusText}`)}const data=await response.json();allCaseUsers=data?.Payload?.Data||[],loadedUsersForCaseId=caseId,populateFilter("users-role-filter",allCaseUsers,"CaseRoleType"),renderCaseUsersTable()}catch(error){handleError(error,responseArea)}} + async function downloadDocument(docId, filename, button) {const originalText=button.textContent;button.textContent="Downloading...",button.disabled=!0;try{const apiUrl="https://coretax.intranet.pajak.go.id/documentmanagement/api/download",payload={DocumentAggregateIdentifier:docId,IsDocumentCases:!1,IsNeedWatermark:null},fetchOptions={method:"POST",headers:getHeaders(),body:JSON.stringify(payload)},response=await fetch(apiUrl,fetchOptions);if(!response.ok)try{const errorData=await response.json();throw new Error(`API Error: ${errorData.Message||response.statusText}`)}catch(e){throw new Error(`API request failed! Status: ${response.statusText}`)}const blob=await response.blob(),url=window.URL.createObjectURL(blob),a=document.createElement("a");a.style.display="none",a.href=url,a.download=filename||"download.pdf",document.body.appendChild(a),a.click(),window.URL.revokeObjectURL(url),a.remove()}catch(error){alert(`Download failed: ${error.message}`)}finally{button.textContent=originalText,button.disabled=!1}} + async function fetchSubProcessId(caseId) {const apiUrl="https://coretax.intranet.pajak.go.id/casemanagement/api/caserouting/view",payload={AggregateIdentifier:caseId,LanguageId:"id-ID"},fetchOptions={method:"POST",headers:getHeaders(caseId),body:JSON.stringify(payload)},response=await fetch(apiUrl,fetchOptions);if(!response.ok){const errorData=await response.json();throw new Error(`Sub Process ID API Error: ${errorData.Message||response.statusText}`)}const data=await response.json(),firstResult=data?.Payload?.[0];if(!firstResult||!firstResult.SubProcessIdentifier)throw new Error("Could not find a 'SubProcessIdentifier' in the caserouting/view response.");return firstResult.SubProcessIdentifier} + async function fetchC02FormDetail(caseId, subProcessId) {const apiUrl="https://coretax.intranet.pajak.go.id/casecomponentspayment/api/c02form014/c02form009detail/current",payload={caseAggregateIdentifier:caseId,CaseSubProcessIdentifier:subProcessId},fetchOptions={method:"POST",headers:getHeaders(caseId),body:JSON.stringify(payload)},response=await fetch(apiUrl,fetchOptions);if(!response.ok){const errorData=await response.json();throw new Error(`C02Form Detail API Error: ${errorData.Message||response.statusText}`)}const data=await response.json(),firstResult=data?.Payload?.[0];if(!firstResult||!firstResult.Reference)throw new Error("Could not find a 'Reference' number in the C02Form Detail response.");return firstResult.Reference} + async function fetchRefundReview(caseId, refNumber) {const responseArea=document.querySelector("#tab-refund .results-container");try{const apiUrl="https://coretax.intranet.pajak.go.id/casecomponentspayment/api/refundprocessreview/get-detailed-review",payload={CaseAggregateIdentifier:caseId,RevenueCode:"411211",TaxPaymentCode:"100",TaxReturnType:"VAT_VATR",ReferenceNumber:refNumber},fetchOptions={method:"POST",headers:getHeaders(caseId),body:JSON.stringify(payload)},response=await fetch(apiUrl,fetchOptions);if(!response.ok){const errorData=await response.json();throw new Error(`Refund Review API Error: ${errorData.Message||response.statusText}`)}const data=await response.json();refundReviewData=data?.Payload||[],populateBooleanFilter("refund-reported-filter"),renderRefundReviewTable()}catch(error){handleError(error,responseArea)}} + // --- HELPER FUNCTIONS --- - function getHeaders(caseId = null) { const authToken = getAuthToken(), requestFromUrl = caseId ? `https://coretax.intranet.pajak.go.id/case-management/id-ID/case-overview/${caseId}` : window.location.href; return { 'accept': 'application/json, text/plain, */*', 'authorization': `Bearer ${authToken}`, 'content-type': 'application/json', 'languageid': 'id-ID', 'request_from': requestFromUrl, }; } + function getHeaders(caseId=null){const authToken=getAuthToken(),requestFromUrl=caseId?`https://coretax.intranet.pajak.go.id/case-management/id-ID/case-overview/${caseId}`:window.location.href;return{accept:"application/json, text/plain, */*",authorization:`Bearer ${authToken}`,"content-type":"application/json",languageid:"id-ID",request_from:requestFromUrl}} function populateFilter(selectId,data,key){const select=document.getElementById(selectId);if(!select)return;const values=[...new Set(data.map(e=>e[key]).filter(Boolean))];select.innerHTML='',values.sort().forEach(e=>{select.innerHTML+=``})} + function populateBooleanFilter(selectId){const e=document.getElementById(selectId);e&&(e.innerHTML='')} function getAuthToken(){const userDataString=localStorage.getItem(AUTH_STORAGE_KEY),userData=userDataString?JSON.parse(userDataString):null,authToken=userData?.access_token;if(!authToken)throw new Error("Authorization Token not found.");return authToken} function createTable(headers){const table=document.createElement("table");table.className="ct-results-table";const thead=document.createElement("thead");return thead.innerHTML=`Please refresh the page to log in again.
`:errorHtml=`An error occurred:
${error.message}
Step 1/3: Fetching Sub Process ID...
`; + if (!caseId) throw new Error("A case must be selected."); + const subProcessId = await fetchSubProcessId(caseId); + responseArea.innerHTML = `Step 2/3: Fetching reference number...
`; + const referenceNumber = await fetchC02FormDetail(caseId, subProcessId); + responseArea.innerHTML = `Step 3/3: Fetching refund details...
`; + await fetchRefundReview(caseId, referenceNumber); + } catch(error) { + handleError(error, responseArea); + } finally { + button.textContent = originalText; + button.disabled = false; } } + function handleCaseAction(event){ + const selectedRow=event.target.closest("tr"); + if(!selectedRow||selectedRow.classList.contains("group-header"))return; + const caseId=selectedRow.dataset.id; + if(!caseId)return; + const selectedCase=allMyCases.find(e=>e.AggregateIdentifier===caseId); + updateHeader(selectedCase); + // Only reset dependent data if the selection actually changes + if (selectedCaseId !== caseId) { + selectedCaseId=caseId; + loadedDocsForCaseId=null; + loadedUsersForCaseId=null; + // --- FIX IS HERE --- + refundReviewData = []; + filteredRefundData = []; + document.querySelector('#tab-refund .results-container').innerHTML = `Please use the 'Review Refund' action on a relevant case.
`; + document.getElementById('refund-download-btn').disabled = true; + } + const allRows=selectedRow.closest("tbody").querySelectorAll("tr"); + allRows.forEach(e=>e.classList.remove("selected")); + selectedRow.classList.add("selected"); + const actionButton=event.target.closest(".action-btn"); + actionButton&&(actionButton.matches(".view-docs")?switchTab("tab-docs"):actionButton.matches(".view-users")?switchTab("tab-users"):actionButton.matches(".review-refund-case")&&startRefundReviewProcess(caseId,actionButton)); + } function handleDocumentAction(event) { - const downloadButton = event.target.closest('.download-doc'); - if (downloadButton) { - const docId = downloadButton.dataset.docId; - const filename = downloadButton.dataset.filename; - downloadDocument(docId, filename, downloadButton); + const target = event.target.closest('.action-btn'); + if (!target) return; + if (target.matches('.download-doc')) { + const docId = target.dataset.docId; + const filename = target.dataset.filename; + downloadDocument(docId, filename, target); } } - - function switchTab(tabId) { - document.querySelectorAll('.ct-tab-button').forEach(btn => btn.classList.remove('active')); - document.querySelectorAll('.ct-tab-panel').forEach(panel => panel.classList.remove('active')); - document.querySelector(`[data-tab="${tabId}"]`).classList.add('active'); - document.getElementById(tabId).classList.add('active'); - if (tabId === 'tab-docs') { - if (selectedCaseId && selectedCaseId !== loadedDocsForCaseId) { - fetchCaseDocuments(selectedCaseId); - } - } else if (tabId === 'tab-users') { - if (selectedCaseId && selectedCaseId !== loadedUsersForCaseId) { - fetchCaseUsers(selectedCaseId); + function handleGroupToggle(event) { + const headerRow = event.target.closest('.group-header'); + if (!headerRow) return; + const groupId = headerRow.dataset.groupId; + if (!groupId) return; + headerRow.classList.toggle('collapsed'); + headerRow.classList.toggle('expanded'); + const isCollapsed = headerRow.classList.contains('collapsed'); + const tableBody = headerRow.parentElement; + const memberRows = tableBody.querySelectorAll(`.group-member.${groupId}`); + memberRows.forEach(row => { row.style.display = isCollapsed ? 'none' : 'table-row'; }); + } + function switchTab(tabId){document.querySelectorAll(".ct-tab-button").forEach(e=>e.classList.remove("active")),document.querySelectorAll(".ct-tab-panel").forEach(e=>e.classList.remove("active")),document.querySelector(`[data-tab="${tabId}"]`).classList.add("active"),document.getElementById(tabId).classList.add("active"),"tab-docs"===tabId?selectedCaseId&&selectedCaseId!==loadedDocsForCaseId&&fetchCaseDocuments(selectedCaseId):"tab-users"===tabId&&selectedCaseId&&selectedCaseId!==loadedUsersForCaseId&&fetchCaseUsers(selectedCaseId)} + function downloadRefundExcel() { + if (!filteredRefundData || filteredRefundData.length === 0) { + alert("No data to download. Please filter the data first."); + return; + } + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const hours = String(now.getHours()).padStart(2, '0'); + const minutes = String(now.getMinutes()).padStart(2, '0'); + const datetimeSuffix = `${year}${month}${day}_${hours}${minutes}`; + let filenamePrefix = 'Refund_Review_Data'; + if (selectedCaseId) { + const selectedCase = allMyCases.find(c => c.AggregateIdentifier === selectedCaseId); + if (selectedCase && selectedCase.CaseNumber) { + const sanitizedCaseNumber = selectedCase.CaseNumber.replace(/[\\/:"*?<>|]/g, '_'); + filenamePrefix = `Refund_Review_Data_${sanitizedCaseNumber}`; } } + const filename = `${filenamePrefix}_${datetimeSuffix}.xlsx`; + const headerMapping = [ + { apiField: "ReportedBySeller", excelHeader: "Telah dilaporkan oleh Penjual?" }, { apiField: "Tin", excelHeader: "NPWP Penjual" }, { apiField: "Name", excelHeader: "Nama Penjual" }, { apiField: "DocumentNumber", excelHeader: "Nomor Faktur Pajak/Dokumen yang Dipersamakan/Nota Retur/Nota Pembatalan" }, { apiField: "DocumentDate", excelHeader: "Tanggal Faktur Pajak/Dokumen yang Dipersamakan/Nota Retur/Nota Pembatalan" }, { apiField: "TransactionCode", excelHeader: "Kode Transaksi" }, { apiField: "SellingPrice", excelHeader: "Harga Jual/Dasar Pengenaan Pajak/Dasar Pengenaan Pajak Lainnya (Rp)" }, { apiField: "VatPaid", excelHeader: "PPN yang dikreditkan pada SPT yang Dilaporkan" }, { apiField: "StlgPaid", excelHeader: "PPnBM yang dikreditkan pada SPT yang Dilaporkan" } + ]; + const excelData = filteredRefundData.map(item => { + const row = {}; + headerMapping.forEach(map => { + if (map.apiField === 'ReportedBySeller') { + row[map.excelHeader] = item[map.apiField] ? 'Yes' : 'No'; + } else { + row[map.excelHeader] = item[map.apiField] === null ? '' : item[map.apiField]; + } + }); + return row; + }); + const worksheet = XLSX.utils.json_to_sheet(excelData); + const workbook = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(workbook, worksheet, "Refund Review"); + XLSX.writeFile(workbook, filename); + } + + function toggleAllGroups(containerSelector, button) { + const isCollapsing = button.textContent === 'Collapse All'; + const headers = document.querySelectorAll(`${containerSelector} .group-header`); + const members = document.querySelectorAll(`${containerSelector} .group-member`); + + headers.forEach(h => { + h.classList.toggle('expanded', !isCollapsing); + h.classList.toggle('collapsed', isCollapsing); + }); + members.forEach(m => { + m.style.display = isCollapsing ? 'none' : 'table-row'; + }); + + button.textContent = isCollapsing ? 'Expand All' : 'Collapse All'; } // --- MAIN INITIALIZATION --- @@ -283,6 +293,13 @@ document.getElementById('cases-status-filter').addEventListener('change', renderMyCasesTable); document.getElementById('docs-status-filter').addEventListener('change', renderCaseDocumentsTable); document.getElementById('users-role-filter').addEventListener('change', renderCaseUsersTable); + document.getElementById('refund-reported-filter').addEventListener('change', renderRefundReviewTable); + document.getElementById('refund-download-btn').addEventListener('click', downloadRefundExcel); + + document.getElementById('toggle-cases-btn').addEventListener('click', (e) => toggleAllGroups('#tab-my-cases .results-container', e.target)); + document.getElementById('toggle-docs-btn').addEventListener('click', (e) => toggleAllGroups('#tab-docs .results-container', e.target)); + document.getElementById('toggle-refund-btn').addEventListener('click', (e) => toggleAllGroups('#tab-refund .results-container', e.target)); + fetchMyCases(); }