From 50401a73c9647c1304a4e7d5a3725e3cfeb8dea1 Mon Sep 17 00:00:00 2001 From: Dias Baskara <25913324+diasbaskara@users.noreply.github.com> Date: Wed, 26 Nov 2025 13:02:55 +0700 Subject: [PATCH] Add Case History tab with grouped history view Introduces a new 'Case History' tab to display case routing history, grouped by case type and including current role information. Implements API calls to fetch history and subprocess data, adds filtering and group toggling, and updates tab switching logic to support the new feature. --- coretabs.user.js | 258 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 239 insertions(+), 19 deletions(-) diff --git a/coretabs.user.js b/coretabs.user.js index 3c8065d..4aecdbe 100644 --- a/coretabs.user.js +++ b/coretabs.user.js @@ -48,11 +48,14 @@ let allMyCases = [], allCaseDocuments = [], allCaseUsers = [], - refundReviewData = []; - let filteredRefundData = []; - let selectedCaseId = null; - let loadedDocsForCaseId = null; - let loadedUsersForCaseId = null; + caseHistoryData = [], + caseSubProcessData = {}, + refundReviewData = [], + filteredRefundData = [], + selectedCaseId = null, + loadedDocsForCaseId = null, + loadedUsersForCaseId = null, + loadedHistoryForCaseId = null; function addStyles() { GM_addStyle(` @@ -109,12 +112,20 @@ +

Loading my cases...

Please select a case to view its documents.

Please select a case to view its users.

+
+
+
+ +
+

Please select a case to view its history.

+
@@ -298,6 +309,118 @@ (responseArea.innerHTML = ""), responseArea.appendChild(table)); } + function renderCaseHistoryTable() { + const responseArea = document.querySelector( + "#tab-history .results-container", + ); + if (!caseHistoryData || caseHistoryData.length === 0) { + responseArea.innerHTML = + '

No history data available for this case.

'; + return; + } + + const filterValue = document.getElementById("history-type-filter").value; + const filteredHistory = caseHistoryData.filter( + (item) => filterValue === "all" || item.CaseType === filterValue, + ); + + if (filteredHistory.length === 0) { + responseArea.innerHTML = + '

No history items match the selected filter.

'; + return; + } + + const table = createTable([ + "Routing Date", + "Performed By", + "Workflow Step", + ]), + tbody = document.createElement("tbody"); + + // Group items by CaseType + const groupedHistory = {}; + filteredHistory.forEach((item) => { + const caseType = item.CaseType || "Unknown"; + if (!groupedHistory[caseType]) { + groupedHistory[caseType] = []; + } + groupedHistory[caseType].push(item); + }); + + // Create table rows with grouping + Object.keys(groupedHistory).forEach((caseType) => { + const groupItems = groupedHistory[caseType]; + + // Create group header row + const sanitizedCaseType = caseType.replace(/[^a-zA-Z0-9]/g, "_"); + tbody.innerHTML += `${caseType} (${groupItems.length} items)`; + + // Add current role type at the top of each group + // Find the most recent item (with latest RoutingDate) + const mostRecentItem = groupItems.reduce((latest, current) => { + const latestDate = new Date(latest.RoutingDate); + const currentDate = new Date(current.RoutingDate); + return currentDate > latestDate ? current : latest; + }); + const currentCaseRoleTypeCode = mostRecentItem.ToWorkflowStepIdentifier + ? caseSubProcessData[mostRecentItem.ToWorkflowStepIdentifier] + : null; + + // Always add the current role row, even if CaseRoleTypeCode is not available + const currentRoleRow = document.createElement("tr"); + currentRoleRow.className = `group-member history-group-${sanitizedCaseType}`; + + // Empty Routing Date + const emptyDateCell = document.createElement("td"); + emptyDateCell.textContent = ""; + currentRoleRow.appendChild(emptyDateCell); + + // CaseRoleTypeCode in Performed By column + const roleCell = document.createElement("td"); + if (currentCaseRoleTypeCode) { + roleCell.innerHTML = `Current Role:
${currentCaseRoleTypeCode}`; + } else { + roleCell.innerHTML = `Current Role:
Not available`; + } + currentRoleRow.appendChild(roleCell); + + // Last To Step in Workflow Step column + const workflowStepCell = document.createElement("td"); + workflowStepCell.textContent = mostRecentItem.ToWorkflowStep || "-"; + currentRoleRow.appendChild(workflowStepCell); + + tbody.appendChild(currentRoleRow); + + // Create member rows for this group + groupItems.forEach((item) => { + const row = document.createElement("tr"); + row.className = `group-member history-group-${sanitizedCaseType}`; + + // Format RoutingDate + const routingDate = new Date(item.RoutingDate); + const dateCell = document.createElement("td"); + dateCell.textContent = routingDate.toLocaleString("id-ID"); + row.appendChild(dateCell); + + // PerformedByUser + const performedByCell = document.createElement("td"); + performedByCell.textContent = item.PerformedByUser || "-"; + row.appendChild(performedByCell); + + // FromWorkflowStep (renamed to Workflow Step) + const workflowStepCell = document.createElement("td"); + workflowStepCell.textContent = item.FromWorkflowStep || "-"; + row.appendChild(workflowStepCell); + + tbody.appendChild(row); + }); + }); + + responseArea.innerHTML = ""; + table.appendChild(tbody); + responseArea.appendChild(table); + tbody.addEventListener("click", handleGroupToggle); + } function renderRefundReviewTable() { const responseArea = document.querySelector( "#tab-refund .results-container", @@ -481,6 +604,91 @@ handleError(error, responseArea); } } + async function fetchCaseHistory(caseId) { + const responseArea = document.querySelector( + "#tab-history .results-container", + ); + responseArea.innerHTML = + '

Loading history...

'; + try { + if (!caseId) throw new Error("No Case ID provided."); + const apiUrl = + "https://coretax.intranet.pajak.go.id/casemanagement/api/caseroutinghistory/list", + payload = { + AggregateIdentifier: caseId, + First: 0, + Rows: 1000, + SortField: "RoutingDate", + 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(); + caseHistoryData = data?.Payload?.Data || []; + loadedHistoryForCaseId = caseId; + + // Fetch CaseRoleTypeCode for each ToWorkflowStepIdentifier + await fetchCaseSubProcessData(caseId); + + populateFilter("history-type-filter", caseHistoryData, "CaseType"); + renderCaseHistoryTable(); + } catch (error) { + handleError(error, responseArea); + } + } + async function fetchCaseSubProcessData(caseId) { + try { + const apiUrl = + "https://coretax.intranet.pajak.go.id/casemanagement/api/casesubprocess/list", + payload = { + First: 0, + Rows: 10000, + Filters: [], + AggregateIdentifier: caseId, + }, + 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( + `Case SubProcess API Error: ${errorData.Message || response.statusText}`, + ); + } + + const data = await response.json(); + const subProcessData = data?.Payload?.Data || []; + + // Create a lookup map for WorkflowStepIdentifier to CaseRoleTypeCode + caseSubProcessData = {}; + subProcessData.forEach((item) => { + if (item.WorkflowStepIdentifier && item.CaseRoleTypeCode) { + caseSubProcessData[item.WorkflowStepIdentifier] = + item.CaseRoleTypeCode; + } + }); + } catch (error) { + console.error("Error fetching case subprocess data:", error); + caseSubProcessData = {}; + } + } + async function downloadDocument(docId, filename, button) { const originalText = button.textContent; ((button.textContent = "Downloading..."), (button.disabled = !0)); @@ -673,11 +881,9 @@ 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 - ); + thead.innerHTML = `${headers.map((e) => `${e}`).join("")}`; + table.appendChild(thead); + return table; } function handleError(error, area) { console.error("Userscript Error:", error); @@ -823,7 +1029,7 @@ headerRow.classList.toggle("expanded"); const isCollapsed = headerRow.classList.contains("collapsed"); const tableBody = headerRow.parentElement; - const memberRows = tableBody.querySelectorAll(`.group-member.${groupId}`); + const memberRows = tableBody.querySelectorAll(`tr.group-member.${groupId}`); memberRows.forEach((row) => { row.style.display = isCollapsed ? "none" : "table-row"; }); @@ -837,14 +1043,18 @@ .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)); + "tab-docs" === tabId && + selectedCaseId && + selectedCaseId !== loadedDocsForCaseId && + fetchCaseDocuments(selectedCaseId), + "tab-users" === tabId && + selectedCaseId && + selectedCaseId !== loadedUsersForCaseId && + fetchCaseUsers(selectedCaseId), + "tab-history" === tabId && + selectedCaseId && + selectedCaseId !== loadedHistoryForCaseId && + fetchCaseHistory(selectedCaseId)); } function downloadRefundExcel() { if (!filteredRefundData || filteredRefundData.length === 0) { @@ -992,6 +1202,10 @@ if (usersFilterEl) usersFilterEl.addEventListener("change", renderCaseUsersTable); + const historyFilterEl = document.getElementById("history-type-filter"); + if (historyFilterEl) + historyFilterEl.addEventListener("change", renderCaseHistoryTable); + const refundFilterEl = document.getElementById("refund-reported-filter"); if (refundFilterEl) refundFilterEl.addEventListener("change", renderRefundReviewTable); @@ -1018,6 +1232,12 @@ toggleAllGroups("#tab-refund .results-container", e.target), ); + const toggleHistoryBtn = document.getElementById("toggle-history-btn"); + if (toggleHistoryBtn) + toggleHistoryBtn.addEventListener("click", (e) => + toggleAllGroups("#tab-history .results-container", e.target), + ); + // initial load fetchMyCases(); }