From b38ee62ef99af5e6ccb4131343e4e0d7c80617b0 Mon Sep 17 00:00:00 2001 From: Dias Baskara <25913324+diasbaskara@users.noreply.github.com> Date: Wed, 26 Nov 2025 10:47:44 +0700 Subject: [PATCH] Prevent sidebar injection in iframes and duplicate loads Added guards to ensure the userscript sidebar is only injected into the top window and not into iframes. Also prevents duplicate sidebar injection in the top window. Updated z-index for sidebar, improved DOM creation safety, and made minor UI text changes. --- coretabs.user.js | 1164 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 889 insertions(+), 275 deletions(-) diff --git a/coretabs.user.js b/coretabs.user.js index bd9b6b0..dec19c9 100644 --- a/coretabs.user.js +++ b/coretabs.user.js @@ -10,25 +10,53 @@ // @run-at document-idle // ==/UserScript== -(function() { - 'use strict'; +(function () { + "use strict"; - // --- 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_PREFIX = 'Pengembalian'; - // ---------------------------- + // Prevent running inside iframes and prevent duplicate injection into the top window. + // This ensures only one instance of the sidebar is created even if the userscript is executed + // for multiple frames on the same page. + try { + if (window.top !== window.self) { + // We're in an iframe - do nothing. + return; + } + } catch (e) { + // In rare cross-origin cases accessing window.top may throw; if so, abort to be safe. + return; + } - // --- State Management --- - let allMyCases = [], allCaseDocuments = [], allCaseUsers = [], refundReviewData = []; - let filteredRefundData = []; - let selectedCaseId = null; - let loadedDocsForCaseId = null; - let loadedUsersForCaseId = null; + // Guard against double-injection in the top window (e.g., userscript re-run) + if (window.__coretabs_injected) { + return; + } + Object.defineProperty(window, "__coretabs_injected", { + value: true, + configurable: false, + writable: false, + enumerable: false, + }); - function addStyles() { - GM_addStyle(` - #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} + // --- 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_PREFIX = "Pengembalian"; + // ---------------------------- + + // --- State Management --- + 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:-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:2147483647;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; } @@ -56,13 +84,16 @@ .currency-wrapper { display: flex; justify-content: space-between; } .currency-num { font-variant-numeric: tabular-nums; } `); - } + } - function createSidebar() { - const sidebarContainer = document.createElement('div'); - sidebarContainer.innerHTML = ` + function createSidebar() { + // ensure we don't create duplicate DOM elements even if something else tries to run + if (document.getElementById("ct-sidebar")) return; + + const sidebarContainer = document.createElement("div"); + sidebarContainer.innerHTML = `
- +
@@ -94,299 +125,882 @@
`; - document.body.appendChild(sidebarContainer); - } + // Append to document.body (we are running only in top window so this is safe) + document.body.appendChild(sidebarContainer); + } - // --- RENDER FUNCTIONS --- - function renderMyCasesTable() {const responseArea=document.querySelector("#tab-my-cases .results-container"),filterValue=document.getElementById("cases-status-filter").value,filteredCases="all"===filterValue?allMyCases:allMyCases.filter(e=>e.CaseStatus===filterValue);if(document.getElementById("toggle-cases-btn").textContent="Collapse All",0===filteredCases.length)return void(responseArea.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+=`${currentGroup||"Uncategorized"}`}const t=document.createElement("tr");t.className=`group-member my-cases-group-${groupIndex}`;const o=e.AggregateIdentifier;t.dataset.id=o,o===selectedCaseId&&t.classList.add("selected");const a=new Date(e.CreatedDate).toLocaleDateString("id-ID"),s=o&&"string"==typeof o&&""!==o.trim(),r=s?"":'disabled title="Action unavailable: Case ID is missing"';let d="";e.CaseTypeName&&e.CaseTypeName.startsWith(REFUND_CASE_PREFIX)&&(d=``),t.innerHTML=` - ${e.CaseNumber||"N/A"} - ${e.MainTaxpayerName||"N/A"} - ${e.CaseTypeName||"N/A"} - ${e.CaseStatus||"N/A"} + // --- RENDER FUNCTIONS --- + function renderMyCasesTable() { + const responseArea = document.querySelector( + "#tab-my-cases .results-container", + ), + filterValue = document.getElementById("cases-status-filter").value, + filteredCases = + "all" === filterValue + ? allMyCases + : allMyCases.filter((e) => e.CaseStatus === filterValue); + if ( + ((document.getElementById("toggle-cases-btn").textContent = + "Collapse All"), + 0 === filteredCases.length) + ) + return void (responseArea.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 += `${currentGroup || "Uncategorized"}`; + } + const t = document.createElement("tr"); + t.className = `group-member my-cases-group-${groupIndex}`; + const o = e.AggregateIdentifier; + ((t.dataset.id = o), o === selectedCaseId && t.classList.add("selected")); + const a = new Date(e.CreatedDate).toLocaleDateString("id-ID"), + s = o && "string" == typeof o && "" !== o.trim(), + r = s ? "" : 'disabled title="Action unavailable: Case ID is missing"'; + let d = ""; + (e.CaseTypeName && + e.CaseTypeName.startsWith(REFUND_CASE_PREFIX) && + (d = ``), + (t.innerHTML = ` + ${e.CaseNumber || "N/A"} + ${e.MainTaxpayerName || "N/A"} + ${e.CaseTypeName || "N/A"} + ${e.CaseStatus || "N/A"} ${a} Open ${d} - `,tbody.appendChild(t)}),table.appendChild(tbody),responseArea.innerHTML="",responseArea.appendChild(table),tbody.addEventListener("click",handleGroupToggle),tbody.addEventListener("click",handleCaseSelection)} - function renderCaseDocumentsTable() {const responseArea=document.querySelector("#tab-docs .results-container"),filterValue=document.getElementById("docs-status-filter").value,filteredDocs="all"===filterValue?allCaseDocuments:allCaseDocuments.filter(e=>e.DocumentStatus===filterValue);if(document.getElementById("toggle-docs-btn").textContent="Collapse All",0===filteredDocs.length)return void(responseArea.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+=`${currentGroup||"Uncategorized"}`}const t=e.DocumentDate?new Date(e.DocumentDate).toLocaleDateString("id-ID"):"N/A";tbody.innerHTML+=` + `), + tbody.appendChild(t)); + }), + table.appendChild(tbody), + (responseArea.innerHTML = ""), + responseArea.appendChild(table), + tbody.addEventListener("click", handleGroupToggle), + tbody.addEventListener("click", handleCaseSelection)); + } + function renderCaseDocumentsTable() { + const responseArea = document.querySelector("#tab-docs .results-container"), + filterValue = document.getElementById("docs-status-filter").value, + filteredDocs = + "all" === filterValue + ? allCaseDocuments + : allCaseDocuments.filter((e) => e.DocumentStatus === filterValue); + if ( + ((document.getElementById("toggle-docs-btn").textContent = + "Collapse All"), + 0 === filteredDocs.length) + ) + return void (responseArea.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 += `${currentGroup || "Uncategorized"}`; + } + const t = e.DocumentDate + ? new Date(e.DocumentDate).toLocaleDateString("id-ID") + : "N/A"; + tbody.innerHTML += ` - ${e.LetterNumber||"N/A"} - ${e.FileName||"N/A"} - ${e.DocumentStatus||"N/A"} + ${e.LetterNumber || "N/A"} + ${e.FileName || "N/A"} + ${e.DocumentStatus || "N/A"} ${t} - `}),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+=` + `; + }), + 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 += ` - ${e.FullName||"N/A"} - ${e.Nip||"N/A"} - ${e.Jabatan||"N/A"} - ${e.OfficeName||"N/A"} - ${e.CaseRoleType||"N/A"} - `}),table.appendChild(tbody),responseArea.innerHTML="",responseArea.appendChild(table)} - function renderRefundReviewTable() {const responseArea=document.querySelector("#tab-refund .results-container"),filterValue=document.getElementById("refund-reported-filter").value;document.getElementById("refund-download-btn").disabled=!refundReviewData||0===refundReviewData.length,document.getElementById("toggle-refund-btn").textContent="Collapse All";let dataToRender=refundReviewData;"all"!==filterValue&&(dataToRender=refundReviewData.filter(e=>e.ReportedBySeller===("true"===filterValue))),filteredRefundData=dataToRender;if(!dataToRender||0===dataToRender.length)return void(responseArea.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=` + ${e.FullName || "N/A"} + ${e.Nip || "N/A"} + ${e.Jabatan || "N/A"} + ${e.OfficeName || "N/A"} + ${e.CaseRoleType || "N/A"} + `; + }), + table.appendChild(tbody), + (responseArea.innerHTML = ""), + responseArea.appendChild(table)); + } + function renderRefundReviewTable() { + const responseArea = document.querySelector( + "#tab-refund .results-container", + ), + filterValue = document.getElementById("refund-reported-filter").value; + ((document.getElementById("refund-download-btn").disabled = + !refundReviewData || 0 === refundReviewData.length), + (document.getElementById("toggle-refund-btn").textContent = + "Collapse All")); + let dataToRender = refundReviewData; + ("all" !== filterValue && + (dataToRender = refundReviewData.filter( + (e) => e.ReportedBySeller === ("true" === filterValue), + )), + (filteredRefundData = dataToRender)); + if (!dataToRender || 0 === dataToRender.length) + return void (responseArea.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 = ` -
${e.Name||"Unknown Name"}
-
${e.Tin||"Unknown TIN"}
-
`}const o=tbody.insertRow();o.className=`group-member refund-group-${groupIndex}`;const a=e.DocumentDate?new Date(e.DocumentDate).toLocaleDateString("id-ID"):"N/A",s=e.ReportedBySeller?'':'';o.insertCell().textContent=e.DocumentNumber||"N/A",o.insertCell().textContent=a;const r=o.insertCell();r.innerHTML=`
Rp${(e.SellingPrice||0).toLocaleString("id-ID")}
`;const d=o.insertCell();d.innerHTML=`
Rp${(e.VatPaid||0).toLocaleString("id-ID")}
`;const n=o.insertCell();n.innerHTML=`
Rp${(e.StlgPaid||0).toLocaleString("id-ID")}
`,o.insertCell().textContent=e.TransactionCode||"N/A";const c=o.insertCell();c.className="reported-cell",c.innerHTML=s}),tbody.addEventListener("click",handleGroupToggle)} - - // --- DATA FETCHING FUNCTIONS --- - async function fetchMyCases(){const responseArea=document.querySelector("#tab-my-cases .results-container");try{const authToken=getAuthToken(),apiUrl="https://coretax.intranet.pajak.go.id/casemanagement/api/caselist/mycases",fetchOptions={method:"POST",headers:getHeaders(),body:JSON.stringify({})},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();allMyCases=data?.Payload?.Data||[],populateFilter("cases-status-filter",allMyCases,"CaseStatus");const casesFilter=document.getElementById("cases-status-filter");Array.from(casesFilter.options).some(e=>e.value===DEFAULT_CASES_FILTER)&&(casesFilter.value=DEFAULT_CASES_FILTER),renderMyCasesTable()}catch(error){handleError(error,responseArea)}} - 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} - - // NEW: This function now includes the fallback logic. - async function fetchC02FormDetail(caseId, subProcessId) { - const payload = { caseAggregateIdentifier: caseId, CaseSubProcessIdentifier: subProcessId }; - const fetchOptions = { method: "POST", headers: getHeaders(caseId), body: JSON.stringify(payload) }; - - // 1. Primary API Attempt - const primaryApiUrl = "https://coretax.intranet.pajak.go.id/casecomponentspayment/api/c02form014/c02form009detail/current"; - try { - const response = await fetch(primaryApiUrl, fetchOptions); - if (response.ok) { - const data = await response.json(); - const reference = data?.Payload?.[0]?.Reference; - if (reference) return reference; // Success on primary API - } - } catch (error) { - console.warn("Primary API for C02Form failed, trying fallback.", error); +
${e.Name || "Unknown Name"}
+
${e.Tin || "Unknown TIN"}
+ `)); } + const o = tbody.insertRow(); + o.className = `group-member refund-group-${groupIndex}`; + const a = e.DocumentDate + ? new Date(e.DocumentDate).toLocaleDateString("id-ID") + : "N/A", + s = e.ReportedBySeller + ? '' + : ''; + ((o.insertCell().textContent = e.DocumentNumber || "N/A"), + (o.insertCell().textContent = a)); + const r = o.insertCell(); + r.innerHTML = `
Rp${(e.SellingPrice || 0).toLocaleString("id-ID")}
`; + const d = o.insertCell(); + d.innerHTML = `
Rp${(e.VatPaid || 0).toLocaleString("id-ID")}
`; + const n = o.insertCell(); + ((n.innerHTML = `
Rp${(e.StlgPaid || 0).toLocaleString("id-ID")}
`), + (o.insertCell().textContent = e.TransactionCode || "N/A")); + const c = o.insertCell(); + ((c.className = "reported-cell"), (c.innerHTML = s)); + }), + tbody.addEventListener("click", handleGroupToggle)); + } - // 2. Fallback API Attempt - const responseArea = document.querySelector('#tab-refund .results-container'); - responseArea.innerHTML = `

