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.
This commit is contained in:
258
coretabs.user.js
258
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 @@
|
||||
<button class="ct-tab-button active" data-tab="tab-my-cases">My Cases</button>
|
||||
<button class="ct-tab-button" data-tab="tab-docs">Case Documents</button>
|
||||
<button class="ct-tab-button" data-tab="tab-users">Case Users</button>
|
||||
<button class="ct-tab-button" data-tab="tab-history">Case History</button>
|
||||
<button class="ct-tab-button" data-tab="tab-refund">Refund Review</button>
|
||||
</div>
|
||||
<div id="ct-tab-content-area">
|
||||
<div id="tab-my-cases" class="ct-tab-panel active"><div class="filter-container"><div><label for="cases-status-filter">Filter by Status:</label><select id="cases-status-filter"></select></div><button id="toggle-cases-btn" class="action-btn" style="margin-left:auto">Collapse All</button></div><div class="results-container"><p style="padding:15px;color:#666;">Loading my cases...</p></div></div>
|
||||
<div id="tab-docs" class="ct-tab-panel"><div class="filter-container"><div><label for="docs-status-filter">Filter by Status:</label><select id="docs-status-filter"></select></div><button id="toggle-docs-btn" class="action-btn" style="margin-left:auto">Collapse All</button></div><div class="results-container"><p style="padding:15px;color:#666;">Please select a case to view its documents.</p></div></div>
|
||||
<div id="tab-users" class="ct-tab-panel"><div class="filter-container"><div><label for="users-role-filter">Filter by Role:</label><select id="users-role-filter"></select></div></div><div class="results-container"><p style="padding:15px;color:#666;">Please select a case to view its users.</p></div></div>
|
||||
<div id="tab-history" class="ct-tab-panel">
|
||||
<div class="filter-container">
|
||||
<div><label for="history-type-filter">Filter by Case Type:</label><select id="history-type-filter"></select></div>
|
||||
<button id="toggle-history-btn" class="action-btn" style="margin-left:auto">Collapse All</button>
|
||||
</div>
|
||||
<div class="results-container"><p style="padding:15px;color:#666;">Please select a case to view its history.</p></div>
|
||||
</div>
|
||||
<div id="tab-refund" class="ct-tab-panel">
|
||||
<div class="filter-container">
|
||||
<div><label for="refund-reported-filter">Filter by Reported:</label><select id="refund-reported-filter"></select></div>
|
||||
@@ -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 =
|
||||
'<p style="padding:15px;color:#666;">No history data available for this case.</p>';
|
||||
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 =
|
||||
'<p style="padding:15px;color:#666;">No history items match the selected filter.</p>';
|
||||
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 += `<tr class="group-header expanded" data-group-id="history-group-${sanitizedCaseType}"><td colspan="3"><span class="toggle-icon"></span>${caseType} (${groupItems.length} items)</td></tr>`;
|
||||
|
||||
// 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 = `<strong>Current Role:</strong><br><small style="color: #666;">${currentCaseRoleTypeCode}</small>`;
|
||||
} else {
|
||||
roleCell.innerHTML = `<strong>Current Role:</strong><br><small style="color: #666; font-style: italic;">Not available</small>`;
|
||||
}
|
||||
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 =
|
||||
'<p style="padding:15px;color:#666;">Loading history...</p>';
|
||||
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 = `<tr>${headers.map((e) => `<th>${e}</th>`).join("")}</tr>`),
|
||||
table.appendChild(thead),
|
||||
table
|
||||
);
|
||||
thead.innerHTML = `<tr>${headers.map((e) => `<th>${e}</th>`).join("")}</tr>`;
|
||||
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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user