All GCC vendors and employee details for IT department management
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

4125 lines
191 KiB

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<script th:inline="javascript">
(function () {
const currentUser = /*[[${session.adj_usrName}]]*/ null;
if (currentUser && sessionStorage.getItem("adj_usrName") !== currentUser) {
window.location.replace(/*[[@{/gccadjustmentmanagement/login}]]*/ '/gccadjustmentmanagement/login');
}
})();
</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Adjustment File Management System</title>
<!-- CSRF Tokens -->
<meta th:if="${_csrf != null}" name="_csrf" th:content="${_csrf.token}" />
<meta th:if="${_csrf != null}" name="_csrf_header" th:content="${_csrf.headerName}" />
<!-- Google Fonts -->
<link
href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&family=Poppins:wght@300;400;500;600;700&display=swap"
rel="stylesheet">
<!-- Bootstrap & FontAwesome -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
<link rel="stylesheet" th:href="@{/assets/font-awesome-6.5.2/css/all.min.css}" type="text/css">
<!-- SweetAlert2 -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<!-- SheetJS (Excel Export) -->
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
<!-- Core Style Design System -->
<style>
:root {
--primary: #0a4b64;
--primary-light: #f0f6f8;
--primary-hover: #083c50;
--accent: #e28b12;
--accent-light: #fef8eb;
--slate-50: #f8fafc;
--slate-100: #f1f5f9;
--slate-200: #e2e8f0;
--slate-300: #cbd5e1;
--slate-400: #94a3b8;
--slate-600: #475569;
--slate-700: #334155;
--slate-800: #1e293b;
--success: #10b981;
--success-light: #ecfdf5;
--warning: #f59e0b;
--warning-light: #fffbeb;
--danger: #ef4444;
--danger-light: #fef2f2;
--card-shadow: 0 10px 30px rgba(0, 0, 0, 0.03);
--hover-shadow: 0 15px 35px rgba(10, 75, 100, 0.08);
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
body {
background-color: #f3f7f9;
font-family: 'Poppins', sans-serif;
color: var(--slate-700);
min-height: 100vh;
overflow-x: hidden;
}
/* Premium Header Styling */
.header {
background: linear-gradient(135deg, #06232e, #0a4b64);
color: #fff;
padding: 15px 30px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
position: relative;
}
.logo-left {
display: flex;
align-items: center;
gap: 15px;
}
.logo-left img {
width: 50px;
height: 50px;
filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.2));
}
.logo-text h2 {
font-family: 'Outfit', sans-serif;
font-size: 1.1rem;
font-weight: 800;
margin: 0;
line-height: 1.2;
letter-spacing: 0.5px;
text-transform: uppercase;
}
.logo-text p {
font-size: 0.7rem;
margin: 0;
color: #8ed0e8;
font-weight: 500;
}
.header-title {
position: absolute;
left: 50%;
transform: translateX(-50%);
text-align: center;
z-index: 1;
}
.header-title h2 {
font-family: 'Outfit', sans-serif;
font-size: 1.4rem;
font-weight: 800;
margin: 0;
color: #ffffff;
letter-spacing: 0.5px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
}
.user-menu {
display: flex;
align-items: center;
gap: 15px;
z-index: 2;
}
.user-badge {
background: rgba(255, 255, 255, 0.12);
padding: 8px 16px;
border-radius: 30px;
font-size: 0.85rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.btn-logout {
background: #ef4444;
color: #fff;
border: none;
padding: 8px 18px;
border-radius: 30px;
font-size: 0.85rem;
font-weight: 600;
cursor: pointer;
transition: var(--transition);
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 6px;
box-shadow: 0 4px 10px rgba(239, 68, 68, 0.2);
}
.btn-logout:hover {
background: #dc2626;
color: #fff;
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(220, 38, 38, 0.3);
}
/* Top Module Navigation (Pills style from screenshot) */
.top-navbar {
background-color: #ffffff;
border-bottom: 1px solid var(--slate-200);
padding: 10px 30px;
display: flex;
justify-content: space-between;
align-items: center;
}
.nav-pills-container {
display: flex;
gap: 12px;
}
.nav-pill-btn {
background: none;
border: 1px solid var(--slate-200);
padding: 8px 20px;
border-radius: 20px;
font-size: 0.9rem;
font-weight: 600;
color: var(--slate-600);
cursor: pointer;
transition: var(--transition);
display: flex;
align-items: center;
gap: 8px;
}
.nav-pill-btn:hover {
background-color: var(--slate-50);
color: var(--primary);
border-color: var(--slate-300);
}
.nav-pill-btn.active {
background: var(--primary);
color: #ffffff;
border-color: var(--primary);
box-shadow: 0 4px 12px rgba(10, 75, 100, 0.2);
}
/* App Container & Panels */
.app-content {
padding: 30px;
max-width: 1800px;
margin: 0 auto;
}
.view-panel {
display: none;
animation: fadeIn 0.4s ease forwards;
}
.view-panel.active {
display: block;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(15px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Sidebar & Vendor Filtering Cards (Left side) */
.filter-sidebar {
background-color: #ffffff;
border-radius: 20px;
padding: 24px;
box-shadow: var(--card-shadow);
border: 1px solid rgba(10, 75, 100, 0.05);
margin-bottom: 30px;
}
.sidebar-header-title {
font-family: 'Outfit', sans-serif;
font-size: 1.1rem;
font-weight: 800;
color: var(--primary);
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 10px;
border-bottom: 1.5px solid var(--slate-100);
padding-bottom: 10px;
}
.vendor-pill {
background-color: var(--slate-50);
border: 1px solid var(--slate-200);
padding: 10px 16px;
border-radius: 12px;
margin-bottom: 10px;
font-size: 0.88rem;
font-weight: 600;
color: var(--slate-700);
cursor: pointer;
transition: var(--transition);
display: flex;
justify-content: space-between;
align-items: center;
}
.vendor-pill:hover,
.vendor-pill.active {
background-color: var(--primary-light);
border-color: var(--primary);
color: var(--primary);
transform: translateX(3px);
}
.vendor-count-badge {
background-color: var(--primary);
color: #ffffff;
padding: 3px 8px;
font-size: 0.75rem;
font-weight: 700;
border-radius: 20px;
}
.vendor-pill:hover .vendor-count-badge,
.vendor-pill.active .vendor-count-badge {
background-color: var(--accent);
}
/* Styling Cards */
.card-custom {
background: #ffffff;
border-radius: 20px;
box-shadow: var(--card-shadow);
border: 1px solid rgba(10, 75, 100, 0.05);
padding: 30px;
margin-bottom: 30px;
transition: var(--transition);
}
.card-custom:hover {
box-shadow: var(--hover-shadow);
}
.section-header-title {
font-family: 'Outfit', sans-serif;
font-size: 1.5rem;
font-weight: 800;
color: var(--primary);
margin-bottom: 25px;
display: flex;
align-items: center;
gap: 12px;
}
.form-section-title {
font-family: 'Outfit', sans-serif;
font-size: 1.15rem;
font-weight: 700;
color: var(--primary);
margin-top: 15px;
margin-bottom: 20px;
padding-bottom: 8px;
border-bottom: 2px solid var(--primary-light);
display: flex;
align-items: center;
gap: 10px;
}
/* Form Inputs */
.form-label {
font-size: 0.85rem;
font-weight: 700;
color: var(--slate-600);
margin-bottom: 6px;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.form-control-custom {
height: 46px;
border-radius: 10px;
border: 1.5px solid var(--slate-200);
padding: 8px 16px;
font-size: 0.92rem;
font-weight: 500;
color: var(--slate-800);
background-color: var(--slate-50);
transition: var(--transition);
width: 100%;
}
.form-control-custom:focus {
outline: none;
border-color: var(--primary);
background-color: #ffffff;
box-shadow: 0 0 0 3px rgba(10, 75, 100, 0.15);
}
/* Premium File Upload Button */
.file-upload-wrapper {
position: relative;
margin-bottom: 15px;
}
.file-upload-btn-custom {
height: 46px;
border-radius: 10px;
border: 1.5px dashed var(--slate-300);
background-color: var(--slate-50);
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
font-size: 0.88rem;
font-weight: 600;
color: var(--slate-600);
cursor: pointer;
transition: var(--transition);
}
.file-upload-wrapper input[type="file"] {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
.file-upload-wrapper:hover .file-upload-btn-custom {
background-color: var(--primary-light);
border-color: var(--primary);
color: var(--primary);
}
.file-selected-indicator {
font-size: 0.72rem;
color: var(--success);
font-weight: 600;
margin-top: 4px;
display: none;
align-items: center;
gap: 4px;
}
/* Buttons styling */
.btn-custom {
padding: 10px 24px;
border-radius: 10px;
font-size: 0.92rem;
font-weight: 600;
transition: var(--transition);
display: inline-flex;
align-items: center;
gap: 8px;
border: none;
}
.btn-custom-primary {
background-color: var(--primary);
color: #ffffff;
box-shadow: 0 4px 10px rgba(10, 75, 100, 0.2);
}
.btn-custom-primary:hover {
background-color: var(--primary-hover);
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(10, 75, 100, 0.3);
color: #ffffff;
}
.btn-custom-secondary {
background-color: var(--slate-200);
color: var(--slate-700);
}
.btn-custom-secondary:hover {
background-color: var(--slate-300);
transform: translateY(-2px);
}
/* Table styles */
.table-responsive-custom {
border-radius: 12px;
overflow-x: auto;
border: 1px solid var(--slate-200);
}
.table-responsive-custom::-webkit-scrollbar {
height: 8px;
}
.table-responsive-custom::-webkit-scrollbar-track {
background: var(--slate-100);
border-radius: 10px;
}
.table-responsive-custom::-webkit-scrollbar-thumb {
background: var(--slate-300);
border-radius: 10px;
transition: var(--transition);
}
.table-responsive-custom::-webkit-scrollbar-thumb:hover {
background: var(--primary);
}
.table-custom {
margin-bottom: 0;
font-size: 0.88rem;
}
.table-custom th {
background-color: var(--primary) !important;
color: #ffffff !important;
font-family: 'Outfit', sans-serif;
font-weight: 600;
padding: 14px 16px;
border: none;
vertical-align: middle;
white-space: nowrap;
}
.table-custom td {
padding: 14px 16px;
vertical-align: middle;
border-bottom: 1px solid var(--slate-200);
white-space: nowrap;
color: var(--slate-800);
}
.table-custom tr:last-child td {
border-bottom: none;
}
.table-custom tbody tr:hover {
background-color: var(--slate-50) !important;
}
/* Action Buttons */
.action-btn-group {
display: flex;
gap: 6px;
}
.action-btn {
width: 32px;
height: 32px;
border-radius: 8px;
display: inline-flex;
align-items: center;
justify-content: center;
color: #ffffff;
text-decoration: none;
transition: var(--transition);
border: none;
font-size: 0.85rem;
}
.action-btn:hover {
transform: scale(1.1);
color: #ffffff;
}
.btn-view {
background-color: var(--primary);
}
.btn-edit {
background-color: var(--accent);
}
.btn-delete {
background-color: var(--danger);
}
/* Loader Overlay */
#loader-overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(6, 35, 46, 0.4);
display: none;
justify-content: center;
align-items: center;
z-index: 9999;
backdrop-filter: blur(4px);
}
.loader-spinner {
width: 55px;
height: 55px;
border: 5px solid rgba(255, 255, 255, 0.2);
border-top: 5px solid #ffffff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* Document download/view labels */
.doc-link {
font-size: 0.8rem;
color: var(--primary);
text-decoration: none;
font-weight: 600;
display: inline-flex;
align-items: center;
gap: 4px;
border: 1px solid var(--primary);
padding: 3px 8px;
border-radius: 6px;
background-color: #ffffff;
transition: var(--transition);
}
.doc-link:hover {
background-color: var(--primary);
color: #ffffff;
}
.doc-missing {
font-size: 0.78rem;
color: var(--slate-400);
font-style: italic;
}
.count-badge-btn {
background-color: #ffffff;
border: 1px solid var(--slate-300);
padding: 8px 16px;
border-radius: 12px;
font-size: 0.9rem;
font-weight: 600;
color: var(--primary);
cursor: pointer;
transition: var(--transition);
display: inline-flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.02);
text-decoration: none;
width: 100%;
}
.count-badge-btn:hover {
background-color: var(--primary-light);
border-color: var(--primary);
color: var(--primary);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(10, 75, 100, 0.08);
}
.count-badge-btn.active-card {
background-color: var(--primary-light) !important;
border: 2px solid var(--primary) !important;
box-shadow: 0 4px 12px rgba(10, 75, 100, 0.12) !important;
transform: translateY(-1px) scale(1.01);
}
.count-card-label {
font-family: 'Outfit', sans-serif;
font-size: 0.92rem;
font-weight: 700;
display: flex;
align-items: center;
gap: 8px;
}
.count-card-number {
background-color: var(--primary-light);
color: var(--primary);
padding: 2px 10px;
border-radius: 12px;
font-size: 0.82rem;
font-weight: 800;
transition: var(--transition);
}
.count-badge-btn:hover .count-card-number {
background-color: var(--primary);
color: #ffffff;
}
/* Pagination custom styles */
.pagination .page-link {
color: var(--primary);
border-color: var(--slate-200);
background-color: #ffffff;
transition: var(--transition);
}
.pagination .page-item.active .page-link {
background-color: var(--primary) !important;
border-color: var(--primary) !important;
color: #ffffff !important;
}
.pagination .page-link:hover {
background-color: var(--primary-light);
color: var(--primary);
border-color: var(--primary);
}
.pagination .page-item.disabled .page-link {
color: var(--slate-400);
background-color: var(--slate-50);
border-color: var(--slate-200);
}
/* Split-up compact table cells styling */
#panel-split-content table th,
#panel-split-content table td {
padding: 5px 8px !important;
font-size: 0.88rem !important;
}
/* Mobile and Tablet Responsiveness */
@media (max-width: 991px) {
.header {
flex-direction: column;
gap: 15px;
padding: 15px 20px;
align-items: center;
text-align: center;
}
.logo-left {
flex-direction: column;
gap: 5px;
text-align: center;
}
.logo-text h2 {
font-size: 0.95rem;
}
.header-title {
position: static;
transform: none;
margin: 10px 0;
}
.header-title h2 {
font-size: 1.15rem;
}
.user-menu {
flex-direction: row;
justify-content: center;
width: 100%;
gap: 10px;
}
.user-badge {
padding: 6px 12px;
font-size: 0.8rem;
}
.btn-logout {
padding: 6px 14px;
font-size: 0.8rem;
}
.top-navbar {
padding: 10px 15px;
overflow-x: auto;
display: block;
}
.nav-pills-container {
display: inline-flex;
flex-wrap: nowrap;
padding-bottom: 5px;
width: max-content;
}
.nav-pill-btn {
padding: 6px 14px;
font-size: 0.8rem;
white-space: nowrap;
}
}
@media (max-width: 767px) {
.app-content {
padding: 12px;
}
.card-custom {
padding: 15px;
border-radius: 12px;
margin-bottom: 15px;
}
.section-header-title {
font-size: 1.2rem;
margin-bottom: 15px;
flex-direction: column;
align-items: flex-start;
gap: 5px;
}
.count-badge-btn {
padding: 10px 14px;
}
.count-card-label {
font-size: 0.82rem;
}
.count-card-number {
padding: 1px 8px;
font-size: 0.78rem;
}
/* Table filters grid layout */
.view-panel .card-custom .d-flex.gap-2.align-items-center.flex-wrap {
width: 100%;
display: grid !important;
grid-template-columns: 1fr 1fr;
gap: 8px !important;
}
.view-panel .card-custom .d-flex.gap-2.align-items-center.flex-wrap select,
.view-panel .card-custom .d-flex.gap-2.align-items-center.flex-wrap input,
.view-panel .card-custom .d-flex.gap-2.align-items-center.flex-wrap button {
width: 100% !important;
height: 38px !important;
font-size: 0.85rem !important;
margin: 0 !important;
}
.view-panel .card-custom .d-flex.gap-2.align-items-center.flex-wrap input {
grid-column: span 2;
}
.view-panel .card-custom .d-flex.gap-2.align-items-center.flex-wrap button {
grid-column: span 2;
}
/* Split up report filters stack */
.bg-light.p-2.rounded.d-flex {
display: grid !important;
grid-template-columns: 1fr;
gap: 10px !important;
width: 100%;
}
.bg-light.p-2.rounded.d-flex .d-flex {
width: 100%;
justify-content: space-between;
}
.bg-light.p-2.rounded.d-flex select {
width: 70% !important;
}
/* Pagination stacked layout */
.mt-4.flex-wrap {
flex-direction: column !important;
align-items: center !important;
text-align: center;
gap: 15px !important;
}
}
</style>
</head>
<body>
<!-- Loader Overlay -->
<div id="loader-overlay">
<div class="loader-spinner"></div>
</div>
<!-- Header block containing title & logo -->
<div class="header">
<div class="logo-left">
<img th:src="@{/assets/header-images/gcc1.png}" alt="Greater Chennai Corporation Logo">
<div class="logo-text">
<h2>GREATER</h2>
<h2>CHENNAI</h2>
<h2>CORPORATION</h2>
<p>பெருநகர சென்னை மாநகராட்சி</p>
</div>
</div>
<div class="header-title">
<h2>Adjustment File Management System</h2>
</div>
<div class="user-menu" style="display: flex; align-items: center; gap: 15px;">
<!-- Latest Update Badge -->
<div id="latest-update-badge" class="d-flex align-items-center gap-2 p-2 px-3 rounded text-white"
style="display: none !important; background-color: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); font-size: 0.8rem; font-family: 'Outfit', sans-serif; cursor: pointer; transition: all 0.3s;"
onclick="openLatestUpdatesModal()">
<i class="fa-solid fa-bell text-warning fa-bounce animate-bounce"></i>
<div>
Latest Update: <span class="fw-bold text-warning" id="latest-update-count">0</span> File(s) on
<span class="fw-semibold" id="latest-update-time">-</span>
</div>
</div>
<span class="user-badge">
<i class="fa-solid fa-user-shield"></i>
<span th:text="${usrName}">Guest</span>
</span>
<a th:href="@{/gccadjustmentmanagement/logout}" class="btn-logout">
<i class="fa-solid fa-right-from-bracket"></i> Logout
</a>
</div>
</div>
<!-- Top navigation tabs -->
<div class="top-navbar">
<div class="nav-pills-container">
<button class="nav-pill-btn active" id="tab-counts" onclick="switchView('counts')">
<i class="fa-solid fa-chart-simple"></i> Counts Dashboard
</button>
<button class="nav-pill-btn" id="tab-reg" onclick="openRegistrationModal()">
<i class="fa-solid fa-file-signature"></i> New File Registration
</button>
<button class="nav-pill-btn" id="tab-list" onclick="switchView('list')">
<i class="fa-solid fa-folder-open"></i> Existing File Details
</button>
<!-- <button class="nav-pill-btn" id="tab-oc" onclick="switchView('oc')">
<i class="fa-solid fa-copy"></i> OC Copies Details
</button> -->
<button class="nav-pill-btn" id="tab-ejv" onclick="switchView('ejv')">
<i class="fa-solid fa-file-pen"></i> Update EJV Number
</button>
</div>
<div class="d-flex align-items-center gap-3" style="display: none !important;">
<!-- Financial Year selector -->
<label for="filter-fin-year" class="fw-bold text-secondary mb-0"
style="font-size: 0.85rem; text-transform: uppercase;">Financial Year :</label>
<select id="filter-fin-year" class="form-control-custom"
style="width: 150px; height: 38px; padding: 4px 12px;" onchange="loadRecordsData()">
<option value="All">All</option>
</select>
<button class="btn btn-primary btn-sm rounded-pill px-3 py-1.5 fw-semibold"
onclick="loadRecordsData()">Apply</button>
</div>
</div>
<div class="app-content">
<div class="row">
<!-- Main Panel (Centered Full Width) -->
<div class="col-12" id="main-content-col">
<!-- 0. VIEW PANEL: COUNTS DASHBOARD -->
<div class="view-panel active" id="panel-counts">
<div class="card-custom py-2 mb-2">
<div class="d-flex justify-content-between align-items-center px-4 mb-2">
<div style="width: 80px;"></div> <!-- Spacer to balance the right button -->
<div class="section-header-title mb-0 text-center flex-grow-1">
<i class="fa-solid fa-chart-simple text-primary"></i> Adjustment Summary Counts
</div>
<button class="btn btn-sm btn-outline-secondary rounded-pill px-3 py-1 fw-semibold"
style="font-size: 0.8rem;" onclick="resetDashboardFilters()">
<i class="fa-solid fa-arrow-rotate-left me-1"></i> Reset
</button>
</div>
<div class="row g-2 justify-content-center mt-1" id="dashboard-counts-list"
style="max-width: 1200px; margin: 0 auto;">
<!-- Dynamic Counts loaded here -->
</div>
</div>
<!-- Split-up & Details Report inside Counts Dashboard -->
<div class="card-custom p-3">
<div
class="d-flex justify-content-between align-items-center mb-2 pb-1 border-bottom flex-wrap gap-3">
<h4 class="text-primary fw-bold mb-0 text-center flex-grow-1"
id="dash-report-title-container"
style="font-family: 'Outfit', sans-serif; font-size: 1.2rem; display: flex; align-items: center; justify-content: center; gap: 8px;">
<i id="dash-report-title-icon" class="fa-solid fa-folder-open text-primary"></i>
<span id="dash-report-title-text">Existing Adjustment Files</span>
<span id="split-filter-indicator" class="badge bg-secondary ms-2"
style="font-size: 0.75rem; vertical-align: middle;">All Files</span>
</h4>
<div class="d-flex align-items-center gap-3">
<div class="btn-group" role="group" aria-label="Dashboard Report Toggle">
<input type="radio" class="btn-check" name="dash-view-mode" id="mode-details"
autocomplete="off" checked onclick="setDashboardViewMode('details')">
<label class="btn btn-outline-primary btn-sm px-3 rounded-start-pill"
for="mode-details">
<i class="fa-solid fa-list me-1"></i> File Details
</label>
<input type="radio" class="btn-check" name="dash-view-mode" id="mode-split"
autocomplete="off" onclick="setDashboardViewMode('split')">
<label class="btn btn-outline-primary btn-sm px-3 rounded-end-pill"
for="mode-split">
<i class="fa-solid fa-table-list me-1"></i> Split-up
</label>
</div>
<button class="btn btn-sm btn-success px-4 py-2 rounded-pill fw-semibold"
id="btn-dash-export" onclick="exportDashboardData()">
<i class="fa-solid fa-file-excel me-1"></i> Export Excel
</button>
</div>
</div>
<!-- Dropdown filters for split-up report / details -->
<div class="d-flex align-items-center gap-3 mb-3 flex-wrap bg-light p-2 rounded">
<div class="d-flex align-items-center gap-2">
<label for="split-filter-vendor" class="fw-bold text-secondary mb-0"
style="font-size: 0.85rem;">Vendor:</label>
<select id="split-filter-vendor" class="form-control-custom"
style="width: 200px; height: 36px; padding: 2px 10px;"
onchange="onDashboardFilterChange()">
<option value="All">All Vendors</option>
</select>
</div>
<div class="d-flex align-items-center gap-2">
<label for="split-filter-advance-type" class="fw-bold text-secondary mb-0"
style="font-size: 0.85rem;">Advance Type:</label>
<select id="split-filter-advance-type" class="form-control-custom"
style="width: 200px; height: 36px; padding: 2px 10px;"
onchange="onDashboardFilterChange()">
<option value="All">All Advance Types</option>
</select>
</div>
<div class="d-flex align-items-center gap-2 ms-auto" id="dash-search-container">
<input type="text" id="dash-table-search" class="form-control-custom"
style="width: 220px; height: 36px; padding: 2px 10px;"
placeholder="Search files/vendors..." oninput="renderDashboardDetails()">
</div>
</div>
<!-- Details Table Mode -->
<div id="panel-details-content">
<div class="table-responsive table-responsive-custom">
<table class="table table-custom text-nowrap align-middle" id="dash-records-table">
<thead>
<tr>
<th class="text-center">S.No</th>
<th class="text-center" style="cursor: pointer;"
onclick="toggleDashSort('fin_year')">
Fin Year <span id="dash-sort-icon-fin_year"><i
class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
</th>
<th>File Num</th>
<th class="text-center">Advance Type</th>
<!-- <th class="text-center">Fund Type</th> -->
<!-- <th class="text-center">A/C Code</th> -->
<th class="text-center">File Available</th>
<th class="text-end" style="cursor: pointer;"
onclick="toggleDashSort('bal_amt')">
Bal Amount <span id="dash-sort-icon-bal_amt"><i
class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
</th>
<th class="text-end" style="cursor: pointer;"
onclick="toggleDashSort('pi_amt')">
PI Amount <span id="dash-sort-icon-pi_amt"><i
class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
</th>
<th class="text-end" style="cursor: pointer;"
onclick="toggleDashSort('fi_amt')">
FI Amount <span id="dash-sort-icon-fi_amt"><i
class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
</th>
<!-- <th>EJV Number</th> -->
<th
style="white-space: normal !important; min-width: 200px; max-width: 350px;">
Remarks</th>
<th style="cursor: pointer;" onclick="toggleDashSort('cdate')">
Created Date <span id="dash-sort-icon-cdate"><i
class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
</th>
<th style="cursor: pointer;" onclick="toggleDashSort('updated_date')">
Last Updated <span id="dash-sort-icon-updated_date"><i
class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
</th>
<th>Updated By</th>
<th class="text-center">Actions</th>
</tr>
</thead>
<tbody id="dash-records-table-body">
<!-- Loaded dynamically -->
</tbody>
</table>
</div>
<!-- Dashboard Pagination Controls -->
<div class="d-flex justify-content-between align-items-center mt-4 flex-wrap gap-3">
<div class="d-flex align-items-center gap-2">
<span class="text-muted" style="font-size: 0.9rem;">Show</span>
<select id="dash-pagination-page-size" class="form-control-custom"
style="width: 80px; height: 38px; padding: 4px 8px; font-size: 0.9rem;"
onchange="changeDashPageSize()">
<option value="15">15</option>
<option value="30">30</option>
<option value="50">50</option>
<option value="All">All</option>
</select>
<span class="text-muted" style="font-size: 0.9rem;">entries</span>
</div>
<div class="text-muted fw-semibold" id="dash-pagination-info"
style="font-size: 0.9rem;">
Showing 0 to 0 of 0 entries
</div>
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm mb-0" id="dash-pagination-buttons">
<!-- Dynamic pagination links -->
</ul>
</nav>
</div>
</div>
<!-- Split-up Table Mode -->
<div id="panel-split-content" style="display: none;"></div>
</div>
</div>
<!-- 2. VIEW PANEL: EXISTING FILES LIST -->
<div class="view-panel" id="panel-list">
<div class="card-custom">
<div class="d-flex justify-content-between align-items-center mb-4 flex-wrap gap-3">
<div class="section-header-title mb-0">
<i class="fa-solid fa-folder-open text-primary"></i> <span id="list-title">Existing
Adjustment Files</span>
</div>
<div class="d-flex gap-2 align-items-center flex-wrap">
<select id="filter-vendor" class="form-control-custom"
style="width: 180px; height: 40px;" onchange="filterRecordsList()">
<option value="All">All Vendors</option>
</select>
<select id="filter-adjustment-status" class="form-control-custom"
style="width: 180px; height: 40px;" onchange="filterRecordsList()">
<option value="All">All Statuses</option>
<option value="Adjusted">Adjusted Only</option>
<option value="Pending">Pending/Not Adjusted</option>
</select>
<select id="filter-advance-type" class="form-control-custom"
style="width: 180px; height: 40px;" onchange="filterRecordsList()">
<option value="All">All Advance Types</option>
</select>
<select id="filter-fund-type" class="form-control-custom"
style="width: 180px; height: 40px;" onchange="filterRecordsList()">
<option value="All">All Fund Types</option>
</select>
<select id="filter-file-available" class="form-control-custom"
style="width: 180px; height: 40px;" onchange="filterRecordsList()">
<option value="All">All Availability</option>
<option value="Available">Available Files Only</option>
<option value="NotAvailable">Not Available Files Only</option>
</select>
<input type="text" id="table-search" class="form-control-custom"
style="width: 220px; height: 40px;" placeholder="Search files/vendors..."
oninput="filterRecordsList()">
<button class="btn-custom btn-custom-primary py-2" onclick="exportToExcel()">
<i class="fa-solid fa-file-excel"></i> Export Excel
</button>
</div>
</div>
<!-- Data Table -->
<div class="table-responsive table-responsive-custom">
<table class="table table-custom text-nowrap align-middle" id="main-records-table">
<thead>
<tr>
<th class="text-center">S.No</th>
<!-- <th>Vendor Name</th> -->
<th class="text-center" style="cursor: pointer;"
onclick="toggleSort('fin_year')">
Fin Year <span id="sort-icon-fin_year"><i
class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
</th>
<th>File Num</th>
<th class="text-center">Advance Type</th>
<th class="text-center">Fund Type</th>
<th class="text-center">A/C Code</th>
<th class="text-center">File Available</th>
<th class="text-end" style="cursor: pointer;" onclick="toggleSort('bal_amt')">
Bal Amount <span id="sort-icon-bal_amt"><i
class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
</th>
<th class="text-end" style="cursor: pointer;" onclick="toggleSort('pi_amt')">
PI Amount <span id="sort-icon-pi_amt"><i
class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
</th>
<th class="text-end" style="cursor: pointer;" onclick="toggleSort('fi_amt')">
FI Amount <span id="sort-icon-fi_amt"><i
class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
</th>
<th>EJV Number</th>
<th style="cursor: pointer;" onclick="toggleSort('cdate')">
Created Date <span id="sort-icon-cdate"><i
class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
</th>
<th style="cursor: pointer;" onclick="toggleSort('updated_date')">
Last Updated <span id="sort-icon-updated_date"><i
class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
</th>
<th>Updated By</th>
<th class="text-center">Actions</th>
</tr>
</thead>
<tbody id="records-table-body">
<!-- Loaded dynamically -->
</tbody>
</table>
</div>
<!-- Pagination Controls -->
<div class="d-flex justify-content-between align-items-center mt-4 flex-wrap gap-3">
<div class="d-flex align-items-center gap-2">
<span class="text-muted" style="font-size: 0.9rem;">Show</span>
<select id="pagination-page-size" class="form-control-custom"
style="width: 80px; height: 38px; padding: 4px 8px; font-size: 0.9rem;"
onchange="changePageSize()">
<option value="15">15</option>
<option value="30">30</option>
<option value="50">50</option>
<option value="All">All</option>
</select>
<span class="text-muted" style="font-size: 0.9rem;">entries</span>
</div>
<div class="text-muted fw-semibold" id="pagination-info" style="font-size: 0.9rem;">
Showing 0 to 0 of 0 entries
</div>
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm mb-0" id="pagination-buttons">
<!-- Dynamic pagination links -->
</ul>
</nav>
</div>
</div>
</div>
<!-- 3. VIEW PANEL: ELCOT OC COPIES VIEW -->
<div class="view-panel" id="panel-oc">
<div class="card-custom">
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="section-header-title mb-0">
<i class="fa-solid fa-copy text-primary"></i> Elcot OC Files Registry
</div>
<div class="d-flex gap-2">
<input type="text" id="oc-table-search" class="form-control-custom"
style="width: 250px; height: 40px;" placeholder="Search ELCOT copies..."
oninput="filterOcList()">
</div>
</div>
<!-- OC Copies Data Table -->
<div class="table-responsive table-responsive-custom">
<table class="table table-custom text-nowrap align-middle">
<thead>
<tr>
<th>ID</th>
<th>Vendor</th>
<th>Fin Year</th>
<th>File Num</th>
<th>Original File</th>
<th>PO File</th>
<th>DC File</th>
<th>Inward File</th>
<th>Stock File</th>
<th>Temp Bill</th>
<th>RTGS File</th>
<th>EJV File</th>
<th>Final AS File</th>
</tr>
</thead>
<tbody id="oc-table-body">
<!-- Loaded dynamically -->
</tbody>
</table>
</div>
</div>
</div>
<!-- 4. VIEW PANEL: UPDATE EJV NUMBER -->
<div class="view-panel" id="panel-ejv">
<div class="card-custom p-4">
<div class="d-flex justify-content-between align-items-center mb-4 pb-2 border-bottom">
<button class="btn btn-outline-secondary btn-sm px-3 rounded-pill"
onclick="switchView('list')">
<i class="fa-solid fa-arrow-left"></i> Back
</button>
<h4 class="text-primary fw-bold mb-0 text-center flex-grow-1"
style="font-family: 'Outfit', sans-serif;">Update EJV Number</h4>
<div style="width: 80px;"></div> <!-- Spacer for centering -->
</div>
<form id="ejv-update-form" onsubmit="submitEJVForm(event)" enctype="multipart/form-data">
<div class="row g-4 justify-content-center">
<!-- File Number Selector -->
<div class="col-md-5">
<label for="ejv-file-num-input" class="form-label fw-bold">Select File Number :
*</label>
<input type="text" id="ejv-file-num-input" class="form-control-custom"
list="file-numbers-list" placeholder="Type or select a file number" required
oninput="handleEJVFileNumberInput()">
<datalist id="file-numbers-list"></datalist>
<input type="hidden" id="ejv-file-id" name="id">
</div>
<!-- Adjustment Order Uploaded Status -->
<div class="col-md-5">
<label for="ejv-adj-order-status" class="form-label fw-bold">Adjustment Order
Uploaded :</label>
<input type="text" id="ejv-adj-order-status" class="form-control-custom" readonly
disabled value="No">
</div>
<!-- EJV Number File -->
<div class="col-md-5">
<label class="form-label fw-bold">EJV Number File : *</label>
<div class="file-upload-wrapper">
<div class="file-upload-btn-custom">
<i class="fa-solid fa-cloud-arrow-up"></i> Choose File
</div>
<input type="file" id="ejv-num-file" name="ejvFile" accept=".pdf"
onchange="indicateFileSelected(this)" required disabled>
<div class="file-selected-indicator"><i class="fa-solid fa-check"></i>
<span></span>
</div>
</div>
</div>
<!-- EJV Number -->
<div class="col-md-5">
<label for="ejv-number-input" class="form-label fw-bold">EJV Number : *</label>
<input type="text" id="ejv-number-input" name="ejv_num" class="form-control-custom"
placeholder="Enter EJV Number" required disabled>
</div>
<!-- Submit Button -->
<div class="col-12 text-center mt-4">
<button type="submit" class="btn-custom btn-custom-primary px-5 py-2.5">
<i class="fa-solid fa-paper-plane me-2"></i>Submit
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- Modal: New File Registration / Edit File -->
<div class="modal fade" id="registrationModal" tabindex="-1" aria-labelledby="registrationModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content" style="border-radius: 16px;">
<div class="modal-header bg-dark text-white" style="border-radius: 16px 16px 0 0;">
<h5 class="modal-title" id="registrationModalLabel">
<i class="fa-solid fa-file-signature text-primary"></i> <span id="registration-title">New File
Registration</span>
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body p-4">
<form id="adjustment-registration-form" onsubmit="submitRegistrationForm(event)"
enctype="multipart/form-data">
<!-- Hidden ID input for editing -->
<input type="hidden" id="form-id" name="id">
<div class="row g-4">
<!-- File Number -->
<div class="col-md-6">
<label for="form-file-data-num" class="form-label">File Number : *</label>
<input type="text" id="form-file-data-num" name="file_data_num"
class="form-control-custom" placeholder="Enter File Number" required>
</div>
<!-- Financial Year -->
<div class="col-md-6" id="form-fin-year-container">
<label for="form-fin-year" class="form-label">Financial Year : *</label>
<select id="form-fin-year" name="fin_year" class="form-control-custom" required>
<option value="">- Select Year -</option>
</select>
</div>
<!-- Subject -->
<div class="col-12">
<label for="form-subject" class="form-label">Subject :</label>
<input type="text" id="form-subject" name="subject" class="form-control-custom"
placeholder="Enter Subject">
</div>
<!-- AS File -->
<div class="col-md-6">
<label class="form-label">AS :</label>
<div class="file-upload-wrapper">
<div class="file-upload-btn-custom">
<i class="fa-solid fa-cloud-arrow-up"></i> Choose File
</div>
<input type="file" id="form-as-file" name="asFile" accept=".pdf"
onchange="indicateFileSelected(this)">
<div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
</div>
</div>
</div>
<!-- AS Signed Date -->
<div class="col-md-6">
<label for="form-as-date" class="form-label">AS Signed Date :</label>
<input type="date" id="form-as-date" name="as_file_gen_date"
class="form-control-custom">
</div>
<!-- Proforma Invoice File -->
<div class="col-md-6">
<label class="form-label">Proforma Invoice :</label>
<div class="file-upload-wrapper">
<div class="file-upload-btn-custom">
<i class="fa-solid fa-file-invoice"></i> Choose File
</div>
<input type="file" id="form-pi-file" name="piFile" accept=".pdf"
onchange="indicateFileSelected(this)">
<div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
</div>
</div>
</div>
<!-- Proforma Invoice Amount -->
<div class="col-md-6">
<label for="form-pi-amt" class="form-label">Proforma Invoice Amount :</label>
<input type="number" step="0.01" id="form-pi-amt" name="pi_amt"
class="form-control-custom" placeholder="Enter PI amount"
oninput="calculateBalance()">
</div>
<!-- Proforma Invoice Generated Date -->
<div class="col-md-6">
<label for="form-pi-gen-date" class="form-label">Proforma Invoice Generated Date
:</label>
<input type="date" id="form-pi-gen-date" name="pi_file_gen_date"
class="form-control-custom">
</div>
<!-- Advance Type -->
<div class="col-md-6">
<label for="form-adv-type" class="form-label">Advance Type : *</label>
<select id="form-adv-type" name="adv_type_id" class="form-control-custom" required
onchange="toggleVendorNameVisibility()">
<option value="">- Select Type -</option>
</select>
</div>
<div class="col-md-6" id="vendor-name-container" style="display: none;">
<label for="form-vend-name" class="form-label">Vendor Name *</label>
<input type="text" id="form-vend-name" name="vend_name" class="form-control-custom"
placeholder="Enter Vendor Name">
</div>
<!-- Fund Type -->
<div class="col-md-6">
<label for="form-fund-type" class="form-label">Fund : *</label>
<select id="form-fund-type" name="fund_type_id" class="form-control-custom" required>
<option value="">-- Select Fund --</option>
</select>
</div>
<!-- A/C Code -->
<div class="col-md-6">
<label for="form-ac-code" class="form-label">A/C Code :</label>
<input type="text" id="form-ac-code" name="ac_code" class="form-control-custom"
placeholder="Enter A/C Code">
</div>
<!-- Has File Available -->
<div class="col-md-6">
<label for="form-file-available-status" class="form-label">Has File Available :</label>
<select id="form-file-available-status" name="file_available_status"
class="form-control-custom">
<option value="">- Select -</option>
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
</div>
<!-- RTGS File -->
<div class="col-md-6 edit-only-field">
<label class="form-label">RTGS File :</label>
<div class="file-upload-wrapper">
<div class="file-upload-btn-custom">
<i class="fa-solid fa-cloud-arrow-up"></i> Choose File
</div>
<input type="file" id="form-rtgs-file" name="rtgsFile" accept=".pdf"
onchange="indicateFileSelected(this)">
<div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
</div>
</div>
</div>
<!-- RTGS Upload Date -->
<div class="col-md-6 edit-only-field">
<label for="form-rtgs-date" class="form-label">RTGS Upload Date :</label>
<input type="date" id="form-rtgs-date" name="rtgs_date" class="form-control-custom">
</div>
<!-- Purchase Order File -->
<div class="col-md-6 edit-only-field">
<label class="form-label">Purchase Order File :</label>
<div class="file-upload-wrapper">
<div class="file-upload-btn-custom">
<i class="fa-solid fa-cloud-arrow-up"></i> Choose File
</div>
<input type="file" id="form-po-file" name="poFile" accept=".pdf"
onchange="indicateFileSelected(this)">
<div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
</div>
</div>
</div>
<!-- Delivery Challan File -->
<div class="col-md-6 edit-only-field">
<label class="form-label">Delivery Challan File :</label>
<div class="file-upload-wrapper">
<div class="file-upload-btn-custom">
<i class="fa-solid fa-cloud-arrow-up"></i> Choose File
</div>
<input type="file" id="form-dc-file" name="dcFile" accept=".pdf"
onchange="indicateFileSelected(this)">
<div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
</div>
</div>
</div>
<!-- DC Completed Date -->
<div class="col-md-6 edit-only-field">
<label for="form-dc-completed-date" class="form-label">DC Completed Date :</label>
<input type="date" id="form-dc-completed-date" name="dc_completed_date"
class="form-control-custom">
</div>
<!-- Stock Register Copy File -->
<div class="col-md-6 edit-only-field">
<label class="form-label">Stock Register Copy File :</label>
<div class="file-upload-wrapper">
<div class="file-upload-btn-custom">
<i class="fa-solid fa-cloud-arrow-up"></i> Choose File
</div>
<input type="file" id="form-sr-file" name="srFile" accept=".pdf"
onchange="indicateFileSelected(this)">
<div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
</div>
</div>
</div>
<!-- Installation Report File -->
<div class="col-md-6 edit-only-field">
<label class="form-label">Installation Report File :</label>
<div class="file-upload-wrapper">
<div class="file-upload-btn-custom">
<i class="fa-solid fa-cloud-arrow-up"></i> Choose File
</div>
<input type="file" id="form-ir-file" name="irFile" accept=".pdf"
onchange="indicateFileSelected(this)">
<div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
</div>
</div>
</div>
<!-- Final Invoice File -->
<div class="col-md-6 edit-only-field">
<label class="form-label">Final Invoice File :</label>
<div class="file-upload-wrapper">
<div class="file-upload-btn-custom">
<i class="fa-solid fa-cloud-arrow-up"></i> Choose File
</div>
<input type="file" id="form-fi-file" name="fiFile" accept=".pdf"
onchange="indicateFileSelected(this)">
<div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
</div>
</div>
</div>
<!-- Final Invoice Amount -->
<div class="col-md-6 edit-only-field">
<label for="form-fi-amt" class="form-label">Final Invoice Amount :</label>
<input type="number" step="0.01" id="form-fi-amt" name="fi_amt"
class="form-control-custom" placeholder="Enter FI amount"
oninput="calculateBalance()">
</div>
<!-- Final Invoice Generated Date -->
<div class="col-md-6 edit-only-field">
<label for="form-fi-gen-date" class="form-label">Final Invoice Generated Date :</label>
<input type="date" id="form-fi-gen-date" name="fi_file_gen_date"
class="form-control-custom">
</div>
<!-- Balance Amount -->
<div class="col-md-6 edit-only-field">
<label for="form-bal-amt" class="form-label">Balance Amount :</label>
<input type="number" step="0.01" id="form-bal-amt" name="bal_amt"
class="form-control-custom" placeholder="0" readonly>
</div>
<!-- Adjustment Order File -->
<div class="col-md-6 edit-only-field">
<label class="form-label">Adjustment Order :</label>
<div class="file-upload-wrapper">
<div class="file-upload-btn-custom">
<i class="fa-solid fa-cloud-arrow-up"></i> Choose File
</div>
<input type="file" id="form-as-final-file" name="asFinalFile" accept=".pdf"
onchange="indicateFileSelected(this)">
<div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
</div>
</div>
</div>
<!-- Adjustment Order Signed Date -->
<div class="col-md-6 edit-only-field">
<label for="form-as-final-date" class="form-label">Adjustment Order Signed Date
:</label>
<input type="date" id="form-as-final-date" name="as_final_file_gen_date"
class="form-control-custom">
</div>
<!-- Treasury Challan File -->
<div class="col-md-6 edit-only-field">
<label class="form-label">Treasury Challan :</label>
<div class="file-upload-wrapper">
<div class="file-upload-btn-custom">
<i class="fa-solid fa-cloud-arrow-up"></i> Choose File
</div>
<input type="file" id="form-tc-file" name="tcFile" accept=".pdf"
onchange="indicateFileSelected(this)">
<div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
</div>
</div>
</div>
<!-- Treasury Challan Signed Date -->
<div class="col-md-6 edit-only-field">
<label for="form-tc-date" class="form-label">Treasury Challan Signed Date :</label>
<input type="date" id="form-tc-date" name="tc_file_gen_date"
class="form-control-custom">
</div>
<!-- Remarks -->
<div class="col-12">
<label for="form-file-remark" class="form-label">Remarks :*</label>
<textarea id="form-file-remark" name="file_remark" class="form-control-custom"
style="height: 100px; padding: 12px;" placeholder="Remarks :*" required></textarea>
</div>
</div>
<div class="d-flex justify-content-center gap-3 mt-5">
<button type="submit" class="btn-custom btn-custom-primary px-5 py-2.5 fs-6"
style="background-color: #005a70 !important;">
<i class="fa-solid fa-circle-check"></i> Submit
</button>
<button type="button" class="btn-custom btn-custom-secondary px-5 py-2.5 fs-6"
data-bs-dismiss="modal">
<i class="fa-solid fa-circle-xmark"></i> Cancel
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- Modal: View Document Details / Checklist Timeline -->
<div class="modal fade" id="viewDetailsModal" tabindex="-1" aria-labelledby="viewDetailsModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content" style="border-radius: 16px;">
<div class="modal-header bg-dark text-white" style="border-radius: 16px 16px 0 0;">
<h5 class="modal-title" id="viewDetailsModalLabel"><i class="fa-solid fa-circle-info text-info"></i>
File Complete Lifecycle</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body p-4" id="modal-details-content">
<!-- Dynamic Checklist will load here -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Modal: Latest Updates Files List -->
<div class="modal fade" id="latestUpdatesModal" tabindex="-1" aria-labelledby="latestUpdatesModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content" style="border-radius: 16px;">
<div class="modal-header bg-dark text-white" style="border-radius: 16px 16px 0 0;">
<h5 class="modal-title" id="latestUpdatesModalLabel">
<i class="fa-solid fa-bell text-warning me-2"></i> <span id="latest-updates-modal-title">Latest
Updates</span>
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body p-4">
<div class="table-responsive table-responsive-custom">
<table class="table table-custom text-nowrap align-middle">
<thead>
<tr>
<th class="text-center">S.No</th>
<th class="text-center">Fin Year</th>
<th>File Num</th>
<th>Vendor Name</th>
<th class="text-center">Advance Type</th>
<th class="text-center">Update Status</th>
<th class="text-center">File Available</th>
<th class="text-end">Bal Amount</th>
<th>Remarks</th>
<th>createdDate</th>
<th>Updated By</th>
<th>Updated Date</th>
<th class="text-center">Actions</th>
</tr>
</thead>
<tbody id="latest-updates-modal-body">
<!-- Loaded dynamically -->
</tbody>
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Bootstrap Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<!-- SPA Application JS Logic -->
<script th:inline="javascript">
const contextPath = /*[[@{/}]]*/ '';
let allRecords = [];
let allRecordsIncludingDeleted = [];
let filteredRecords = [];
let activeVendorFilter = 'All';
let currentTab = 'counts';
let currentSplitFilter = 'All';
let detailsModalObj = null;
let registrationModalObj = null;
let currentPage = 1;
let pageSize = 15;
let sortField = 'fin_year';
let sortDirection = 'desc';
// Dashboard specific view state
let dashboardViewMode = 'details';
let dashCurrentPage = 1;
let dashPageSize = 15;
let dashSortField = 'fin_year';
let dashSortDirection = 'desc';
let dashFilteredRecords = [];
// On document load
document.addEventListener("DOMContentLoaded", () => {
populateFinancialYears();
loadInitialDropdowns();
loadRecordsData();
detailsModalObj = new bootstrap.Modal(document.getElementById('viewDetailsModal'));
registrationModalObj = new bootstrap.Modal(document.getElementById('registrationModal'));
});
function showLoader(show) {
document.getElementById('loader-overlay').style.display = show ? 'flex' : 'none';
}
// Generate Financial Years dynamically from 2005 - 2006 to current
function populateFinancialYears() {
const selectFilter = document.getElementById('filter-fin-year');
const selectForm = document.getElementById('form-fin-year');
if (selectFilter) {
selectFilter.innerHTML = '<option value="All">All</option>';
}
if (selectForm) {
selectForm.innerHTML = '<option value="">- Select Year -</option>';
}
const startYear = 2005;
const currentYear = new Date().getFullYear() + 2;
for (let y = startYear; y <= currentYear; y++) {
const fy = `${y} - ${y + 1}`;
if (selectFilter) {
const opt = document.createElement('option');
opt.value = fy;
opt.textContent = fy;
selectFilter.appendChild(opt);
}
if (selectForm) {
const opt = document.createElement('option');
opt.value = fy;
opt.textContent = fy;
selectForm.appendChild(opt);
}
}
}
// Toggle SPA tabs
function switchView(tabName) {
currentTab = tabName;
document.querySelectorAll('.nav-pill-btn').forEach(btn => btn.classList.remove('active'));
document.querySelectorAll('.view-panel').forEach(panel => panel.classList.remove('active'));
if (tabName === 'counts') {
document.getElementById('tab-counts').classList.add('active');
document.getElementById('panel-counts').classList.add('active');
renderCountsDashboard();
setDashboardViewMode(dashboardViewMode);
} else if (tabName === 'list') {
document.getElementById('tab-list').classList.add('active');
document.getElementById('panel-list').classList.add('active');
filterRecordsList();
} else if (tabName === 'oc') {
document.getElementById('tab-oc').classList.add('active');
document.getElementById('panel-oc').classList.add('active');
filterOcList();
} else if (tabName === 'ejv') {
document.getElementById('tab-ejv').classList.add('active');
document.getElementById('panel-ejv').classList.add('active');
resetEJVForm();
}
}
// File indicator selection text
function indicateFileSelected(input) {
const indicator = input.parentElement.querySelector('.file-selected-indicator');
if (indicator) {
if (input.files && input.files[0]) {
indicator.querySelector('span').textContent = input.files[0].name;
indicator.style.display = 'inline-flex';
} else {
indicator.style.display = 'none';
}
}
}
// Populate Select File Number for Update EJV
function populateEJVFileDropdown() {
const datalist = document.getElementById('file-numbers-list');
if (!datalist) return;
datalist.innerHTML = '';
const sortedRecords = [...allRecords].sort((a, b) => {
const numA = String(a.file_data_num || '').toLowerCase();
const numB = String(b.file_data_num || '').toLowerCase();
return numA.localeCompare(numB);
});
sortedRecords.forEach(r => {
if (r.file_data_num) {
const opt = document.createElement('option');
opt.value = r.file_data_num;
datalist.appendChild(opt);
}
});
}
// Handle File Number change in Update EJV view
function handleEJVFileNumberInput() {
const input = document.getElementById('ejv-file-num-input');
const hiddenId = document.getElementById('ejv-file-id');
const statusInput = document.getElementById('ejv-adj-order-status');
const fileInput = document.getElementById('ejv-num-file');
const numInput = document.getElementById('ejv-number-input');
if (!input || !hiddenId || !statusInput || !fileInput || !numInput) return;
const typedVal = input.value.trim().toLowerCase();
if (!typedVal) {
hiddenId.value = '';
statusInput.value = '';
fileInput.value = '';
fileInput.setAttribute('disabled', 'true');
fileInput.required = false;
numInput.value = '';
numInput.setAttribute('disabled', 'true');
const container = fileInput.closest('.file-upload-wrapper');
if (container) {
const ind = container.querySelector('.file-selected-indicator');
if (ind) ind.style.display = 'none';
}
return;
}
const r = allRecords.find(item => String(item.file_data_num || '').trim().toLowerCase() === typedVal);
if (!r) {
hiddenId.value = '';
statusInput.value = '';
fileInput.value = '';
fileInput.setAttribute('disabled', 'true');
fileInput.required = false;
numInput.value = '';
numInput.setAttribute('disabled', 'true');
const container = fileInput.closest('.file-upload-wrapper');
if (container) {
const ind = container.querySelector('.file-selected-indicator');
if (ind) ind.style.display = 'none';
}
return;
}
hiddenId.value = r.id;
// Check condition: as_final_file_gen_date and as_final_file_path is not null/empty/'-'
const finalPath = r.as_final_file_path;
const finalDate = r.as_final_file_gen_date;
const isFinalUploaded = finalPath && finalPath !== '-' && finalPath !== 'null' && String(finalPath).trim() !== '' &&
finalDate && finalDate !== '-' && finalDate !== 'null' && String(finalDate).trim() !== '';
if (isFinalUploaded) {
statusInput.value = 'Yes';
fileInput.removeAttribute('disabled');
numInput.removeAttribute('disabled');
numInput.value = (r.ejv_num && r.ejv_num !== '-') ? r.ejv_num : '';
const hasExistingFile = r.ejv_num_file_path && r.ejv_num_file_path !== '-' && r.ejv_num_file_path !== 'null';
fileInput.required = !hasExistingFile;
const container = fileInput.closest('.file-upload-wrapper');
if (container) {
const ind = container.querySelector('.file-selected-indicator');
if (ind) {
if (hasExistingFile) {
ind.style.display = 'inline-block';
const fileName = r.ejv_num_file_path.substring(r.ejv_num_file_path.lastIndexOf('/') + 1);
ind.querySelector('span').textContent = 'Existing: ' + fileName;
} else {
ind.style.display = 'none';
}
}
}
} else {
statusInput.value = 'No';
fileInput.value = '';
fileInput.setAttribute('disabled', 'true');
fileInput.required = false;
numInput.value = '';
numInput.setAttribute('disabled', 'true');
const container = fileInput.closest('.file-upload-wrapper');
if (container) {
const ind = container.querySelector('.file-selected-indicator');
if (ind) ind.style.display = 'none';
}
Swal.fire({
title: 'Upload Locked',
text: 'Adjustment Order (Final AS Copy) is not uploaded for this file. EJV upload is locked.',
icon: 'warning',
confirmButtonColor: '#0a4b64'
});
}
}
// Submit EJV Update Form
function submitEJVForm(event) {
event.preventDefault();
const fileSelect = document.getElementById('ejv-file-id');
const fileInput = document.getElementById('ejv-num-file');
const numInput = document.getElementById('ejv-number-input');
const statusInput = document.getElementById('ejv-adj-order-status');
if (!fileSelect || !fileSelect.value) {
Swal.fire({ title: 'Error', text: 'Please select a valid registered file number.', icon: 'error' });
return;
}
if (statusInput.value !== 'Yes') {
Swal.fire({ title: 'Locked', text: 'EJV upload is locked because the Adjustment Order is missing.', icon: 'warning' });
return;
}
const ejvNum = numInput.value.trim();
if (!ejvNum) {
Swal.fire({ title: 'Error', text: 'Please enter EJV Number.', icon: 'error' });
return;
}
const fileId = parseInt(fileSelect.value);
const r = allRecords.find(item => item.id === fileId);
const hasExistingFile = r && r.ejv_num_file_path && r.ejv_num_file_path !== '-' && r.ejv_num_file_path !== 'null';
if (!hasExistingFile && (!fileInput.files || fileInput.files.length === 0)) {
Swal.fire({ title: 'Error', text: 'Please choose an EJV file.', icon: 'error' });
return;
}
showLoader(true);
const form = document.getElementById('ejv-update-form');
const formData = new FormData(form);
const csrfMeta = document.querySelector("meta[name='_csrf']");
const csrfHeaderMeta = document.querySelector("meta[name='_csrf_header']");
const headers = {};
if (csrfMeta && csrfHeaderMeta) {
headers[csrfHeaderMeta.getAttribute("content")] = csrfMeta.getAttribute("content");
}
fetch(contextPath + 'gccadjustmentmanagement/api/update-ejv', {
method: 'POST',
headers: headers,
body: formData
})
.then(res => res.json())
.then(data => {
showLoader(false);
if (data.success) {
Swal.fire({
title: 'Success!',
text: data.message,
icon: 'success',
confirmButtonColor: '#0a4b64'
}).then(() => {
resetEJVForm();
loadRecordsData();
switchView('list');
});
} else {
Swal.fire({
title: 'Failed!',
text: data.error || 'Failed to save EJV details.',
icon: 'error',
confirmButtonColor: '#ef4444'
});
}
})
.catch(err => {
showLoader(false);
Swal.fire({
title: 'Error!',
text: 'An error occurred during submission: ' + err,
icon: 'error',
confirmButtonColor: '#ef4444'
});
});
}
// Reset EJV Form
function resetEJVForm() {
const form = document.getElementById('ejv-update-form');
if (form) form.reset();
const input = document.getElementById('ejv-file-num-input');
if (input) input.value = '';
const hiddenId = document.getElementById('ejv-file-id');
if (hiddenId) hiddenId.value = '';
handleEJVFileNumberInput();
}
// Fetch Dropdown options (Advance Types & Fund Types)
function loadInitialDropdowns() {
fetch(contextPath + 'gccadjustmentmanagement/api/advance-types')
.then(res => res.json())
.then(data => {
const sel = document.getElementById('form-adv-type');
sel.innerHTML = '<option value="">- Select Type -</option>';
const filSel = document.getElementById('filter-advance-type');
if (filSel) filSel.innerHTML = '<option value="All">All Advance Types</option>';
const splitFilSel = document.getElementById('split-filter-advance-type');
if (splitFilSel) splitFilSel.innerHTML = '<option value="All">All Advance Types</option>';
data.forEach(t => {
sel.innerHTML += `<option value="${t.id}">${t.adv_type}</option>`;
if (filSel) filSel.innerHTML += `<option value="${t.id}">${t.adv_type}</option>`;
if (splitFilSel) splitFilSel.innerHTML += `<option value="${t.id}">${t.adv_type}</option>`;
});
});
fetch(contextPath + 'gccadjustmentmanagement/api/fund-types')
.then(res => res.json())
.then(data => {
const sel = document.getElementById('form-fund-type');
sel.innerHTML = '<option value="">- Select Type -</option>';
const filSel = document.getElementById('filter-fund-type');
if (filSel) filSel.innerHTML = '<option value="All">All Fund Types</option>';
data.forEach(t => {
sel.innerHTML += `<option value="${t.id}">${t.fund_type}</option>`;
if (filSel) filSel.innerHTML += `<option value="${t.id}">${t.fund_type}</option>`;
});
});
}
// Populate the vendor dropdown filter dynamically
function populateVendorFilterDropdown() {
const select = document.getElementById('filter-vendor');
const splitSelect = document.getElementById('split-filter-vendor');
const curVal = select ? select.value : 'All';
const curSplitVal = splitSelect ? splitSelect.value : 'All';
if (select) select.innerHTML = '<option value="All">All Vendors</option>';
if (splitSelect) splitSelect.innerHTML = '<option value="All">All Vendors</option>';
const vendors = new Set();
allRecords.forEach(r => {
let v = String(r.vend_name || '').trim();
if (v !== '') vendors.add(v);
});
const sortedVendors = Array.from(vendors).sort();
sortedVendors.forEach(v => {
if (select) {
const opt = document.createElement('option');
opt.value = v;
opt.textContent = v;
select.appendChild(opt);
}
if (splitSelect) {
const opt = document.createElement('option');
opt.value = v;
opt.textContent = v;
splitSelect.appendChild(opt);
}
});
if (select && Array.from(select.options).some(opt => opt.value === curVal)) {
select.value = curVal;
}
if (splitSelect && Array.from(splitSelect.options).some(opt => opt.value === curSplitVal)) {
splitSelect.value = curSplitVal;
}
}
// Load all database files
function loadRecordsData() {
showLoader(true);
Promise.all([
fetch(contextPath + 'gccadjustmentmanagement/api/list').then(res => res.json()),
fetch(contextPath + 'gccadjustmentmanagement/api/list-all').then(res => res.json())
])
.then(([activeData, allData]) => {
allRecords = activeData;
allRecordsIncludingDeleted = allData;
populateVendorFilterDropdown();
populateEJVFileDropdown();
renderCountsDashboard();
updateLatestUpdateBadge();
renderSplitUpReport();
renderDashboardDetails();
filterRecordsList();
showLoader(false);
})
.catch(err => {
showLoader(false);
console.error("Error loading records:", err);
});
}
// Render dynamic counts badges in Dashboard Landing view
function renderCountsDashboard() {
const fyFilter = document.getElementById('filter-fin-year').value;
const cleanFy = str => String(str || '').replace(/\s+/g, '');
let records = allRecords;
if (fyFilter !== 'All') {
records = records.filter(r => cleanFy(r.fin_year) === cleanFy(fyFilter));
}
// Calculate vendor and adjustment status counts
const counts = {};
let total = 0;
let adjustedCount = 0;
let pendingCount = 0;
records.forEach(r => {
let v = String(r.vend_name || '').trim();
if (v === '') v = '-';
counts[v] = (counts[v] || 0) + 1;
total++;
// Check adjustment status
const ejvNum = String(r.ejv_num || '').trim();
const ejvPath = String(r.ejv_num_file_path || '').trim();
const isAdjusted = (ejvNum !== '' && ejvNum !== '-') || (ejvPath !== '' && ejvPath !== '-');
if (isAdjusted) {
adjustedCount++;
} else {
pendingCount++;
}
});
const sortedVendors = Object.keys(counts).sort();
const container = document.getElementById('dashboard-counts-list');
if (!container) return;
let html = '';
// Total Files count shows records.length (representing active database files only)
const displayTotal = records.length;
// 1. Total Files Button
html += `
<div class="col-lg-4 col-md-6 col-12">
<div class="count-badge-btn justify-content-between animate-fade-in ${currentSplitFilter === 'All' ? 'active-card' : ''}"
id="card-total-files" onclick="setSplitFilter('All')">
<span class="count-card-label"><i class="fa-solid fa-folder me-2 text-primary"></i> Total Files</span>
<span class="count-card-number text-white" style="background-color: var(--primary) !important;">${displayTotal}</span>
</div>
</div>
`;
// 1.1. Adjusted Files Button
html += `
<div class="col-lg-4 col-md-6 col-12">
<div class="count-badge-btn justify-content-between animate-fade-in ${currentSplitFilter === 'Adjusted' ? 'active-card' : ''}"
id="card-adjusted-files" onclick="setSplitFilter('Adjusted')">
<span class="count-card-label"><i class="fa-solid fa-circle-check me-2 text-success"></i> Adjusted Files</span>
<span class="count-card-number text-white" style="background-color: var(--success) !important;">${adjustedCount}</span>
</div>
</div>
`;
// 1.2. Pending / Not Adjusted Files Button
html += `
<div class="col-lg-4 col-md-6 col-12">
<div class="count-badge-btn justify-content-between animate-fade-in ${currentSplitFilter === 'Pending' ? 'active-card' : ''}"
id="card-pending-files" onclick="setSplitFilter('Pending')">
<span class="count-card-label"><i class="fa-solid fa-circle-xmark me-2 text-danger"></i> Pending / Not Adjusted</span>
<span class="count-card-number text-white" style="background-color: var(--danger) !important;">${pendingCount}</span>
</div>
</div>
`;
// Calculate Election and Not Available Files
let electionUploaded = records.filter(r => String(r.adv_type_id) === '9').length;
let notAvailableUploaded = records.filter(r => r.file_available_status === 'No').length;
// 1.3. Election Files
html += `
<div class="col-lg-4 col-md-6 col-12">
<div class="count-badge-btn justify-content-between animate-fade-in ${currentSplitFilter === 'Election' ? 'active-card' : ''}"
id="card-election-files" onclick="setSplitFilter('Election')">
<span class="count-card-label"><i class="fa-solid fa-square-poll-vertical me-2 text-warning"></i> Election Files</span>
<span class="count-card-number text-white" style="background-color: var(--warning) !important;">${electionUploaded}</span>
</div>
</div>
`;
// 1.4. Not Available Files
html += `
<div class="col-lg-4 col-md-6 col-12">
<div class="count-badge-btn justify-content-between animate-fade-in ${currentSplitFilter === 'NotAvailable' ? 'active-card' : ''}"
id="card-notavailable-files" onclick="setSplitFilter('NotAvailable')">
<span class="count-card-label"><i class="fa-solid fa-ban me-2 text-secondary"></i> Not Available Files</span>
<span class="count-card-number text-white" style="background-color: #64748b !important;">${notAvailableUploaded}</span>
</div>
</div>
`;
// 2. Vendor buttons (hidden as requested)
container.innerHTML = html;
}
function goToListViewWithFilter(vendor) {
const select = document.getElementById('filter-vendor');
if (select) {
if (vendor === 'All' || !Array.from(select.options).some(opt => opt.value === vendor)) {
select.value = 'All';
} else {
select.value = vendor;
}
}
const statusSelect = document.getElementById('filter-adjustment-status');
if (statusSelect) {
statusSelect.value = 'All';
}
const advSelect = document.getElementById('filter-advance-type');
if (advSelect) advSelect.value = 'All';
const fundSelect = document.getElementById('filter-fund-type');
if (fundSelect) fundSelect.value = 'All';
switchView('list');
}
function goToListViewWithStatusFilter(status) {
const statusSelect = document.getElementById('filter-adjustment-status');
if (statusSelect) {
statusSelect.value = status;
}
const vendorSelect = document.getElementById('filter-vendor');
if (vendorSelect) {
vendorSelect.value = 'All';
}
const advSelect = document.getElementById('filter-advance-type');
if (advSelect) advSelect.value = 'All';
const fundSelect = document.getElementById('filter-fund-type');
if (fundSelect) fundSelect.value = 'All';
switchView('list');
}
// Filter and display files list
function filterRecordsList(keepPage = false) {
if (!keepPage) {
currentPage = 1;
}
const fyFilter = document.getElementById('filter-fin-year').value;
const vendorFilter = document.getElementById('filter-vendor') ? document.getElementById('filter-vendor').value : 'All';
const searchVal = document.getElementById('table-search').value.toLowerCase().trim();
const cleanFy = str => String(str || '').replace(/\s+/g, '');
let records = allRecords;
// 1. FY Filter
if (fyFilter !== 'All') {
records = records.filter(r => cleanFy(r.fin_year) === cleanFy(fyFilter));
}
// 2. Vendor Dropdown Filter
if (vendorFilter !== 'All') {
records = records.filter(r => (r.vend_name || '').trim() === vendorFilter);
}
// 2.5. Adjustment Status Filter
const statusFilter = document.getElementById('filter-adjustment-status') ? document.getElementById('filter-adjustment-status').value : 'All';
if (statusFilter !== 'All') {
records = records.filter(r => {
const ejvNum = String(r.ejv_num || '').trim();
const ejvPath = String(r.ejv_num_file_path || '').trim();
const isAdjusted = (ejvNum !== '' && ejvNum !== '-') || (ejvPath !== '' && ejvPath !== '-');
return statusFilter === 'Adjusted' ? isAdjusted : !isAdjusted;
});
}
// 2.6. Advance Type Filter
const advTypeFilter = document.getElementById('filter-advance-type') ? document.getElementById('filter-advance-type').value : 'All';
if (advTypeFilter !== 'All') {
records = records.filter(r => String(r.adv_type_id || '').trim() === advTypeFilter);
}
// 2.7. Fund Type Filter
const fundTypeFilter = document.getElementById('filter-fund-type') ? document.getElementById('filter-fund-type').value : 'All';
if (fundTypeFilter !== 'All') {
records = records.filter(r => String(r.fund_type_id || '').trim() === fundTypeFilter);
}
// 2.8. File Available Filter
const availFilter = document.getElementById('filter-file-available') ? document.getElementById('filter-file-available').value : 'All';
if (availFilter !== 'All') {
records = records.filter(r => {
let isAvail = false;
if (r.file_available_status === 'Yes') {
isAvail = true;
} else if (r.file_available_status === 'No') {
isAvail = false;
} else {
// Check physical files
const paths = [
r.file_path, r.po_file_path, r.dc_file_path, r.ir_file_path,
r.sr_filepath, r.pi_file_path, r.fi_file_path, r.tc_file_path,
r.rtgs_file_path, r.ejv_num_file_path, r.as_file_path, r.as_final_file_path
];
isAvail = paths.some(p => p && p !== '-' && p !== 'null' && String(p).trim() !== '');
}
return availFilter === 'Available' ? isAvail : !isAvail;
});
}
// 3. Search input filter
if (searchVal !== '') {
records = records.filter(r =>
String(r.vend_name || '').toLowerCase().includes(searchVal) ||
String(r.file_data_num || '').toLowerCase().includes(searchVal) ||
String(r.subject || '').toLowerCase().includes(searchVal) ||
String(r.ejv_num || '').toLowerCase().includes(searchVal)
);
}
// 4. Sort records dynamically
records.sort((a, b) => {
let valA, valB;
if (sortField === 'fin_year') {
valA = String(a.fin_year || '').trim();
valB = String(b.fin_year || '').trim();
} else if (sortField === 'bal_amt') {
valA = Number(a.bal_amt || 0);
valB = Number(b.bal_amt || 0);
} else if (sortField === 'pi_amt') {
valA = Number(a.pi_amt || 0);
valB = Number(b.pi_amt || 0);
} else if (sortField === 'fi_amt') {
valA = Number(a.fi_amt || 0);
valB = Number(b.fi_amt || 0);
} else if (sortField === 'updated_date') {
valA = new Date(a.updated_date || a.cdate).getTime();
valB = new Date(b.updated_date || b.cdate).getTime();
} else {
valA = a.id;
valB = b.id;
}
if (valA === valB) return b.id - a.id; // secondary sort by ID descending
if (sortDirection === 'asc') {
if (typeof valA === 'string') {
return valA.localeCompare(valB);
}
return valA > valB ? 1 : -1;
} else {
if (typeof valA === 'string') {
return valB.localeCompare(valA);
}
return valA < valB ? 1 : -1;
}
});
// Store filtered records globally for Excel export
filteredRecords = records;
const totalRecords = records.length;
const startIndex = (currentPage - 1) * pageSize;
const endIndex = Math.min(startIndex + pageSize, totalRecords);
const paginatedRecords = records.slice(startIndex, endIndex);
if (currentTab === 'list') {
renderTableData(paginatedRecords, startIndex + 1);
renderPagination(totalRecords);
updateSortIcons();
} else if (currentTab === 'oc') {
// For OC Copy list, show records with ELCOT vendor primarily or filter ELCOT
const elcotRecords = records.filter(r => String(r.vend_name || '').toUpperCase().includes('ELCOT'));
renderOcTableData(elcotRecords);
}
}
function changePageSize() {
const val = document.getElementById('pagination-page-size').value;
if (val === 'All') {
pageSize = 999999;
} else {
pageSize = parseInt(val, 10);
}
currentPage = 1;
filterRecordsList(true);
}
function renderPagination(totalRecords) {
const info = document.getElementById('pagination-info');
const buttons = document.getElementById('pagination-buttons');
if (!info || !buttons) return;
const totalPages = Math.ceil(totalRecords / pageSize);
const startRange = totalRecords === 0 ? 0 : (currentPage - 1) * pageSize + 1;
const endRange = Math.min(currentPage * pageSize, totalRecords);
info.textContent = `Showing ${startRange} to ${endRange} of ${totalRecords} entries`;
buttons.innerHTML = '';
if (totalPages <= 1) {
return;
}
// Previous Button
const prevLi = document.createElement('li');
prevLi.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`;
prevLi.innerHTML = `<a class="page-link" href="javascript:void(0)" onclick="goToPage(${currentPage - 1})">&laquo;</a>`;
buttons.appendChild(prevLi);
// Page Numbers
const maxPageButtons = 5;
let startPage = Math.max(1, currentPage - 2);
let endPage = Math.min(totalPages, startPage + maxPageButtons - 1);
if (endPage - startPage < maxPageButtons - 1) {
startPage = Math.max(1, endPage - maxPageButtons + 1);
}
for (let p = startPage; p <= endPage; p++) {
const li = document.createElement('li');
li.className = `page-item ${currentPage === p ? 'active' : ''}`;
li.innerHTML = `<a class="page-link" href="javascript:void(0)" onclick="goToPage(${p})">${p}</a>`;
buttons.appendChild(li);
}
// Next Button
const nextLi = document.createElement('li');
nextLi.className = `page-item ${currentPage === totalPages ? 'disabled' : ''}`;
nextLi.innerHTML = `<a class="page-link" href="javascript:void(0)" onclick="goToPage(${currentPage + 1})">&raquo;</a>`;
buttons.appendChild(nextLi);
}
function goToPage(page) {
currentPage = page;
filterRecordsList(true);
}
function toggleSort(field) {
if (sortField === field) {
sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
} else {
sortField = field;
sortDirection = 'desc';
}
filterRecordsList(true);
}
function updateSortIcons() {
const fields = ['fin_year', 'bal_amt', 'pi_amt', 'fi_amt', 'cdate', 'updated_date'];
fields.forEach(f => {
const el = document.getElementById(`sort-icon-${f}`);
if (!el) return;
if (sortField === f) {
el.innerHTML = sortDirection === 'asc' ? '<i class="fa-solid fa-sort-up text-warning ms-1"></i>' : '<i class="fa-solid fa-sort-down text-warning ms-1"></i>';
} else {
el.innerHTML = '<i class="fa-solid fa-sort text-muted opacity-50 ms-1"></i>';
}
});
}
// Search in OC Copies Tab Specifically
function filterOcList() {
const searchVal = document.getElementById('oc-table-search').value.toLowerCase().trim();
let records = allRecords.filter(r => String(r.vend_name || '').toUpperCase().includes('ELCOT'));
const fyFilter = document.getElementById('filter-fin-year').value;
const cleanFy = str => String(str || '').replace(/\s+/g, '');
if (fyFilter !== 'All') {
records = records.filter(r => cleanFy(r.fin_year) === cleanFy(fyFilter));
}
if (searchVal !== '') {
records = records.filter(r =>
String(r.file_data_num || '').toLowerCase().includes(searchVal) ||
String(r.ejv_num || '').toLowerCase().includes(searchVal)
);
}
renderOcTableData(records);
}
// Format Date
function formatDate(dateStr) {
if (!dateStr) return '-';
try {
const d = new Date(dateStr);
if (isNaN(d.getTime())) return dateStr;
const dd = String(d.getDate()).padStart(2, '0');
const MM = String(d.getMonth() + 1).padStart(2, '0');
const yyyy = d.getFullYear();
return `${dd}/${MM}/${yyyy}`;
} catch (e) {
return dateStr;
}
}
// Format DateTime
function formatDateTime(dateTimeStr) {
if (!dateTimeStr) return '-';
try {
const d = new Date(dateTimeStr);
if (isNaN(d.getTime())) return dateTimeStr;
const dd = String(d.getDate()).padStart(2, '0');
const MM = String(d.getMonth() + 1).padStart(2, '0');
const yyyy = d.getFullYear();
const hh = String(d.getHours()).padStart(2, '0');
const mm = String(d.getMinutes()).padStart(2, '0');
return `${dd}/${MM}/${yyyy} ${hh}:${mm}`;
} catch (e) {
return dateTimeStr;
}
}
// Render main table rows
function renderTableData(records, startSNo = 1) {
const tbody = document.getElementById('records-table-body');
tbody.innerHTML = '';
if (records.length === 0) {
tbody.innerHTML = '<tr><td colspan="14" class="text-center text-muted">No files found matching criteria.</td></tr>';
return;
}
let currentSNo = startSNo;
records.forEach(r => {
const balAmt = r.bal_amt ? Number(r.bal_amt).toLocaleString('en-IN') : '0';
const piAmt = r.pi_amt ? Number(r.pi_amt).toLocaleString('en-IN') : '-';
const fiAmt = r.fi_amt ? Number(r.fi_amt).toLocaleString('en-IN') : '-';
tbody.innerHTML += `
<tr>
<td class="text-center">${currentSNo++}</td>
<td class="text-center">${escapeHtml(r.fin_year)}</td>
<td>${escapeHtml(r.file_data_num || '-')}</td>
<td class="text-center"><span class="badge bg-secondary">${escapeHtml(r.resolved_adv_type || '-')}</span></td>
<td class="text-center"><span class="badge bg-info text-dark">${escapeHtml(r.resolved_fund_type || '-')}</span></td>
<td class="text-center">${escapeHtml(r.ac_code || '-')}</td>
<td class="text-center">
${r.file_available_status === 'Yes'
? `<span class="badge bg-success-light text-success border border-success px-2 py-0.5">Yes</span>`
: r.file_available_status === 'No'
? `<span class="badge bg-danger-light text-danger border border-danger px-2 py-0.5">No</span>`
: '-'}
</td>
<td class="fw-semibold text-primary text-end">₹ ${balAmt}</td>
<td class="text-end">₹ ${piAmt}</td>
<td class="text-end">₹ ${fiAmt}</td>
<td>${escapeHtml(r.ejv_num || '-')}</td>
<td class="text-muted" style="font-size: 0.8rem;">${formatDateTime(r.cdate)}</td>
<td class="text-muted" style="font-size: 0.8rem;">${formatDateTime(r.updated_date)}</td>
<td>${escapeHtml(r.updated_by_name || r.updated_by || '-')}</td>
<td class="text-center">
<div class="action-btn-group justify-content-center">
<button class="action-btn btn-view" title="View Lifecycle" onclick="viewRecordDetails(${r.id})">
<i class="fa-solid fa-eye"></i>
</button>
<button class="action-btn btn-edit" title="Edit File" onclick="editRecord(${r.id})">
<i class="fa-solid fa-pen-to-square"></i>
</button>
<button class="action-btn btn-delete" title="Delete File" onclick="deleteRecord(${r.id})">
<i class="fa-solid fa-trash"></i>
</button>
</div>
</td>
</tr>
`;
});
}
// Render ELCOT OC Copies Table
function renderOcTableData(records) {
const tbody = document.getElementById('oc-table-body');
tbody.innerHTML = '';
if (records.length === 0) {
tbody.innerHTML = '<tr><td colspan="13" class="text-center text-muted">No ELCOT files found.</td></tr>';
return;
}
records.forEach(r => {
tbody.innerHTML += `
<tr>
<td>${r.id}</td>
<td class="fw-bold">${escapeHtml(r.vend_name)}</td>
<td>${escapeHtml(r.fin_year)}</td>
<td>${escapeHtml(r.file_data_num || '-')}</td>
<td>${getFileLink(r.file_path, 'Original')}</td>
<td>${getFileLink(r.po_file_path, 'PO')}</td>
<td>${getFileLink(r.dc_file_path, 'DC')}</td>
<td>${getFileLink(r.ir_file_path, 'Inward')}</td>
<td>${getFileLink(r.sr_filepath, 'Stock')}</td>
<td>${getFileLink(r.tc_file_path, 'Temp Bill')}</td>
<td>${getFileLink(r.rtgs_file_path, 'RTGS')}</td>
<td>${getFileLink(r.ejv_num_file_path, 'EJV')}</td>
<td>${getFileLink(r.as_final_file_path, 'Final AS')}</td>
</tr>
`;
});
}
function isValidFilePath(path) {
if (!path) return false;
const p = String(path).trim();
return p !== '' && p !== '-' && p !== 'null';
}
function getFileLink(path, label) {
if (!isValidFilePath(path)) return '<span class="doc-missing">-</span>';
let cleanPath = String(path).trim();
if (cleanPath.startsWith('/')) {
cleanPath = cleanPath.substring(1);
}
return `<a href="${contextPath}${cleanPath}" target="_blank" class="doc-link"><i class="fa-solid fa-file-pdf"></i> ${label}</a>`;
}
// Details Modal View / Lifecycle
function viewRecordDetails(id) {
const r = allRecords.find(item => item.id === id);
if (!r) return;
const balAmt = r.bal_amt ? Number(r.bal_amt).toLocaleString('en-IN') : '0';
const piAmt = r.pi_amt ? Number(r.pi_amt).toLocaleString('en-IN') : '-';
const fiAmt = r.fi_amt ? Number(r.fi_amt).toLocaleString('en-IN') : '-';
// Define checklist document array
const docs = [
{ name: "Original / Main Registration File", path: r.file_path, extra: `Ref Num: ${r.file_data_num || '-'}` },
{ name: "Purchase Order (PO) Document", path: r.po_file_path, extra: null },
{ name: "Delivery Challan (DC) Document", path: r.dc_file_path, extra: r.dc_completed_date ? `Completed Date: ${formatDate(r.dc_completed_date)}` : null },
{ name: "Inward Register Document", path: r.ir_file_path, extra: null },
{ name: "Stock Register (SR) Document", path: r.sr_filepath, extra: null },
{ name: "Proforma Invoice (PI) Document", path: r.pi_file_path, extra: `PI Amount: ₹ ${piAmt} | Date: ${formatDate(r.pi_file_gen_date)}` },
{ name: "Final Invoice (FI) Document", path: r.fi_file_path, extra: `FI Amount: ₹ ${fiAmt} | Date: ${formatDate(r.fi_file_gen_date)}` },
{ name: "Temporary Bill (TC) Document", path: r.tc_file_path, extra: `Date: ${formatDate(r.tc_file_gen_date)}` },
{ name: "RTGS Bank Payment Proof", path: r.rtgs_file_path, extra: r.rtgs_date ? `Upload Date: ${formatDate(r.rtgs_date)}` : null },
{ name: "EJV Adjustment Bill Copy", path: r.ejv_num_file_path, extra: `EJV Num: ${r.ejv_num || '-'} | Date: ${formatDate(r.ejv_num_file_gen_date)}` },
{ name: "AS Sanction Copy", path: r.as_file_path, extra: `Date: ${formatDate(r.as_file_gen_date)}` },
{ name: "Final AS Copy", path: r.as_final_file_path, extra: `Date: ${r.as_final_file_gen_date || '-'}` }
];
let html = `
<div class="row mb-3">
<div class="col-md-6">
<strong>Vendor Name:</strong> ${escapeHtml(r.vend_name)}<br>
<strong>Financial Year:</strong> ${escapeHtml(r.fin_year)}<br>
<strong>Advance Type:</strong> ${escapeHtml(r.resolved_adv_type || '-')}<br>
<strong>Fund Type:</strong> ${escapeHtml(r.resolved_fund_type || '-')}<br>
<strong>A/C Code:</strong> ${escapeHtml(r.ac_code || '-')}<br>
<strong>File Available Status:</strong> ${escapeHtml(r.file_available_status || '-')}
</div>
<div class="col-md-6 text-md-end">
<strong>Balance Amount:</strong> <span class="text-primary fw-bold">₹ ${balAmt}</span><br>
<strong>Last Updated By:</strong> ${escapeHtml(r.updated_by_name || r.updated_by || '-')}<br>
<strong>Last Updated Date:</strong> ${formatDateTime(r.updated_date || r.cdate)}
</div>
<div class="col-12 mt-2">
<strong>Subject:</strong> <span class="text-muted">${escapeHtml(r.subject || 'No subject provided.')}</span>
</div>
<div class="col-12 mt-2">
<strong>File Remarks:</strong> <span class="text-muted">${escapeHtml(r.file_remark || 'No remarks provided.')}</span>
</div>
</div>
<hr>
<h6 class="fw-bold mb-3"><i class="fa-solid fa-list-check text-success"></i> Document Checklist Lifecycle</h6>
<div class="list-group">
`;
docs.forEach((d, index) => {
const serial = index + 1;
const statusIcon = isValidFilePath(d.path)
? `<span class="badge bg-success-light text-success border border-success px-2 py-1"><i class="fa-solid fa-check-circle"></i> Uploaded</span>`
: `<span class="badge bg-danger-light text-danger border border-danger px-2 py-1"><i class="fa-solid fa-circle-xmark"></i> Missing</span>`;
let cleanPath = '';
if (d.path) {
cleanPath = String(d.path).trim();
if (cleanPath.startsWith('/')) {
cleanPath = cleanPath.substring(1);
}
}
const linkBtn = isValidFilePath(d.path)
? `<a href="${contextPath}${cleanPath}" target="_blank" class="btn btn-sm btn-outline-primary ms-3"><i class="fa-solid fa-eye"></i> View File</a>`
: '';
html += `
<div class="list-group-item d-flex justify-content-between align-items-center flex-wrap gap-2 py-3">
<div>
<span class="fw-bold text-dark me-2">${serial}.</span>
<span class="fw-semibold text-secondary" style="font-size: 0.95rem;">${d.name}</span>
${d.extra ? `<br><small class="text-muted ms-4">${escapeHtml(d.extra)}</small>` : ''}
</div>
<div class="d-flex align-items-center">
${statusIcon}
${linkBtn}
</div>
</div>
`;
});
html += `</div>`;
document.getElementById('modal-details-content').innerHTML = html;
detailsModalObj.show();
}
// Edit Record
function editRecord(id) {
const r = allRecords.find(item => item.id === id);
if (!r) return;
// Check if file is adjusted
const ejvNum = String(r.ejv_num || '').trim();
const ejvPath = String(r.ejv_num_file_path || '').trim();
const isAdjusted = (ejvNum !== '' && ejvNum !== '-') || (ejvPath !== '' && ejvPath !== '-');
if (isAdjusted) {
Swal.fire({
title: 'Access Denied',
text: 'This file has already been adjusted and cannot be updated.',
icon: 'warning',
confirmButtonColor: '#0a4b64'
});
return;
}
// Pre-populate form fields
document.getElementById('form-id').value = r.id;
document.getElementById('form-vend-name').value = r.vend_name;
document.getElementById('form-fin-year').value = r.fin_year;
document.getElementById('form-subject').value = r.subject || '';
document.getElementById('form-adv-type').value = r.adv_type_id || '';
document.getElementById('form-fund-type').value = r.fund_type_id || '';
document.getElementById('form-ac-code').value = r.ac_code || '';
document.getElementById('form-file-available-status').value = r.file_available_status || '';
document.getElementById('form-bal-amt').value = r.bal_amt || '';
document.getElementById('form-file-remark').value = r.file_remark || '';
document.getElementById('form-file-data-num').value = r.file_data_num || '';
document.getElementById('form-pi-amt').value = r.pi_amt || '';
document.getElementById('form-pi-gen-date').value = r.pi_file_gen_date || '';
document.getElementById('form-as-date').value = r.as_file_gen_date || '';
// Pre-populate new update-only fields
document.getElementById('form-fi-amt').value = r.fi_amt || '';
document.getElementById('form-fi-gen-date').value = r.fi_file_gen_date || '';
document.getElementById('form-as-final-date').value = r.as_final_file_gen_date || '';
document.getElementById('form-tc-date').value = r.tc_file_gen_date || '';
document.getElementById('form-rtgs-date').value = r.rtgs_date || '';
document.getElementById('form-dc-completed-date').value = r.dc_completed_date || '';
// Hide financial year input in edit mode (keep enabled but hidden so its value gets submitted)
document.getElementById('form-fin-year-container').style.display = 'none';
document.getElementById('form-fin-year').removeAttribute('disabled');
document.getElementById('form-fin-year').required = false;
// Run visibility check after populating fields
toggleVendorNameVisibility();
// Reset indicators
document.querySelectorAll('.file-selected-indicator').forEach(ind => {
ind.style.display = 'none';
ind.querySelector('span').textContent = '';
});
// Existing file paths links
setupExistingFileLink('form-as-file', r.as_file_path);
setupExistingFileLink('form-pi-file', r.pi_file_path);
setupExistingFileLink('form-rtgs-file', r.rtgs_file_path);
setupExistingFileLink('form-po-file', r.po_file_path);
setupExistingFileLink('form-dc-file', r.dc_file_path);
setupExistingFileLink('form-sr-file', r.sr_filepath);
setupExistingFileLink('form-ir-file', r.ir_file_path);
setupExistingFileLink('form-fi-file', r.fi_file_path);
setupExistingFileLink('form-as-final-file', r.as_final_file_path);
setupExistingFileLink('form-tc-file', r.tc_file_path);
// Show all edit-only fields
document.querySelectorAll('.edit-only-field').forEach(el => el.style.display = 'block');
// Set Form Title
document.getElementById('registration-title').innerHTML = `Edit File Registration (ID: ${r.id})`;
// Show registration modal for editing
registrationModalObj.show();
}
function openRegistrationModal() {
// Count active database records
// if (allRecords.length >= 257) {
// Swal.fire({
// title: 'Upload Locked',
// text: 'The database files count has reached the maximum cap of 257. No more file registrations are allowed.',
// icon: 'warning',
// confirmButtonColor: '#0a4b64'
// });
// return;
// }
resetForm();
// Show financial year input in new registration mode
document.getElementById('form-fin-year-container').style.display = 'block';
document.getElementById('form-fin-year').removeAttribute('disabled');
document.getElementById('form-fin-year').required = true;
// Hide edit-only fields in new registration mode
document.querySelectorAll('.edit-only-field').forEach(el => el.style.display = 'none');
// Hide any existing file links
document.querySelectorAll('.existing-file-link').forEach(el => el.style.display = 'none');
registrationModalObj.show();
}
// Reset form
function resetForm() {
document.getElementById('adjustment-registration-form').reset();
document.getElementById('form-id').value = '';
document.getElementById('registration-title').innerHTML = `New File Registration`;
// Show financial year input on form reset
document.getElementById('form-fin-year-container').style.display = 'block';
document.getElementById('form-fin-year').removeAttribute('disabled');
document.getElementById('form-fin-year').required = true;
document.querySelectorAll('.file-selected-indicator').forEach(ind => {
ind.style.display = 'none';
});
document.querySelectorAll('.existing-file-link').forEach(link => {
link.style.display = 'none';
});
toggleVendorNameVisibility();
}
function calculateBalance() {
const piVal = parseFloat(document.getElementById('form-pi-amt').value) || 0;
const fiVal = parseFloat(document.getElementById('form-fi-amt').value) || 0;
const balVal = piVal - fiVal;
document.getElementById('form-bal-amt').value = balVal.toFixed(2);
}
function setupExistingFileLink(inputId, filePath) {
const input = document.getElementById(inputId);
if (!input) return;
const container = input.closest('.file-upload-wrapper');
if (!container) return;
let linkDiv = container.querySelector('.existing-file-link');
if (!linkDiv) {
linkDiv = document.createElement('div');
linkDiv.className = 'existing-file-link mt-1';
linkDiv.style.fontSize = '0.85rem';
container.appendChild(linkDiv);
}
if (filePath && filePath !== '-' && filePath !== 'null') {
let cleanPath = String(filePath).trim();
if (cleanPath.startsWith('/')) {
cleanPath = cleanPath.substring(1);
}
const fileName = cleanPath.substring(cleanPath.lastIndexOf('/') + 1);
linkDiv.innerHTML = `<a href="${contextPath}${cleanPath}" target="_blank" class="text-primary"><i class="fa-solid fa-file-pdf"></i> View Existing: ${fileName}</a>`;
linkDiv.style.display = 'block';
} else {
linkDiv.style.display = 'none';
}
}
// Toggle vendor name visibility based on Senior System Manager (ID 6)
function toggleVendorNameVisibility() {
const advTypeSelect = document.getElementById('form-adv-type');
const vendorContainer = document.getElementById('vendor-name-container');
const vendorInput = document.getElementById('form-vend-name');
if (!advTypeSelect || !vendorContainer || !vendorInput) return;
if (advTypeSelect.value === '6') {
vendorContainer.style.display = 'block';
vendorInput.required = true;
} else {
vendorContainer.style.display = 'none';
vendorInput.required = false;
vendorInput.value = ''; // Reset when hidden
}
}
// Submit form (Save / Update)
function submitRegistrationForm(event) {
event.preventDefault();
showLoader(true);
const form = document.getElementById('adjustment-registration-form');
// Temporarily enable disabled fields (like fin_year in edit mode) so they are submitted in FormData
const disabledInputs = form.querySelectorAll(':disabled');
disabledInputs.forEach(input => input.removeAttribute('disabled'));
const formData = new FormData(form);
// Re-disable them
disabledInputs.forEach(input => input.setAttribute('disabled', 'true'));
// Append CSRF tokens if available
const csrfMeta = document.querySelector("meta[name='_csrf']");
const csrfHeaderMeta = document.querySelector("meta[name='_csrf_header']");
const headers = {};
if (csrfMeta && csrfHeaderMeta) {
headers[csrfHeaderMeta.getAttribute("content")] = csrfMeta.getAttribute("content");
}
fetch(contextPath + 'gccadjustmentmanagement/api/save', {
method: 'POST',
headers: headers,
body: formData
})
.then(res => res.json())
.then(data => {
showLoader(false);
if (data.success) {
Swal.fire({
title: 'Success!',
text: data.message,
icon: 'success',
confirmButtonColor: '#0a4b64'
}).then(() => {
if (registrationModalObj) {
registrationModalObj.hide();
}
resetForm();
loadRecordsData();
switchView('list');
});
} else {
Swal.fire({
title: 'Failed!',
text: data.error || 'Failed to save record details.',
icon: 'error',
confirmButtonColor: '#ef4444'
});
}
})
.catch(err => {
showLoader(false);
Swal.fire({
title: 'Error!',
text: 'An error occurred during submission: ' + err,
icon: 'error',
confirmButtonColor: '#ef4444'
});
});
}
// Delete Record with remarks
function deleteRecord(id) {
Swal.fire({
title: 'Delete Record?',
text: 'Please enter remarks/reason for deletion:',
input: 'text',
inputAttributes: {
autocapitalize: 'off',
required: true
},
showCancelButton: true,
confirmButtonText: 'Delete',
confirmButtonColor: '#ef4444',
cancelButtonColor: '#475569',
preConfirm: (remarks) => {
if (!remarks || remarks.trim() === '') {
Swal.showValidationMessage('Deletion remarks are required!');
}
return remarks;
}
}).then((result) => {
if (result.isConfirmed) {
showLoader(true);
const csrfMeta = document.querySelector("meta[name='_csrf']");
const csrfHeaderMeta = document.querySelector("meta[name='_csrf_header']");
const headers = {};
if (csrfMeta && csrfHeaderMeta) {
headers[csrfHeaderMeta.getAttribute("content")] = csrfMeta.getAttribute("content");
}
const formData = new FormData();
formData.append('id', id);
formData.append('remarks', result.value);
fetch(contextPath + 'gccadjustmentmanagement/api/delete', {
method: 'POST',
headers: headers,
body: formData
})
.then(res => res.json())
.then(data => {
showLoader(false);
if (data.success) {
Swal.fire({
title: 'Deleted!',
text: data.message,
icon: 'success',
confirmButtonColor: '#0a4b64'
}).then(() => {
loadRecordsData();
});
} else {
Swal.fire({
title: 'Failed!',
text: data.error || 'Failed to delete record.',
icon: 'error',
confirmButtonColor: '#ef4444'
});
}
})
.catch(err => {
showLoader(false);
Swal.fire('Error', 'Error deleting record: ' + err, 'error');
});
}
});
}
// Export data to Excel
function exportToExcel() {
const tempTable = document.createElement('table');
tempTable.innerHTML = `
<thead>
<tr>
<th style="text-align: center;">S.No</th>
<th>Vendor Name</th>
<th style="text-align: center;">Fin Year</th>
<th>File Num</th>
<th>Subject</th>
<th style="text-align: center;">Advance Type</th>
<th style="text-align: center;">Fund Type</th>
<th style="text-align: center;">A/C Code</th>
<th style="text-align: center;">File Available Status</th>
<th style="text-align: right;">Bal Amount</th>
<th style="text-align: right;">PI Amount</th>
<th style="text-align: right;">FI Amount</th>
<th>EJV Number</th>
<th>Created Date</th>
<th>Last Updated</th>
<th>Updated By</th>
</tr>
</thead>
<tbody>
</tbody>
`;
const tbody = tempTable.querySelector('tbody');
let currentSNo = 1;
filteredRecords.forEach(r => {
const balAmt = r.bal_amt ? Number(r.bal_amt).toLocaleString('en-IN') : '0';
const piAmt = r.pi_amt ? Number(r.pi_amt).toLocaleString('en-IN') : '-';
const fiAmt = r.fi_amt ? Number(r.fi_amt).toLocaleString('en-IN') : '-';
tbody.innerHTML += `
<tr>
<td style="text-align: center;">${currentSNo++}</td>
<td>${escapeHtml(r.vend_name || '')}</td>
<td style="text-align: center;">${escapeHtml(r.fin_year || '')}</td>
<td>${escapeHtml(r.file_data_num || '-')}</td>
<td>${escapeHtml(r.subject || '-')}</td>
<td style="text-align: center;">${escapeHtml(r.resolved_adv_type || '-')}</td>
<td style="text-align: center;">${escapeHtml(r.resolved_fund_type || '-')}</td>
<td style="text-align: center;">${escapeHtml(r.ac_code || '-')}</td>
<td style="text-align: center;">${escapeHtml(r.file_available_status || '-')}</td>
<td style="text-align: right;">₹ ${balAmt}</td>
<td style="text-align: right;">₹ ${piAmt}</td>
<td style="text-align: right;">₹ ${fiAmt}</td>
<td>${escapeHtml(r.ejv_num || '-')}</td>
<td>${formatDateTime(r.cdate)}</td>
<td>${formatDateTime(r.updated_date)}</td>
<td>${escapeHtml(r.updated_by_name || r.updated_by || '-')}</td>
</tr>
`;
});
const wb = XLSX.utils.table_to_book(tempTable, { sheet: "Adjustments" });
XLSX.writeFile(wb, "Adjustment_Files_Report.xlsx");
}
// Dashboard view toggling and detail list functions
function setDashboardViewMode(mode) {
dashboardViewMode = mode;
// Sync radio button checks
const detailsRadio = document.getElementById('mode-details');
const splitRadio = document.getElementById('mode-split');
if (detailsRadio) detailsRadio.checked = (mode === 'details');
if (splitRadio) splitRadio.checked = (mode === 'split');
// Update title text and icon
const titleText = document.getElementById('dash-report-title-text');
const titleIcon = document.getElementById('dash-report-title-icon');
const exportBtn = document.getElementById('btn-dash-export');
const searchContainer = document.getElementById('dash-search-container');
if (mode === 'details') {
if (titleText) titleText.textContent = 'Existing Adjustment Files';
if (titleIcon) {
titleIcon.className = 'fa-solid fa-folder-open text-primary me-2';
}
if (exportBtn) {
exportBtn.innerHTML = '<i class="fa-solid fa-file-excel me-1"></i> Export Excel';
}
if (searchContainer) searchContainer.style.display = 'block';
document.getElementById('panel-details-content').style.display = 'block';
document.getElementById('panel-split-content').style.display = 'none';
renderDashboardDetails();
} else {
if (titleText) titleText.textContent = 'Advance Split-up Report';
if (titleIcon) {
titleIcon.className = 'fa-solid fa-table-list text-primary me-2';
}
if (exportBtn) {
exportBtn.innerHTML = '<i class="fa-solid fa-file-excel me-1"></i> Export Split-up';
}
if (searchContainer) searchContainer.style.display = 'none';
document.getElementById('panel-details-content').style.display = 'none';
document.getElementById('panel-split-content').style.display = 'block';
renderSplitUpReport();
}
}
function onDashboardFilterChange() {
dashCurrentPage = 1;
renderSplitUpReport();
renderDashboardDetails();
}
function renderDashboardDetails() {
const fyFilter = document.getElementById('filter-fin-year').value;
const splitVendorSelect = document.getElementById('split-filter-vendor');
const splitVendorFilter = splitVendorSelect ? splitVendorSelect.value : 'All';
const splitAdvSelect = document.getElementById('split-filter-advance-type');
const splitAdvFilter = splitAdvSelect ? splitAdvSelect.value : 'All';
const searchVal = document.getElementById('dash-table-search') ? document.getElementById('dash-table-search').value.toLowerCase().trim() : '';
const cleanFy = str => String(str || '').replace(/\s+/g, '');
let records = allRecords;
// 1. FY Filter
if (fyFilter !== 'All') {
records = records.filter(r => cleanFy(r.fin_year) === cleanFy(fyFilter));
}
// 2. Click Filter (Total, Adjusted, Pending, Election, NotAvailable, LatestUpdates)
if (currentSplitFilter === 'Adjusted') {
records = records.filter(r => {
const ejvNum = String(r.ejv_num || '').trim();
const ejvPath = String(r.ejv_num_file_path || '').trim();
return (ejvNum !== '' && ejvNum !== '-') || (ejvPath !== '' && ejvPath !== '-');
});
} else if (currentSplitFilter === 'Pending') {
records = records.filter(r => {
const ejvNum = String(r.ejv_num || '').trim();
const ejvPath = String(r.ejv_num_file_path || '').trim();
return !((ejvNum !== '' && ejvNum !== '-') || (ejvPath !== '' && ejvPath !== '-'));
});
} else if (currentSplitFilter === 'Election') {
records = records.filter(r => String(r.adv_type_id) === '9');
} else if (currentSplitFilter === 'NotAvailable') {
records = records.filter(r => r.file_available_status === 'No');
} else if (currentSplitFilter === 'LatestUpdates') {
records = records.filter(r => {
const dateStr = r.updated_date || r.cdate;
return dateStr && getLocalDateString(dateStr) === latestUpdateDateString;
});
}
// 3. Vendor Dropdown
if (splitVendorFilter !== 'All') {
records = records.filter(r => String(r.vend_name || '').trim() === splitVendorFilter);
}
// 4. Advance Type Dropdown
if (splitAdvFilter !== 'All') {
records = records.filter(r => String(r.adv_type_id || '').trim() === splitAdvFilter);
}
// 5. Search text filter
if (searchVal !== '') {
records = records.filter(r =>
String(r.vend_name || '').toLowerCase().includes(searchVal) ||
String(r.file_data_num || '').toLowerCase().includes(searchVal) ||
String(r.subject || '').toLowerCase().includes(searchVal) ||
String(r.ejv_num || '').toLowerCase().includes(searchVal)
);
}
// 6. Sorting
records.sort((a, b) => {
let valA, valB;
if (dashSortField === 'fin_year') {
valA = String(a.fin_year || '').trim();
valB = String(b.fin_year || '').trim();
} else if (dashSortField === 'bal_amt') {
valA = Number(a.bal_amt || 0);
valB = Number(b.bal_amt || 0);
} else if (dashSortField === 'pi_amt') {
valA = Number(a.pi_amt || 0);
valB = Number(b.pi_amt || 0);
} else if (dashSortField === 'fi_amt') {
valA = Number(a.fi_amt || 0);
valB = Number(b.fi_amt || 0);
} else if (dashSortField === 'updated_date') {
valA = new Date(a.updated_date || a.cdate).getTime();
valB = new Date(b.updated_date || b.cdate).getTime();
} else {
valA = a.id;
valB = b.id;
}
if (valA === valB) return b.id - a.id; // secondary sort by ID descending
if (dashSortDirection === 'asc') {
if (typeof valA === 'string') {
return valA.localeCompare(valB);
}
return valA > valB ? 1 : -1;
} else {
if (typeof valA === 'string') {
return valB.localeCompare(valA);
}
return valA < valB ? 1 : -1;
}
});
dashFilteredRecords = records;
const totalRecords = records.length;
const startIndex = (dashCurrentPage - 1) * dashPageSize;
const endIndex = Math.min(startIndex + dashPageSize, totalRecords);
const paginatedRecords = records.slice(startIndex, endIndex);
const tbody = document.getElementById('dash-records-table-body');
tbody.innerHTML = '';
if (paginatedRecords.length === 0) {
tbody.innerHTML = '<tr><td colspan="14" class="text-center text-muted">No files found matching criteria.</td></tr>';
renderDashPagination(0);
return;
}
let currentSNo = startIndex + 1;
paginatedRecords.forEach(r => {
const balAmt = r.bal_amt ? Number(r.bal_amt).toLocaleString('en-IN') : '0';
const piAmt = r.pi_amt ? Number(r.pi_amt).toLocaleString('en-IN') : '-';
const fiAmt = r.fi_amt ? Number(r.fi_amt).toLocaleString('en-IN') : '-';
// <td class="text-center"><span class="badge bg-info text-dark">${escapeHtml(r.resolved_fund_type || '-')}</span></td>
// <td class="text-center">${escapeHtml(r.ac_code || '-')}</td>
tbody.innerHTML += `
<tr>
<td class="text-center">${currentSNo++}</td>
<td class="text-center">${escapeHtml(r.fin_year)}</td>
<td>${escapeHtml(r.file_data_num || '-')}</td>
<td class="text-center"><span class="badge bg-secondary">${escapeHtml(r.resolved_adv_type || '-')}</span></td>
<td class="text-center">
${r.file_available_status === 'Yes'
? `<span class="badge bg-success-light text-success border border-success px-2 py-0.5">Yes</span>`
: r.file_available_status === 'No'
? `<span class="badge bg-danger-light text-danger border border-danger px-2 py-0.5">No</span>`
: '-'}
</td>
<td class="fw-semibold text-primary text-end">₹ ${balAmt}</td>
<td class="text-end">₹ ${piAmt}</td>
<td class="text-end">₹ ${fiAmt}</td>
<td class="text-left text-wrap" style="white-space: normal !important; min-width: 200px; max-width: 350px; word-break: break-word;">${escapeHtml(r.file_remark || '-')}</td>
<td class="text-muted" style="font-size: 0.8rem;">${formatDateTime(r.cdate)}</td>
<td class="text-muted" style="font-size: 0.8rem;">${formatDateTime(r.updated_date)}</td>
<td>${escapeHtml(r.updated_by_name || r.updated_by || '-')}</td>
<td class="text-center">
<div class="action-btn-group justify-content-center">
<button class="action-btn btn-view" title="View Lifecycle" onclick="viewRecordDetails(${r.id})">
<i class="fa-solid fa-eye"></i>
</button>
<button class="action-btn btn-edit" title="Edit File" onclick="editRecord(${r.id})">
<i class="fa-solid fa-pen-to-square"></i>
</button>
<button class="action-btn btn-delete" title="Delete File" onclick="deleteRecord(${r.id})">
<i class="fa-solid fa-trash"></i>
</button>
</div>
</td>
</tr>
`;
});
renderDashPagination(totalRecords);
updateDashSortIcons();
}
function renderDashPagination(totalRecords) {
const info = document.getElementById('dash-pagination-info');
const buttons = document.getElementById('dash-pagination-buttons');
if (!info || !buttons) return;
const totalPages = Math.ceil(totalRecords / dashPageSize);
const startRange = totalRecords === 0 ? 0 : (dashCurrentPage - 1) * dashPageSize + 1;
const endRange = Math.min(dashCurrentPage * dashPageSize, totalRecords);
info.textContent = `Showing ${startRange} to ${endRange} of ${totalRecords} entries`;
buttons.innerHTML = '';
if (totalPages <= 1) {
return;
}
// Previous Button
const prevLi = document.createElement('li');
prevLi.className = `page-item ${dashCurrentPage === 1 ? 'disabled' : ''}`;
prevLi.innerHTML = `<a class="page-link" href="javascript:void(0)" onclick="goToDashPage(${dashCurrentPage - 1})">&laquo;</a>`;
buttons.appendChild(prevLi);
// Page Numbers
const maxPageButtons = 5;
let startPage = Math.max(1, dashCurrentPage - 2);
let endPage = Math.min(totalPages, startPage + maxPageButtons - 1);
if (endPage - startPage < maxPageButtons - 1) {
startPage = Math.max(1, endPage - maxPageButtons + 1);
}
for (let p = startPage; p <= endPage; p++) {
const li = document.createElement('li');
li.className = `page-item ${dashCurrentPage === p ? 'active' : ''}`;
li.innerHTML = `<a class="page-link" href="javascript:void(0)" onclick="goToDashPage(${p})">${p}</a>`;
buttons.appendChild(li);
}
// Next Button
const nextLi = document.createElement('li');
nextLi.className = `page-item ${dashCurrentPage === totalPages ? 'disabled' : ''}`;
nextLi.innerHTML = `<a class="page-link" href="javascript:void(0)" onclick="goToDashPage(${dashCurrentPage + 1})">&raquo;</a>`;
buttons.appendChild(nextLi);
}
function changeDashPageSize() {
const val = document.getElementById('dash-pagination-page-size').value;
if (val === 'All') {
dashPageSize = 999999;
} else {
dashPageSize = parseInt(val, 10);
}
dashCurrentPage = 1;
renderDashboardDetails();
}
function goToDashPage(page) {
dashCurrentPage = page;
renderDashboardDetails();
}
function toggleDashSort(field) {
if (dashSortField === field) {
dashSortDirection = dashSortDirection === 'asc' ? 'desc' : 'asc';
} else {
dashSortField = field;
dashSortDirection = 'desc';
}
renderDashboardDetails();
}
function updateDashSortIcons() {
const fields = ['fin_year', 'bal_amt', 'pi_amt', 'fi_amt', 'cdate', 'updated_date'];
fields.forEach(f => {
const el = document.getElementById(`dash-sort-icon-${f}`);
if (!el) return;
if (dashSortField === f) {
el.innerHTML = dashSortDirection === 'asc' ? '<i class="fa-solid fa-sort-up text-warning ms-1"></i>' : '<i class="fa-solid fa-sort-down text-warning ms-1"></i>';
} else {
el.innerHTML = '<i class="fa-solid fa-sort text-muted opacity-50 ms-1"></i>';
}
});
}
function exportDashboardDetailsToExcel() {
const tempTable = document.createElement('table');
tempTable.innerHTML = `
<thead>
<tr>
<th style="text-align: center;">S.No</th>
<th>Vendor Name</th>
<th style="text-align: center;">Fin Year</th>
<th>File Num</th>
<th>Subject</th>
<th style="text-align: center;">Advance Type</th>
<th style="text-align: center;">Fund Type</th>
<th style="text-align: center;">A/C Code</th>
<th style="text-align: center;">File Available Status</th>
<th style="text-align: right;">Bal Amount</th>
<th style="text-align: right;">PI Amount</th>
<th style="text-align: right;">FI Amount</th>
<th>EJV Number</th>
<th>Created Date</th>
<th>Last Updated</th>
<th>Updated By</th>
</tr>
</thead>
<tbody>
</tbody>
`;
const tbody = tempTable.querySelector('tbody');
let currentSNo = 1;
dashFilteredRecords.forEach(r => {
const balAmt = r.bal_amt ? Number(r.bal_amt).toLocaleString('en-IN') : '0';
const piAmt = r.pi_amt ? Number(r.pi_amt).toLocaleString('en-IN') : '-';
const fiAmt = r.fi_amt ? Number(r.fi_amt).toLocaleString('en-IN') : '-';
tbody.innerHTML += `
<tr>
<td style="text-align: center;">${currentSNo++}</td>
<td>${escapeHtml(r.vend_name || '')}</td>
<td style="text-align: center;">${escapeHtml(r.fin_year || '')}</td>
<td>${escapeHtml(r.file_data_num || '-')}</td>
<td>${escapeHtml(r.subject || '-')}</td>
<td style="text-align: center;">${escapeHtml(r.resolved_adv_type || '-')}</td>
<td style="text-align: center;">${escapeHtml(r.resolved_fund_type || '-')}</td>
<td style="text-align: center;">${escapeHtml(r.ac_code || '-')}</td>
<td style="text-align: center;">${escapeHtml(r.file_available_status || '-')}</td>
<td style="text-align: right;">₹ ${balAmt}</td>
<td style="text-align: right;">₹ ${piAmt}</td>
<td style="text-align: right;">₹ ${fiAmt}</td>
<td>${escapeHtml(r.ejv_num || '-')}</td>
<td>${formatDateTime(r.cdate)}</td>
<td>${formatDateTime(r.updated_date)}</td>
<td>${escapeHtml(r.updated_by_name || r.updated_by || '-')}</td>
</tr>
`;
});
const wb = XLSX.utils.table_to_book(tempTable, { sheet: "Dashboard Details" });
XLSX.writeFile(wb, "Dashboard_Files_Report.xlsx");
}
function exportDashboardData() {
if (dashboardViewMode === 'details') {
exportDashboardDetailsToExcel();
} else {
exportSplitUpToExcel();
}
}
// Generate Year-wise and Advance Type-wise Split-up Report
function renderSplitUpReport() {
const container = document.getElementById('panel-split-content');
if (!container) return;
if (!allRecords || allRecords.length === 0) {
container.innerHTML = `<div class="text-center text-muted p-5">No records loaded to generate split-up report.</div>`;
return;
}
// Filter allRecords based on currentSplitFilter
let recordsToProcess = allRecords;
if (currentSplitFilter === 'Adjusted') {
recordsToProcess = recordsToProcess.filter(r => {
const ejvNum = String(r.ejv_num || '').trim();
const ejvPath = String(r.ejv_num_file_path || '').trim();
return (ejvNum !== '' && ejvNum !== '-') || (ejvPath !== '' && ejvPath !== '-');
});
} else if (currentSplitFilter === 'Pending') {
recordsToProcess = recordsToProcess.filter(r => {
const ejvNum = String(r.ejv_num || '').trim();
const ejvPath = String(r.ejv_num_file_path || '').trim();
return !((ejvNum !== '' && ejvNum !== '-') || (ejvPath !== '' && ejvPath !== '-'));
});
} else if (currentSplitFilter === 'Election') {
recordsToProcess = recordsToProcess.filter(r => String(r.adv_type_id) === '9');
} else if (currentSplitFilter === 'NotAvailable') {
recordsToProcess = recordsToProcess.filter(r => r.file_available_status === 'No');
} else if (currentSplitFilter === 'LatestUpdates') {
recordsToProcess = recordsToProcess.filter(r => {
const dateStr = r.updated_date || r.cdate;
return dateStr && getLocalDateString(dateStr) === latestUpdateDateString;
});
}
// 1.1. Filter by split-page vendor dropdown filter
const splitVendorSelect = document.getElementById('split-filter-vendor');
const splitVendorFilter = splitVendorSelect ? splitVendorSelect.value : 'All';
if (splitVendorFilter !== 'All') {
recordsToProcess = recordsToProcess.filter(r => String(r.vend_name || '').trim() === splitVendorFilter);
}
// 1.2. Filter by split-page advance type dropdown filter
const splitAdvSelect = document.getElementById('split-filter-advance-type');
const splitAdvFilter = splitAdvSelect ? splitAdvSelect.value : 'All';
if (splitAdvFilter !== 'All') {
recordsToProcess = recordsToProcess.filter(r => String(r.adv_type_id || '').trim() === splitAdvFilter);
}
if (recordsToProcess.length === 0) {
container.innerHTML = `<div class="text-center text-muted p-5">No records matching the filter ("${currentSplitFilter === 'All' ? 'All Files' : (currentSplitFilter === 'Adjusted' ? 'Adjusted Files Only' : 'Pending / Not Adjusted Only')}") are available.</div>`;
return;
}
// 1. Group records by advance type (resolved_adv_type)
const groups = {};
recordsToProcess.forEach(r => {
const advType = String(r.resolved_adv_type || 'Others').trim();
if (!groups[advType]) {
groups[advType] = [];
}
groups[advType].push(r);
});
const sortedAdvTypes = Object.keys(groups).sort();
let html = `
<div class="table-responsive">
<table class="table table-bordered table-striped align-middle table-sm" style="border-color: #cbd5e1;">
<thead class="bg-primary text-white text-center fw-bold" style="background-color: #0a4b64 !important; color: white !important;">
<tr>
<th style="width: 60px;">Sl. No.</th>
<th>Advance Type</th>
<th>A/C Code</th>
<th>Year</th>
<th>To Whom paid</th>
<th>No. of Files</th>
<th>No. Of file not available</th>
<th>No.of file available</th>
<th>Amt.of Adv.paid</th>
<th>No.of file adjusted</th>
<th>adjusted amount</th>
</tr>
</thead>
<tbody>
`;
let slNo = 1;
let grandTotalFiles = 0;
let grandNotAvailable = 0;
let grandAvailable = 0;
let grandAdvPaid = 0;
let grandAdjustedCount = 0;
let grandAdjustedAmt = 0;
sortedAdvTypes.forEach(advType => {
const recordsInGroup = groups[advType];
// 2. Sub-group by financial year (fin_year)
const yearGroups = {};
recordsInGroup.forEach(r => {
const fy = String(r.fin_year || '-').trim();
if (!yearGroups[fy]) {
yearGroups[fy] = [];
}
yearGroups[fy].push(r);
});
// Sort years chronologically
const sortedYears = Object.keys(yearGroups).sort((a, b) => a.localeCompare(b));
const totalRowsForAdvType = sortedYears.length;
// Sums for this advance type
let subTotalFiles = 0;
let subNotAvailable = 0;
let subAvailable = 0;
let subAdvPaid = 0;
let subAdjustedCount = 0;
let subAdjustedAmt = 0;
// Unique A/C Codes for this advance type group
const acCodes = new Set();
recordsInGroup.forEach(r => {
if (r.ac_code && r.ac_code !== '-' && r.ac_code !== 'null') acCodes.add(r.ac_code);
});
const acCodeStr = acCodes.size > 0 ? Array.from(acCodes).join(', ') : '-';
sortedYears.forEach((fy, index) => {
const subRecords = yearGroups[fy];
const noOfFiles = subRecords.length;
let notAvailableCount = 0;
let availableCount = 0;
let sumAdvPaid = 0;
let adjustedCount = 0;
let sumAdjustedAmt = 0;
subRecords.forEach(r => {
// Availability calculation
let isAvail = false;
if (r.file_available_status === 'Yes') {
isAvail = true;
} else if (r.file_available_status === 'No') {
isAvail = false;
} else {
// Check physical files presence
const paths = [
r.file_path, r.po_file_path, r.dc_file_path, r.ir_file_path,
r.sr_filepath, r.pi_file_path, r.fi_file_path, r.tc_file_path,
r.rtgs_file_path, r.ejv_num_file_path, r.as_file_path, r.as_final_file_path
];
isAvail = paths.some(p => p && p !== '-' && p !== 'null' && String(p).trim() !== '');
}
if (isAvail) {
availableCount++;
} else {
notAvailableCount++;
}
// Advance amount sum
const piAmtVal = r.pi_amt ? Number(r.pi_amt) : 0;
sumAdvPaid += piAmtVal;
// Adjusted count & sum
const ejvNum = String(r.ejv_num || '').trim();
const ejvPath = String(r.ejv_num_file_path || '').trim();
const isAdjusted = (ejvNum !== '' && ejvNum !== '-') || (ejvPath !== '' && ejvPath !== '-');
if (isAdjusted) {
adjustedCount++;
sumAdjustedAmt += piAmtVal;
}
});
// Add to sub-totals
subTotalFiles += noOfFiles;
subNotAvailable += notAvailableCount;
subAvailable += availableCount;
subAdvPaid += sumAdvPaid;
subAdjustedCount += adjustedCount;
subAdjustedAmt += sumAdjustedAmt;
html += `<tr>`;
if (index === 0) {
html += `
<td class="text-center" rowspan="${totalRowsForAdvType}">${slNo}</td>
<td class="text-center" rowspan="${totalRowsForAdvType}"><strong>${escapeHtml(advType)}</strong></td>
<td class="text-center font-monospace" rowspan="${totalRowsForAdvType}">${escapeHtml(acCodeStr)}</td>
`;
}
html += `
<td class="text-center">${escapeHtml(fy)}</td>
<td class="text-center">${escapeHtml(advType)}</td>
<td class="text-center">
<span class="text-primary text-decoration-underline fw-semibold" style="cursor: pointer;" onclick="goToListViewWithSplitFilters('${advType.replace(/'/g, "\\'")}', '${fy}', 'All', 'All')">${noOfFiles}</span>
</td>
<td class="text-center">
<span class="text-danger text-decoration-underline fw-semibold" style="cursor: pointer;" onclick="goToListViewWithSplitFilters('${advType.replace(/'/g, "\\'")}', '${fy}', 'NotAvailable', 'All')">${notAvailableCount}</span>
</td>
<td class="text-center">
<span class="text-success text-decoration-underline fw-semibold" style="cursor: pointer;" onclick="goToListViewWithSplitFilters('${advType.replace(/'/g, "\\'")}', '${fy}', 'Available', 'All')">${availableCount}</span>
</td>
<td class="text-end fw-semibold">₹ ${sumAdvPaid.toLocaleString('en-IN')}</td>
<td class="text-center">
<span class="text-info text-decoration-underline fw-semibold" style="cursor: pointer;" onclick="goToListViewWithSplitFilters('${advType.replace(/'/g, "\\'")}', '${fy}', 'All', 'Adjusted')">${adjustedCount > 0 ? adjustedCount : ''}</span>
</td>
<td class="text-end">${sumAdjustedAmt > 0 ? '₹ ' + sumAdjustedAmt.toLocaleString('en-IN') : ''}</td>
</tr>
`;
});
// Render Sub-totals Row
html += `
<tr class="table-warning fw-bold text-dark" style="background-color: #fef08a !important;">
<td colspan="5" class="text-end">Total ${escapeHtml(advType)} Advance</td>
<td class="text-center">${subTotalFiles}</td>
<td class="text-center">${subNotAvailable}</td>
<td class="text-center">${subAvailable}</td>
<td class="text-end">₹ ${subAdvPaid.toLocaleString('en-IN')}</td>
<td class="text-center">${subAdjustedCount}</td>
<td class="text-end">₹ ${subAdjustedAmt.toLocaleString('en-IN')}</td>
</tr>
`;
// Add to grand totals
grandTotalFiles += subTotalFiles;
grandNotAvailable += subNotAvailable;
grandAvailable += subAvailable;
grandAdvPaid += subAdvPaid;
grandAdjustedCount += subAdjustedCount;
grandAdjustedAmt += subAdjustedAmt;
slNo++;
});
// Render Grand Total Row
html += `
<tr class="bg-dark text-white fw-bold">
<td colspan="5" class="text-end fw-bold" style="background-color: #1e293b !important; color: #ffffff !important;">Grand Total</td>
<td class="text-center fw-bold" style="background-color: #1e293b !important; color: #ffffff !important;">${grandTotalFiles}</td>
<td class="text-center fw-bold" style="background-color: #1e293b !important; color: #ffffff !important;">${grandNotAvailable}</td>
<td class="text-center fw-bold" style="background-color: #1e293b !important; color: #ffffff !important;">${grandAvailable}</td>
<td class="text-end fw-bold" style="background-color: #1e293b !important; color: #ffffff !important;">₹ ${grandAdvPaid.toLocaleString('en-IN')}</td>
<td class="text-center fw-bold" style="background-color: #1e293b !important; color: #ffffff !important;">${grandAdjustedCount}</td>
<td class="text-end fw-bold" style="background-color: #10b981 !important; color: #ffffff !important;">₹ ${grandAdjustedAmt.toLocaleString('en-IN')}</td>
</tr>
</tbody>
</table>
</div>
`;
container.innerHTML = html;
}
// Export split up report to Excel
function exportSplitUpToExcel() {
const table = document.querySelector('#panel-split-content table');
if (!table) {
Swal.fire({ title: 'Error', text: 'No table data available to export.', icon: 'error' });
return;
}
const wb = XLSX.utils.table_to_book(table, { sheet: "Split-up Report" });
XLSX.writeFile(wb, "Advance_Split_up_Report.xlsx");
}
// Navigate to Existing Files Details view with custom pre-applied filters
function goToListViewWithSplitFilters(advType, fy, availability, isAdjusted) {
const cleanFy = str => String(str || '').replace(/\s+/g, '');
// 1. Set Financial Year dropdown filter (whitespace-insensitive matching)
const fySelect = document.getElementById('filter-fin-year');
if (fySelect) {
const cleanTarget = cleanFy(fy);
const matchedOpt = Array.from(fySelect.options).find(opt => cleanFy(opt.value) === cleanTarget);
if (matchedOpt) {
fySelect.value = matchedOpt.value;
} else {
fySelect.value = 'All';
}
}
// 2. Set Advance Type dropdown filter (case-insensitive & trimmed matching)
const advSelect = document.getElementById('filter-advance-type');
if (advSelect) {
const matchedOption = Array.from(advSelect.options).find(opt => opt.textContent.trim().toLowerCase() === advType.trim().toLowerCase());
if (matchedOption) {
advSelect.value = matchedOption.value;
} else {
advSelect.value = 'All';
}
}
// 3. Set File Available dropdown filter
const availSelect = document.getElementById('filter-file-available');
if (availSelect) {
availSelect.value = availability;
}
// 4. Set Adjustment Status dropdown filter
const statusSelect = document.getElementById('filter-adjustment-status');
if (statusSelect) {
statusSelect.value = isAdjusted;
}
// 5. Reset other filters to 'All' or empty
const vendorSelect = document.getElementById('filter-vendor');
if (vendorSelect) vendorSelect.value = 'All';
const fundSelect = document.getElementById('filter-fund-type');
if (fundSelect) fundSelect.value = 'All';
const searchInput = document.getElementById('table-search');
if (searchInput) searchInput.value = '';
// 6. Switch SPA view to list and refresh data grid
switchView('list');
}
// Filter split-up report from dashboard card clicks
function setSplitFilter(filterType) {
currentSplitFilter = filterType;
// 1. Update visual card active indicators
const totalCard = document.getElementById('card-total-files');
const adjCard = document.getElementById('card-adjusted-files');
const pendCard = document.getElementById('card-pending-files');
const electCard = document.getElementById('card-election-files');
const notAvailCard = document.getElementById('card-notavailable-files');
if (totalCard) totalCard.classList.remove('active-card');
if (adjCard) adjCard.classList.remove('active-card');
if (pendCard) pendCard.classList.remove('active-card');
if (electCard) electCard.classList.remove('active-card');
if (notAvailCard) notAvailCard.classList.remove('active-card');
if (filterType === 'All') {
if (totalCard) totalCard.classList.add('active-card');
const ind = document.getElementById('split-filter-indicator');
if (ind) ind.textContent = 'All Files';
} else if (filterType === 'Adjusted') {
if (adjCard) adjCard.classList.add('active-card');
const ind = document.getElementById('split-filter-indicator');
if (ind) ind.textContent = 'Adjusted Files Only';
} else if (filterType === 'Pending') {
if (pendCard) pendCard.classList.add('active-card');
const ind = document.getElementById('split-filter-indicator');
if (ind) ind.textContent = 'Pending / Not Adjusted Only';
} else if (filterType === 'Election') {
if (electCard) electCard.classList.add('active-card');
const ind = document.getElementById('split-filter-indicator');
if (ind) ind.textContent = 'Election Files Only';
} else if (filterType === 'NotAvailable') {
if (notAvailCard) notAvailCard.classList.add('active-card');
const ind = document.getElementById('split-filter-indicator');
if (ind) ind.textContent = 'Not Available Files Only';
} else if (filterType === 'LatestUpdates') {
const ind = document.getElementById('split-filter-indicator');
if (ind) ind.textContent = 'Latest Updates Only';
}
// 2. Refresh Split-up table & Details table below
dashCurrentPage = 1;
renderSplitUpReport();
renderDashboardDetails();
}
// Reset all dashboard summary counts and filter selections
function resetDashboardFilters() {
// 1. Reset Split filter to All
currentSplitFilter = 'All';
const ind = document.getElementById('split-filter-indicator');
if (ind) ind.textContent = 'All Files';
const totalCard = document.getElementById('card-total-files');
const adjCard = document.getElementById('card-adjusted-files');
const pendCard = document.getElementById('card-pending-files');
const electCard = document.getElementById('card-election-files');
const notAvailCard = document.getElementById('card-notavailable-files');
if (totalCard) totalCard.classList.add('active-card');
if (adjCard) adjCard.classList.remove('active-card');
if (pendCard) pendCard.classList.remove('active-card');
if (electCard) electCard.classList.remove('active-card');
if (notAvailCard) notAvailCard.classList.remove('active-card');
// 2. Reset Financial Year selector to All
const fySelect = document.getElementById('filter-fin-year');
if (fySelect) fySelect.value = 'All';
// 3. Reset all list view filters to All / Empty
const vendorSelect = document.getElementById('filter-vendor');
if (vendorSelect) vendorSelect.value = 'All';
const statusSelect = document.getElementById('filter-adjustment-status');
if (statusSelect) statusSelect.value = 'All';
const advSelect = document.getElementById('filter-advance-type');
if (advSelect) advSelect.value = 'All';
const fundSelect = document.getElementById('filter-fund-type');
if (fundSelect) fundSelect.value = 'All';
const availSelect = document.getElementById('filter-file-available');
if (availSelect) availSelect.value = 'All';
const searchInput = document.getElementById('table-search');
if (searchInput) searchInput.value = '';
// 3.1. Reset split-page/dashboard selectors & search as well
const splitVendorSelect = document.getElementById('split-filter-vendor');
if (splitVendorSelect) splitVendorSelect.value = 'All';
const splitAdvSelect = document.getElementById('split-filter-advance-type');
if (splitAdvSelect) splitAdvSelect.value = 'All';
const dashSearchInput = document.getElementById('dash-table-search');
if (dashSearchInput) dashSearchInput.value = '';
// 3.2. Reset dashboard details paging/sorting state
dashCurrentPage = 1;
dashPageSize = 15;
dashSortField = 'fin_year';
dashSortDirection = 'desc';
// 4. Reload database data to ensure counts are fully fresh
loadRecordsData();
}
// --- LATEST UPDATES CORNER BADGE LOGIC ---
let latestUpdateDateString = null;
function updateLatestUpdateBadge() {
const badge = document.getElementById('latest-update-badge');
if (!badge) return;
if (!allRecordsIncludingDeleted || allRecordsIncludingDeleted.length === 0) {
badge.style.setProperty('display', 'none', 'important');
return;
}
// Find the maximum updated_date
let maxTime = 0;
let maxDateStr = null;
allRecordsIncludingDeleted.forEach(r => {
const dateStr = r.updated_date || r.cdate;
if (dateStr) {
const time = new Date(dateStr).getTime();
if (!isNaN(time) && time > maxTime) {
maxTime = time;
maxDateStr = dateStr;
}
}
});
if (maxTime === 0 || !maxDateStr) {
badge.style.setProperty('display', 'none', 'important');
return;
}
const latestDateOnly = getLocalDateString(maxDateStr);
latestUpdateDateString = latestDateOnly;
// Count how many files share this exact latest update date stamp
let count = 0;
allRecordsIncludingDeleted.forEach(r => {
const dateStr = r.updated_date || r.cdate;
if (dateStr && getLocalDateString(dateStr) === latestDateOnly) {
count++;
}
});
// Formatted date string helper
const formatDateTimeHelper = dateTimeStr => {
if (!dateTimeStr) return '-';
try {
const d = new Date(dateTimeStr);
if (isNaN(d.getTime())) return dateTimeStr;
const pad = n => String(n).padStart(2, '0');
return `${pad(d.getDate())}/${pad(d.getMonth() + 1)}/${d.getFullYear()} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
} catch (e) {
return dateTimeStr;
}
};
const countEl = document.getElementById('latest-update-count');
const timeEl = document.getElementById('latest-update-time');
if (countEl) countEl.textContent = count;
if (timeEl) timeEl.textContent = formatDateTimeHelper(maxDateStr);
badge.style.setProperty('display', 'flex', 'important');
}
// Get local date string YYYY-MM-DD
function getLocalDateString(dateStr) {
if (!dateStr) return null;
try {
const d = new Date(dateStr);
if (isNaN(d.getTime())) return null;
const yyyy = d.getFullYear();
const mm = String(d.getMonth() + 1).padStart(2, '0');
const dd = String(d.getDate()).padStart(2, '0');
return `${yyyy}-${mm}-${dd}`;
} catch (e) {
return null;
}
}
let latestUpdatesModalObj = null;
function openLatestUpdatesModal() {
if (!latestUpdateDateString) return;
// Filter allRecordsIncludingDeleted to find files updated on the latest date
const files = allRecordsIncludingDeleted.filter(r => {
const dateStr = r.updated_date || r.cdate;
return dateStr && getLocalDateString(dateStr) === latestUpdateDateString;
});
if (files.length === 0) return;
// Set modal title
const timeEl = document.getElementById('latest-update-time');
const timeStr = timeEl ? timeEl.textContent : '';
document.getElementById('latest-updates-modal-title').innerHTML = `<i class="fa-solid fa-bell text-warning me-2"></i> Latest Updates (${timeStr})`;
// Populate the modal table body
const tbody = document.getElementById('latest-updates-modal-body');
tbody.innerHTML = '';
let currentSNo = 1;
files.forEach(r => {
const balAmt = r.bal_amt ? Number(r.bal_amt).toLocaleString('en-IN') : '0';
// Determine update status tag
let statusBadge = '';
const cTime = r.cdate ? new Date(r.cdate).getTime() : 0;
const uTime = r.updated_date ? new Date(r.updated_date).getTime() : 0;
const isSoftDeleted = r.is_delete === 1 || r.is_delete === '1' || r.is_active === 0 || r.is_active === '0';
if (isSoftDeleted) {
statusBadge = `<span class="badge bg-danger">Deleted</span>`;
} else if (cTime > 0 && uTime > 0 && Math.abs(cTime - uTime) < 2000) {
statusBadge = `<span class="badge bg-success">Added</span>`;
} else {
statusBadge = `<span class="badge bg-primary">Updated</span>`;
}
tbody.innerHTML += `
<tr>
<td class="text-center">${currentSNo++}</td>
<td class="text-center">${escapeHtml(r.fin_year)}</td>
<td>${escapeHtml(r.file_data_num || '-')}</td>
<td>${escapeHtml(r.vend_name || '-')}</td>
<td class="text-center"><span class="badge bg-secondary">${escapeHtml(r.resolved_adv_type || '-')}</span></td>
<td class="text-center">${statusBadge}</td>
<td class="text-center">
${r.file_available_status === 'Yes'
? `<span class="badge bg-success-light text-success border border-success px-2 py-0.5">Yes</span>`
: r.file_available_status === 'No'
? `<span class="badge bg-danger-light text-danger border border-danger px-2 py-0.5">No</span>`
: '-'}
</td>
<td class="fw-semibold text-primary text-end">₹ ${balAmt}</td>
<td class="text-left text-wrap" style="white-space: normal !important; min-width: 200px; max-width: 300px; word-break: break-word;">${escapeHtml(r.file_remark || '-')}</td>
<td class="text-center">${formatDateTime(r.cdate)}</td>
<td class="text-center">${escapeHtml(r.updated_by_name || r.updated_by || '-')}</td>
<td class="text-center">${formatDateTime(r.updated_date)}</td>
<td class="text-center">
<div class="action-btn-group justify-content-center">
<button class="action-btn btn-view" title="View Lifecycle"
onclick="closeLatestUpdatesModalAndShowDetails(${r.id})">
<i class="fa-solid fa-eye"></i>
</button>
${isSoftDeleted
? ''
: `<button class="action-btn btn-edit" title="Edit File" onclick="closeLatestUpdatesModalAndEdit(${r.id})">
<i class="fa-solid fa-pen-to-square"></i>
</button>`
}
</div>
</td>
</tr>
`;
});
// Initialize bootstrap modal if needed
if (!latestUpdatesModalObj) {
latestUpdatesModalObj = new bootstrap.Modal(document.getElementById('latestUpdatesModal'));
}
latestUpdatesModalObj.show();
}
function closeLatestUpdatesModalAndShowDetails(id) {
if (latestUpdatesModalObj) {
latestUpdatesModalObj.hide();
}
setTimeout(() => {
viewRecordDetails(id);
}, 300);
}
function closeLatestUpdatesModalAndEdit(id) {
if (latestUpdatesModalObj) {
latestUpdatesModalObj.hide();
}
setTimeout(() => {
editRecord(id);
}, 300);
}
// HTML escaping helper
function escapeHtml(str) {
if (!str) return '';
return String(str)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
</script>
</body>
</html>