|
|
<!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})">«</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})">»</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})">«</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})">»</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, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } </script></body>
</html>
|