Step 2/3: Fetching reference number (trying fallback API)...

`; - const fallbackApiUrl = "https://coretax.intranet.pajak.go.id/casecomponentspayment/api/c02form014/view"; - const fallbackResponse = await fetch(fallbackApiUrl, fetchOptions); - if (!fallbackResponse.ok) { - const errorData = await fallbackResponse.json(); - throw new Error(`C02Form Detail API Error (Fallback): ${errorData.Message || fallbackResponse.statusText}`); - } - const fallbackData = await fallbackResponse.json(); - const fallbackReference = fallbackData?.Payload?.Details?.[0]?.Reference; - if (fallbackReference) return fallbackReference; // Success on fallback API - - // 3. If both fail - throw new Error("Could not find a 'Reference' number in either C02Form Detail response."); + // --- DATA FETCHING FUNCTIONS --- + async function fetchMyCases() { + const responseArea = document.querySelector( + "#tab-my-cases .results-container", + ); + try { + const authToken = getAuthToken(), + apiUrl = + "https://coretax.intranet.pajak.go.id/casemanagement/api/caselist/mycases", + fetchOptions = { + method: "POST", + headers: getHeaders(), + body: JSON.stringify({}), + }, + 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(); + ((allMyCases = data?.Payload?.Data || []), + populateFilter("cases-status-filter", allMyCases, "CaseStatus")); + const casesFilter = document.getElementById("cases-status-filter"); + (Array.from(casesFilter.options).some( + (e) => e.value === DEFAULT_CASES_FILTER, + ) && (casesFilter.value = DEFAULT_CASES_FILTER), + renderMyCasesTable()); + } catch (error) { + handleError(error, responseArea); } - - 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)}} + } + 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; + } - // --- 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 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=`${headers.map(e=>`${e}`).join("")}`,table.appendChild(thead),table} - function handleError(error,area){console.error("Userscript Error:",error);let errorHtml;error.message.includes("Authorization Token not found")?errorHtml=` + // NEW: This function now includes the fallback logic. + async function fetchC02FormDetail(caseId, subProcessId) { + const payload = { + caseAggregateIdentifier: caseId, + CaseSubProcessIdentifier: subProcessId, + }; + const fetchOptions = { + method: "POST", + headers: getHeaders(caseId), + body: JSON.stringify(payload), + }; + + // 1. Primary API Attempt + const primaryApiUrl = + "https://coretax.intranet.pajak.go.id/casecomponentspayment/api/c02form014/c02form009detail/current"; + try { + const response = await fetch(primaryApiUrl, fetchOptions); + if (response.ok) { + const data = await response.json(); + const reference = data?.Payload?.[0]?.Reference; + if (reference) return reference; // Success on primary API + } + } catch (error) { + console.warn("Primary API for C02Form failed, trying fallback.", error); + } + + // 2. Fallback API Attempt + const responseArea = document.querySelector( + "#tab-refund .results-container", + ); + responseArea.innerHTML = `

