// ==UserScript== // @name CoreTabs // @namespace https://git.diasbaskara.id // @version 0.1 // @description Manage your cases easily. // @author Dias Baskara // @match https://coretax.intranet.pajak.go.id/* // @grant GM_addStyle // @run-at document-idle // ==/UserScript== (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'; // ---------------------------- // --- State Management --- let allMyCases = [], allCaseDocuments = [], allCaseUsers = []; 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-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} .action-btn.download-doc { background-color: #28a745; color: white; border-color: #28a745; } .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} `); } function createSidebar() { const sidebarContainer = document.createElement('div'); sidebarContainer.innerHTML = `
`; document.body.appendChild(sidebarContainer); } // --- 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 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 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 += `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; } } // --- 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 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.
An error occurred:
${error.message}