Step 2/3: Fetching reference number (trying fallback API)...

`; + const fallbackApiUrl = + "https://coretax.intranet.pajak.go.id/casecomponentspayment/api/c02form014/view"; + const fallbackResponse = await fetch(fallbackApiUrl, fetchOptions); + if (!fallbackResponse.ok) { + const errorData = await fallbackResponse.json(); + throw new Error( + `C02Form Detail API Error (Fallback): ${errorData.Message || fallbackResponse.statusText}`, + ); + } + const fallbackData = await fallbackResponse.json(); + const fallbackReference = fallbackData?.Payload?.Details?.[0]?.Reference; + if (fallbackReference) return fallbackReference; // Success on fallback API + + // 3. If both fail + throw new Error( + "Could not find a 'Reference' number in either C02Form Detail response.", + ); + } + + 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 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 = `${headers.map((e) => `${e}`).join("")}`), + table.appendChild(thead), + table + ); + } + function handleError(error, area) { + console.error("Userscript Error:", error); + let errorHtml; + (error.message.includes("Authorization Token not found") + ? (errorHtml = `
Session Expired or Token Not Found

Please refresh the page to log in again.

-
`:errorHtml=`

An error occurred:
${error.message}

`,area.innerHTML=errorHtml;const refreshBtn=area.querySelector("#auth-refresh-btn");refreshBtn&&refreshBtn.addEventListener("click",()=>window.location.reload())} - function updateHeader(caseObject) { - const titleEl = document.getElementById('ct-header-title'); - const subtitleEl = document.getElementById('ct-header-subtitle'); - const actionsContainer = document.getElementById('ct-header-actions'); - actionsContainer.innerHTML = ''; // Clear previous buttons +
`) + : (errorHtml = `

An error occurred:
${error.message}

`), + (area.innerHTML = errorHtml)); + const refreshBtn = area.querySelector("#auth-refresh-btn"); + refreshBtn && + refreshBtn.addEventListener("click", () => window.location.reload()); + } + function updateHeader(caseObject) { + const titleEl = document.getElementById("ct-header-title"); + const subtitleEl = document.getElementById("ct-header-subtitle"); + const actionsContainer = document.getElementById("ct-header-actions"); + actionsContainer.innerHTML = ""; // Clear previous buttons - if (caseObject) { - titleEl.textContent = caseObject.MainTaxpayerName || 'N/A'; - subtitleEl.textContent = caseObject.CaseNumber || 'N/A'; - const caseId = caseObject.AggregateIdentifier; - const hasValidId = caseId && typeof caseId === 'string' && caseId.trim() !== ''; + if (caseObject) { + titleEl.textContent = caseObject.MainTaxpayerName || "N/A"; + subtitleEl.textContent = caseObject.CaseNumber || "N/A"; + const caseId = caseObject.AggregateIdentifier; + const hasValidId = + caseId && typeof caseId === "string" && caseId.trim() !== ""; - const openBtn = document.createElement('a'); - openBtn.href = `https://coretax.intranet.pajak.go.id/case-management/id-ID/case-overview/${caseId}`; - openBtn.className = 'action-btn open-case'; - openBtn.textContent = 'Open'; - if (!hasValidId) openBtn.disabled = true; + const openBtn = document.createElement("a"); + openBtn.href = `https://coretax.intranet.pajak.go.id/case-management/id-ID/case-overview/${caseId}`; + openBtn.className = "action-btn open-case"; + openBtn.textContent = "Open"; + if (!hasValidId) openBtn.disabled = true; - const docsBtn = document.createElement('button'); - docsBtn.className = 'action-btn view-docs'; - docsBtn.textContent = 'View Docs'; - if (!hasValidId) docsBtn.disabled = true; - docsBtn.addEventListener('click', () => switchTab('tab-docs')); + const docsBtn = document.createElement("button"); + docsBtn.className = "action-btn view-docs"; + docsBtn.textContent = "View Docs"; + if (!hasValidId) docsBtn.disabled = true; + docsBtn.addEventListener("click", () => switchTab("tab-docs")); - const usersBtn = document.createElement('button'); - usersBtn.className = 'action-btn view-users'; - usersBtn.textContent = 'View Users'; - if (!hasValidId) usersBtn.disabled = true; - usersBtn.addEventListener('click', () => switchTab('tab-users')); + const usersBtn = document.createElement("button"); + usersBtn.className = "action-btn view-users"; + usersBtn.textContent = "View Users"; + if (!hasValidId) usersBtn.disabled = true; + usersBtn.addEventListener("click", () => switchTab("tab-users")); - actionsContainer.append(openBtn, docsBtn, usersBtn); + actionsContainer.append(openBtn, docsBtn, usersBtn); - if (caseObject.CaseTypeName && caseObject.CaseTypeName.startsWith(REFUND_CASE_PREFIX)) { - const refundBtn = document.createElement('button'); - refundBtn.className = 'action-btn review-refund-case'; - refundBtn.textContent = 'Refund Review'; - actionsContainer.appendChild(refundBtn); - refundBtn.addEventListener('click', () => startRefundReviewProcess(caseId, refundBtn)); - } + if ( + caseObject.CaseTypeName && + caseObject.CaseTypeName.startsWith(REFUND_CASE_PREFIX) + ) { + const refundBtn = document.createElement("button"); + refundBtn.className = "action-btn review-refund-case"; + refundBtn.textContent = "Refund Review"; + actionsContainer.appendChild(refundBtn); + refundBtn.addEventListener("click", () => + startRefundReviewProcess(caseId, refundBtn), + ); + } + } else { + titleEl.textContent = "No Case Selected"; + subtitleEl.textContent = 'Please select a case from the "My Cases" tab'; + } + } + + // MODIFIED: This function has updated user-facing messages. + async function startRefundReviewProcess(caseId, button) { + const originalText = button.textContent; + button.textContent = "..."; + button.disabled = true; + const responseArea = document.querySelector( + "#tab-refund .results-container", + ); + try { + switchTab("tab-refund"); + responseArea.innerHTML = `

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 (trying primary API)...

`; + 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 handleCaseSelection(event) { + const selectedRow = event.target.closest("tr"); + if (!selectedRow || selectedRow.classList.contains("group-header")) return; + const caseId = selectedRow.dataset.id; + if (!caseId || caseId === selectedCaseId) return; + + const selectedCase = allMyCases.find( + (e) => e.AggregateIdentifier === caseId, + ); + updateHeader(selectedCase); + selectedCaseId = caseId; + loadedDocsForCaseId = null; + loadedUsersForCaseId = null; + 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"); + if (actionButton) { + if (actionButton.matches(".view-docs")) { + switchTab("tab-docs"); + } else if (actionButton.matches(".view-users")) { + switchTab("tab-users"); + } else if (actionButton.matches(".review-refund-case")) { + startRefundReviewProcess(caseId, actionButton); + } + } + } + function handleDocumentAction(event) { + 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 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 { - titleEl.textContent = 'No Case Selected'; - subtitleEl.textContent = 'Please select a case from the "My Cases" tab'; - } - } - - // MODIFIED: This function has updated user-facing messages. - async function startRefundReviewProcess(caseId, button) { - const originalText = button.textContent; - button.textContent = '...'; - button.disabled = true; - const responseArea = document.querySelector('#tab-refund .results-container'); - try { - switchTab('tab-refund'); - responseArea.innerHTML = `

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 (trying primary API)...

`; - 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; + 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 --- + function main() { + addStyles(); + createSidebar(); + const toggleBtn = document.getElementById("ct-sidebar-toggle"); + if (toggleBtn) { + toggleBtn.addEventListener("click", () => { + const sidebar = document.getElementById("ct-sidebar"); + if (sidebar) sidebar.classList.toggle("open"); + }); } - function handleCaseSelection(event) { - const selectedRow = event.target.closest("tr"); - if (!selectedRow || selectedRow.classList.contains("group-header")) return; - const caseId = selectedRow.dataset.id; - if (!caseId || caseId === selectedCaseId) return; - - const selectedCase = allMyCases.find(e => e.AggregateIdentifier === caseId); - updateHeader(selectedCase); - selectedCaseId = caseId; - loadedDocsForCaseId = null; - loadedUsersForCaseId = null; - 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"); - if (actionButton) { - if (actionButton.matches(".view-docs")) { switchTab("tab-docs"); } - else if (actionButton.matches(".view-users")) { switchTab("tab-users"); } - else if (actionButton.matches(".review-refund-case")) { startRefundReviewProcess(caseId, actionButton); } - } - } - function handleDocumentAction(event) { - 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 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); + const tabBar = document.getElementById("ct-tab-bar"); + if (tabBar) { + tabBar.addEventListener("click", (e) => { + if (e.target.matches(".ct-tab-button")) switchTab(e.target.dataset.tab); + }); } - function toggleAllGroups(containerSelector, button) { - const isCollapsing = button.textContent === 'Collapse All'; - const headers = document.querySelectorAll(`${containerSelector} .group-header`); - const members = document.querySelectorAll(`${containerSelector} .group-member`); + const casesFilterEl = document.getElementById("cases-status-filter"); + if (casesFilterEl) + casesFilterEl.addEventListener("change", renderMyCasesTable); - headers.forEach(h => { - h.classList.toggle('expanded', !isCollapsing); - h.classList.toggle('collapsed', isCollapsing); - }); - members.forEach(m => { - m.style.display = isCollapsing ? 'none' : 'table-row'; - }); + const docsFilterEl = document.getElementById("docs-status-filter"); + if (docsFilterEl) + docsFilterEl.addEventListener("change", renderCaseDocumentsTable); - button.textContent = isCollapsing ? 'Expand All' : 'Collapse All'; - } + const usersFilterEl = document.getElementById("users-role-filter"); + if (usersFilterEl) + usersFilterEl.addEventListener("change", renderCaseUsersTable); - // --- MAIN INITIALIZATION --- - function main() { - addStyles(); - createSidebar(); - document.getElementById('ct-sidebar-toggle').addEventListener('click', () => { - document.getElementById('ct-sidebar').classList.toggle('open'); - }); - document.getElementById('ct-tab-bar').addEventListener('click', (e) => { - if (e.target.matches('.ct-tab-button')) switchTab(e.target.dataset.tab); - }); - 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); + const refundFilterEl = document.getElementById("refund-reported-filter"); + if (refundFilterEl) + refundFilterEl.addEventListener("change", renderRefundReviewTable); - 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)); + const refundDownloadBtn = document.getElementById("refund-download-btn"); + if (refundDownloadBtn) + refundDownloadBtn.addEventListener("click", downloadRefundExcel); - fetchMyCases(); - } + const toggleCasesBtn = document.getElementById("toggle-cases-btn"); + if (toggleCasesBtn) + toggleCasesBtn.addEventListener("click", (e) => + toggleAllGroups("#tab-my-cases .results-container", e.target), + ); + const toggleDocsBtn = document.getElementById("toggle-docs-btn"); + if (toggleDocsBtn) + toggleDocsBtn.addEventListener("click", (e) => + toggleAllGroups("#tab-docs .results-container", e.target), + ); + + const toggleRefundBtn = document.getElementById("toggle-refund-btn"); + if (toggleRefundBtn) + toggleRefundBtn.addEventListener("click", (e) => + toggleAllGroups("#tab-refund .results-container", e.target), + ); + + // initial load + fetchMyCases(); + } + + // Run main when DOM is ready (script runs at document-idle but ensure body exists) + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", main); + } else { main(); -})(); \ No newline at end of file + } +})();