You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4125 lines
191 KiB
4125 lines
191 KiB
<!DOCTYPE html>
|
|
<html xmlns:th="http://www.thymeleaf.org" lang="en">
|
|
|
|
<head>
|
|
<script th:inline="javascript">
|
|
(function () {
|
|
const currentUser = /*[[${session.adj_usrName}]]*/ null;
|
|
if (currentUser && sessionStorage.getItem("adj_usrName") !== currentUser) {
|
|
window.location.replace(/*[[@{/gccadjustmentmanagement/login}]]*/ '/gccadjustmentmanagement/login');
|
|
}
|
|
})();
|
|
</script>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Adjustment File Management System</title>
|
|
|
|
<!-- CSRF Tokens -->
|
|
<meta th:if="${_csrf != null}" name="_csrf" th:content="${_csrf.token}" />
|
|
<meta th:if="${_csrf != null}" name="_csrf_header" th:content="${_csrf.headerName}" />
|
|
|
|
<!-- Google Fonts -->
|
|
<link
|
|
href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&family=Poppins:wght@300;400;500;600;700&display=swap"
|
|
rel="stylesheet">
|
|
|
|
<!-- Bootstrap & FontAwesome -->
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
|
<link rel="stylesheet" th:href="@{/assets/font-awesome-6.5.2/css/all.min.css}" type="text/css">
|
|
|
|
<!-- SweetAlert2 -->
|
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
|
|
|
<!-- SheetJS (Excel Export) -->
|
|
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
|
|
|
|
<!-- Core Style Design System -->
|
|
<style>
|
|
:root {
|
|
--primary: #0a4b64;
|
|
--primary-light: #f0f6f8;
|
|
--primary-hover: #083c50;
|
|
--accent: #e28b12;
|
|
--accent-light: #fef8eb;
|
|
--slate-50: #f8fafc;
|
|
--slate-100: #f1f5f9;
|
|
--slate-200: #e2e8f0;
|
|
--slate-300: #cbd5e1;
|
|
--slate-400: #94a3b8;
|
|
--slate-600: #475569;
|
|
--slate-700: #334155;
|
|
--slate-800: #1e293b;
|
|
--success: #10b981;
|
|
--success-light: #ecfdf5;
|
|
--warning: #f59e0b;
|
|
--warning-light: #fffbeb;
|
|
--danger: #ef4444;
|
|
--danger-light: #fef2f2;
|
|
--card-shadow: 0 10px 30px rgba(0, 0, 0, 0.03);
|
|
--hover-shadow: 0 15px 35px rgba(10, 75, 100, 0.08);
|
|
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
body {
|
|
background-color: #f3f7f9;
|
|
font-family: 'Poppins', sans-serif;
|
|
color: var(--slate-700);
|
|
min-height: 100vh;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
/* Premium Header Styling */
|
|
.header {
|
|
background: linear-gradient(135deg, #06232e, #0a4b64);
|
|
color: #fff;
|
|
padding: 15px 30px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
|
position: relative;
|
|
}
|
|
|
|
.logo-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 15px;
|
|
}
|
|
|
|
.logo-left img {
|
|
width: 50px;
|
|
height: 50px;
|
|
filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.2));
|
|
}
|
|
|
|
.logo-text h2 {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-size: 1.1rem;
|
|
font-weight: 800;
|
|
margin: 0;
|
|
line-height: 1.2;
|
|
letter-spacing: 0.5px;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.logo-text p {
|
|
font-size: 0.7rem;
|
|
margin: 0;
|
|
color: #8ed0e8;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.header-title {
|
|
position: absolute;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
text-align: center;
|
|
z-index: 1;
|
|
}
|
|
|
|
.header-title h2 {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-size: 1.4rem;
|
|
font-weight: 800;
|
|
margin: 0;
|
|
color: #ffffff;
|
|
letter-spacing: 0.5px;
|
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.user-menu {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 15px;
|
|
z-index: 2;
|
|
}
|
|
|
|
.user-badge {
|
|
background: rgba(255, 255, 255, 0.12);
|
|
padding: 8px 16px;
|
|
border-radius: 30px;
|
|
font-size: 0.85rem;
|
|
font-weight: 600;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
}
|
|
|
|
.btn-logout {
|
|
background: #ef4444;
|
|
color: #fff;
|
|
border: none;
|
|
padding: 8px 18px;
|
|
border-radius: 30px;
|
|
font-size: 0.85rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: var(--transition);
|
|
text-decoration: none;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
box-shadow: 0 4px 10px rgba(239, 68, 68, 0.2);
|
|
}
|
|
|
|
.btn-logout:hover {
|
|
background: #dc2626;
|
|
color: #fff;
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 6px 15px rgba(220, 38, 38, 0.3);
|
|
}
|
|
|
|
/* Top Module Navigation (Pills style from screenshot) */
|
|
.top-navbar {
|
|
background-color: #ffffff;
|
|
border-bottom: 1px solid var(--slate-200);
|
|
padding: 10px 30px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.nav-pills-container {
|
|
display: flex;
|
|
gap: 12px;
|
|
}
|
|
|
|
.nav-pill-btn {
|
|
background: none;
|
|
border: 1px solid var(--slate-200);
|
|
padding: 8px 20px;
|
|
border-radius: 20px;
|
|
font-size: 0.9rem;
|
|
font-weight: 600;
|
|
color: var(--slate-600);
|
|
cursor: pointer;
|
|
transition: var(--transition);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.nav-pill-btn:hover {
|
|
background-color: var(--slate-50);
|
|
color: var(--primary);
|
|
border-color: var(--slate-300);
|
|
}
|
|
|
|
.nav-pill-btn.active {
|
|
background: var(--primary);
|
|
color: #ffffff;
|
|
border-color: var(--primary);
|
|
box-shadow: 0 4px 12px rgba(10, 75, 100, 0.2);
|
|
}
|
|
|
|
/* App Container & Panels */
|
|
.app-content {
|
|
padding: 30px;
|
|
max-width: 1800px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.view-panel {
|
|
display: none;
|
|
animation: fadeIn 0.4s ease forwards;
|
|
}
|
|
|
|
.view-panel.active {
|
|
display: block;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(15px);
|
|
}
|
|
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
/* Sidebar & Vendor Filtering Cards (Left side) */
|
|
.filter-sidebar {
|
|
background-color: #ffffff;
|
|
border-radius: 20px;
|
|
padding: 24px;
|
|
box-shadow: var(--card-shadow);
|
|
border: 1px solid rgba(10, 75, 100, 0.05);
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.sidebar-header-title {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-size: 1.1rem;
|
|
font-weight: 800;
|
|
color: var(--primary);
|
|
margin-bottom: 20px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
border-bottom: 1.5px solid var(--slate-100);
|
|
padding-bottom: 10px;
|
|
}
|
|
|
|
.vendor-pill {
|
|
background-color: var(--slate-50);
|
|
border: 1px solid var(--slate-200);
|
|
padding: 10px 16px;
|
|
border-radius: 12px;
|
|
margin-bottom: 10px;
|
|
font-size: 0.88rem;
|
|
font-weight: 600;
|
|
color: var(--slate-700);
|
|
cursor: pointer;
|
|
transition: var(--transition);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.vendor-pill:hover,
|
|
.vendor-pill.active {
|
|
background-color: var(--primary-light);
|
|
border-color: var(--primary);
|
|
color: var(--primary);
|
|
transform: translateX(3px);
|
|
}
|
|
|
|
.vendor-count-badge {
|
|
background-color: var(--primary);
|
|
color: #ffffff;
|
|
padding: 3px 8px;
|
|
font-size: 0.75rem;
|
|
font-weight: 700;
|
|
border-radius: 20px;
|
|
}
|
|
|
|
.vendor-pill:hover .vendor-count-badge,
|
|
.vendor-pill.active .vendor-count-badge {
|
|
background-color: var(--accent);
|
|
}
|
|
|
|
/* Styling Cards */
|
|
.card-custom {
|
|
background: #ffffff;
|
|
border-radius: 20px;
|
|
box-shadow: var(--card-shadow);
|
|
border: 1px solid rgba(10, 75, 100, 0.05);
|
|
padding: 30px;
|
|
margin-bottom: 30px;
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.card-custom:hover {
|
|
box-shadow: var(--hover-shadow);
|
|
}
|
|
|
|
.section-header-title {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-size: 1.5rem;
|
|
font-weight: 800;
|
|
color: var(--primary);
|
|
margin-bottom: 25px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.form-section-title {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-size: 1.15rem;
|
|
font-weight: 700;
|
|
color: var(--primary);
|
|
margin-top: 15px;
|
|
margin-bottom: 20px;
|
|
padding-bottom: 8px;
|
|
border-bottom: 2px solid var(--primary-light);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
/* Form Inputs */
|
|
.form-label {
|
|
font-size: 0.85rem;
|
|
font-weight: 700;
|
|
color: var(--slate-600);
|
|
margin-bottom: 6px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.3px;
|
|
}
|
|
|
|
.form-control-custom {
|
|
height: 46px;
|
|
border-radius: 10px;
|
|
border: 1.5px solid var(--slate-200);
|
|
padding: 8px 16px;
|
|
font-size: 0.92rem;
|
|
font-weight: 500;
|
|
color: var(--slate-800);
|
|
background-color: var(--slate-50);
|
|
transition: var(--transition);
|
|
width: 100%;
|
|
}
|
|
|
|
.form-control-custom:focus {
|
|
outline: none;
|
|
border-color: var(--primary);
|
|
background-color: #ffffff;
|
|
box-shadow: 0 0 0 3px rgba(10, 75, 100, 0.15);
|
|
}
|
|
|
|
/* Premium File Upload Button */
|
|
.file-upload-wrapper {
|
|
position: relative;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.file-upload-btn-custom {
|
|
height: 46px;
|
|
border-radius: 10px;
|
|
border: 1.5px dashed var(--slate-300);
|
|
background-color: var(--slate-50);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
font-size: 0.88rem;
|
|
font-weight: 600;
|
|
color: var(--slate-600);
|
|
cursor: pointer;
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.file-upload-wrapper input[type="file"] {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
opacity: 0;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.file-upload-wrapper:hover .file-upload-btn-custom {
|
|
background-color: var(--primary-light);
|
|
border-color: var(--primary);
|
|
color: var(--primary);
|
|
}
|
|
|
|
.file-selected-indicator {
|
|
font-size: 0.72rem;
|
|
color: var(--success);
|
|
font-weight: 600;
|
|
margin-top: 4px;
|
|
display: none;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
|
|
/* Buttons styling */
|
|
.btn-custom {
|
|
padding: 10px 24px;
|
|
border-radius: 10px;
|
|
font-size: 0.92rem;
|
|
font-weight: 600;
|
|
transition: var(--transition);
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
border: none;
|
|
}
|
|
|
|
.btn-custom-primary {
|
|
background-color: var(--primary);
|
|
color: #ffffff;
|
|
box-shadow: 0 4px 10px rgba(10, 75, 100, 0.2);
|
|
}
|
|
|
|
.btn-custom-primary:hover {
|
|
background-color: var(--primary-hover);
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 6px 15px rgba(10, 75, 100, 0.3);
|
|
color: #ffffff;
|
|
}
|
|
|
|
.btn-custom-secondary {
|
|
background-color: var(--slate-200);
|
|
color: var(--slate-700);
|
|
}
|
|
|
|
.btn-custom-secondary:hover {
|
|
background-color: var(--slate-300);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
/* Table styles */
|
|
.table-responsive-custom {
|
|
border-radius: 12px;
|
|
overflow-x: auto;
|
|
border: 1px solid var(--slate-200);
|
|
}
|
|
|
|
.table-responsive-custom::-webkit-scrollbar {
|
|
height: 8px;
|
|
}
|
|
|
|
.table-responsive-custom::-webkit-scrollbar-track {
|
|
background: var(--slate-100);
|
|
border-radius: 10px;
|
|
}
|
|
|
|
.table-responsive-custom::-webkit-scrollbar-thumb {
|
|
background: var(--slate-300);
|
|
border-radius: 10px;
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.table-responsive-custom::-webkit-scrollbar-thumb:hover {
|
|
background: var(--primary);
|
|
}
|
|
|
|
.table-custom {
|
|
margin-bottom: 0;
|
|
font-size: 0.88rem;
|
|
}
|
|
|
|
.table-custom th {
|
|
background-color: var(--primary) !important;
|
|
color: #ffffff !important;
|
|
font-family: 'Outfit', sans-serif;
|
|
font-weight: 600;
|
|
padding: 14px 16px;
|
|
border: none;
|
|
vertical-align: middle;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.table-custom td {
|
|
padding: 14px 16px;
|
|
vertical-align: middle;
|
|
border-bottom: 1px solid var(--slate-200);
|
|
white-space: nowrap;
|
|
color: var(--slate-800);
|
|
}
|
|
|
|
.table-custom tr:last-child td {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.table-custom tbody tr:hover {
|
|
background-color: var(--slate-50) !important;
|
|
}
|
|
|
|
/* Action Buttons */
|
|
.action-btn-group {
|
|
display: flex;
|
|
gap: 6px;
|
|
}
|
|
|
|
.action-btn {
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: 8px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: #ffffff;
|
|
text-decoration: none;
|
|
transition: var(--transition);
|
|
border: none;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.action-btn:hover {
|
|
transform: scale(1.1);
|
|
color: #ffffff;
|
|
}
|
|
|
|
.btn-view {
|
|
background-color: var(--primary);
|
|
}
|
|
|
|
.btn-edit {
|
|
background-color: var(--accent);
|
|
}
|
|
|
|
.btn-delete {
|
|
background-color: var(--danger);
|
|
}
|
|
|
|
/* Loader Overlay */
|
|
#loader-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
background-color: rgba(6, 35, 46, 0.4);
|
|
display: none;
|
|
justify-content: center;
|
|
align-items: center;
|
|
z-index: 9999;
|
|
backdrop-filter: blur(4px);
|
|
}
|
|
|
|
.loader-spinner {
|
|
width: 55px;
|
|
height: 55px;
|
|
border: 5px solid rgba(255, 255, 255, 0.2);
|
|
border-top: 5px solid #ffffff;
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% {
|
|
transform: rotate(0deg);
|
|
}
|
|
|
|
100% {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
/* Document download/view labels */
|
|
.doc-link {
|
|
font-size: 0.8rem;
|
|
color: var(--primary);
|
|
text-decoration: none;
|
|
font-weight: 600;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
border: 1px solid var(--primary);
|
|
padding: 3px 8px;
|
|
border-radius: 6px;
|
|
background-color: #ffffff;
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.doc-link:hover {
|
|
background-color: var(--primary);
|
|
color: #ffffff;
|
|
}
|
|
|
|
.doc-missing {
|
|
font-size: 0.78rem;
|
|
color: var(--slate-400);
|
|
font-style: italic;
|
|
}
|
|
|
|
.count-badge-btn {
|
|
background-color: #ffffff;
|
|
border: 1px solid var(--slate-300);
|
|
padding: 8px 16px;
|
|
border-radius: 12px;
|
|
font-size: 0.9rem;
|
|
font-weight: 600;
|
|
color: var(--primary);
|
|
cursor: pointer;
|
|
transition: var(--transition);
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.02);
|
|
text-decoration: none;
|
|
width: 100%;
|
|
}
|
|
|
|
.count-badge-btn:hover {
|
|
background-color: var(--primary-light);
|
|
border-color: var(--primary);
|
|
color: var(--primary);
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 4px 12px rgba(10, 75, 100, 0.08);
|
|
}
|
|
|
|
.count-badge-btn.active-card {
|
|
background-color: var(--primary-light) !important;
|
|
border: 2px solid var(--primary) !important;
|
|
box-shadow: 0 4px 12px rgba(10, 75, 100, 0.12) !important;
|
|
transform: translateY(-1px) scale(1.01);
|
|
}
|
|
|
|
.count-card-label {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-size: 0.92rem;
|
|
font-weight: 700;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.count-card-number {
|
|
background-color: var(--primary-light);
|
|
color: var(--primary);
|
|
padding: 2px 10px;
|
|
border-radius: 12px;
|
|
font-size: 0.82rem;
|
|
font-weight: 800;
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.count-badge-btn:hover .count-card-number {
|
|
background-color: var(--primary);
|
|
color: #ffffff;
|
|
}
|
|
|
|
/* Pagination custom styles */
|
|
.pagination .page-link {
|
|
color: var(--primary);
|
|
border-color: var(--slate-200);
|
|
background-color: #ffffff;
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.pagination .page-item.active .page-link {
|
|
background-color: var(--primary) !important;
|
|
border-color: var(--primary) !important;
|
|
color: #ffffff !important;
|
|
}
|
|
|
|
.pagination .page-link:hover {
|
|
background-color: var(--primary-light);
|
|
color: var(--primary);
|
|
border-color: var(--primary);
|
|
}
|
|
|
|
.pagination .page-item.disabled .page-link {
|
|
color: var(--slate-400);
|
|
background-color: var(--slate-50);
|
|
border-color: var(--slate-200);
|
|
}
|
|
|
|
/* Split-up compact table cells styling */
|
|
#panel-split-content table th,
|
|
#panel-split-content table td {
|
|
padding: 5px 8px !important;
|
|
font-size: 0.88rem !important;
|
|
}
|
|
|
|
/* Mobile and Tablet Responsiveness */
|
|
@media (max-width: 991px) {
|
|
.header {
|
|
flex-direction: column;
|
|
gap: 15px;
|
|
padding: 15px 20px;
|
|
align-items: center;
|
|
text-align: center;
|
|
}
|
|
|
|
.logo-left {
|
|
flex-direction: column;
|
|
gap: 5px;
|
|
text-align: center;
|
|
}
|
|
|
|
.logo-text h2 {
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.header-title {
|
|
position: static;
|
|
transform: none;
|
|
margin: 10px 0;
|
|
}
|
|
|
|
.header-title h2 {
|
|
font-size: 1.15rem;
|
|
}
|
|
|
|
.user-menu {
|
|
flex-direction: row;
|
|
justify-content: center;
|
|
width: 100%;
|
|
gap: 10px;
|
|
}
|
|
|
|
.user-badge {
|
|
padding: 6px 12px;
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.btn-logout {
|
|
padding: 6px 14px;
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.top-navbar {
|
|
padding: 10px 15px;
|
|
overflow-x: auto;
|
|
display: block;
|
|
}
|
|
|
|
.nav-pills-container {
|
|
display: inline-flex;
|
|
flex-wrap: nowrap;
|
|
padding-bottom: 5px;
|
|
width: max-content;
|
|
}
|
|
|
|
.nav-pill-btn {
|
|
padding: 6px 14px;
|
|
font-size: 0.8rem;
|
|
white-space: nowrap;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 767px) {
|
|
.app-content {
|
|
padding: 12px;
|
|
}
|
|
|
|
.card-custom {
|
|
padding: 15px;
|
|
border-radius: 12px;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.section-header-title {
|
|
font-size: 1.2rem;
|
|
margin-bottom: 15px;
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: 5px;
|
|
}
|
|
|
|
.count-badge-btn {
|
|
padding: 10px 14px;
|
|
}
|
|
|
|
.count-card-label {
|
|
font-size: 0.82rem;
|
|
}
|
|
|
|
.count-card-number {
|
|
padding: 1px 8px;
|
|
font-size: 0.78rem;
|
|
}
|
|
|
|
/* Table filters grid layout */
|
|
.view-panel .card-custom .d-flex.gap-2.align-items-center.flex-wrap {
|
|
width: 100%;
|
|
display: grid !important;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 8px !important;
|
|
}
|
|
|
|
.view-panel .card-custom .d-flex.gap-2.align-items-center.flex-wrap select,
|
|
.view-panel .card-custom .d-flex.gap-2.align-items-center.flex-wrap input,
|
|
.view-panel .card-custom .d-flex.gap-2.align-items-center.flex-wrap button {
|
|
width: 100% !important;
|
|
height: 38px !important;
|
|
font-size: 0.85rem !important;
|
|
margin: 0 !important;
|
|
}
|
|
|
|
.view-panel .card-custom .d-flex.gap-2.align-items-center.flex-wrap input {
|
|
grid-column: span 2;
|
|
}
|
|
|
|
.view-panel .card-custom .d-flex.gap-2.align-items-center.flex-wrap button {
|
|
grid-column: span 2;
|
|
}
|
|
|
|
/* Split up report filters stack */
|
|
.bg-light.p-2.rounded.d-flex {
|
|
display: grid !important;
|
|
grid-template-columns: 1fr;
|
|
gap: 10px !important;
|
|
width: 100%;
|
|
}
|
|
|
|
.bg-light.p-2.rounded.d-flex .d-flex {
|
|
width: 100%;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.bg-light.p-2.rounded.d-flex select {
|
|
width: 70% !important;
|
|
}
|
|
|
|
/* Pagination stacked layout */
|
|
.mt-4.flex-wrap {
|
|
flex-direction: column !important;
|
|
align-items: center !important;
|
|
text-align: center;
|
|
gap: 15px !important;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<!-- Loader Overlay -->
|
|
<div id="loader-overlay">
|
|
<div class="loader-spinner"></div>
|
|
</div>
|
|
|
|
<!-- Header block containing title & logo -->
|
|
<div class="header">
|
|
<div class="logo-left">
|
|
<img th:src="@{/assets/header-images/gcc1.png}" alt="Greater Chennai Corporation Logo">
|
|
<div class="logo-text">
|
|
<h2>GREATER</h2>
|
|
<h2>CHENNAI</h2>
|
|
<h2>CORPORATION</h2>
|
|
<p>பெருநகர சென்னை மாநகராட்சி</p>
|
|
</div>
|
|
</div>
|
|
<div class="header-title">
|
|
<h2>Adjustment File Management System</h2>
|
|
</div>
|
|
<div class="user-menu" style="display: flex; align-items: center; gap: 15px;">
|
|
<!-- Latest Update Badge -->
|
|
<div id="latest-update-badge" class="d-flex align-items-center gap-2 p-2 px-3 rounded text-white"
|
|
style="display: none !important; background-color: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); font-size: 0.8rem; font-family: 'Outfit', sans-serif; cursor: pointer; transition: all 0.3s;"
|
|
onclick="openLatestUpdatesModal()">
|
|
<i class="fa-solid fa-bell text-warning fa-bounce animate-bounce"></i>
|
|
<div>
|
|
Latest Update: <span class="fw-bold text-warning" id="latest-update-count">0</span> File(s) on
|
|
<span class="fw-semibold" id="latest-update-time">-</span>
|
|
</div>
|
|
</div>
|
|
|
|
<span class="user-badge">
|
|
<i class="fa-solid fa-user-shield"></i>
|
|
<span th:text="${usrName}">Guest</span>
|
|
</span>
|
|
<a th:href="@{/gccadjustmentmanagement/logout}" class="btn-logout">
|
|
<i class="fa-solid fa-right-from-bracket"></i> Logout
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Top navigation tabs -->
|
|
<div class="top-navbar">
|
|
<div class="nav-pills-container">
|
|
<button class="nav-pill-btn active" id="tab-counts" onclick="switchView('counts')">
|
|
<i class="fa-solid fa-chart-simple"></i> Counts Dashboard
|
|
</button>
|
|
<button class="nav-pill-btn" id="tab-reg" onclick="openRegistrationModal()">
|
|
<i class="fa-solid fa-file-signature"></i> New File Registration
|
|
</button>
|
|
<button class="nav-pill-btn" id="tab-list" onclick="switchView('list')">
|
|
<i class="fa-solid fa-folder-open"></i> Existing File Details
|
|
</button>
|
|
<!-- <button class="nav-pill-btn" id="tab-oc" onclick="switchView('oc')">
|
|
<i class="fa-solid fa-copy"></i> OC Copies Details
|
|
</button> -->
|
|
<button class="nav-pill-btn" id="tab-ejv" onclick="switchView('ejv')">
|
|
<i class="fa-solid fa-file-pen"></i> Update EJV Number
|
|
</button>
|
|
|
|
</div>
|
|
<div class="d-flex align-items-center gap-3" style="display: none !important;">
|
|
<!-- Financial Year selector -->
|
|
<label for="filter-fin-year" class="fw-bold text-secondary mb-0"
|
|
style="font-size: 0.85rem; text-transform: uppercase;">Financial Year :</label>
|
|
<select id="filter-fin-year" class="form-control-custom"
|
|
style="width: 150px; height: 38px; padding: 4px 12px;" onchange="loadRecordsData()">
|
|
<option value="All">All</option>
|
|
</select>
|
|
<button class="btn btn-primary btn-sm rounded-pill px-3 py-1.5 fw-semibold"
|
|
onclick="loadRecordsData()">Apply</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="app-content">
|
|
<div class="row">
|
|
<!-- Main Panel (Centered Full Width) -->
|
|
<div class="col-12" id="main-content-col">
|
|
|
|
<!-- 0. VIEW PANEL: COUNTS DASHBOARD -->
|
|
<div class="view-panel active" id="panel-counts">
|
|
<div class="card-custom py-2 mb-2">
|
|
<div class="d-flex justify-content-between align-items-center px-4 mb-2">
|
|
<div style="width: 80px;"></div> <!-- Spacer to balance the right button -->
|
|
<div class="section-header-title mb-0 text-center flex-grow-1">
|
|
<i class="fa-solid fa-chart-simple text-primary"></i> Adjustment Summary Counts
|
|
</div>
|
|
<button class="btn btn-sm btn-outline-secondary rounded-pill px-3 py-1 fw-semibold"
|
|
style="font-size: 0.8rem;" onclick="resetDashboardFilters()">
|
|
<i class="fa-solid fa-arrow-rotate-left me-1"></i> Reset
|
|
</button>
|
|
</div>
|
|
<div class="row g-2 justify-content-center mt-1" id="dashboard-counts-list"
|
|
style="max-width: 1200px; margin: 0 auto;">
|
|
<!-- Dynamic Counts loaded here -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Split-up & Details Report inside Counts Dashboard -->
|
|
<div class="card-custom p-3">
|
|
<div
|
|
class="d-flex justify-content-between align-items-center mb-2 pb-1 border-bottom flex-wrap gap-3">
|
|
<h4 class="text-primary fw-bold mb-0 text-center flex-grow-1"
|
|
id="dash-report-title-container"
|
|
style="font-family: 'Outfit', sans-serif; font-size: 1.2rem; display: flex; align-items: center; justify-content: center; gap: 8px;">
|
|
<i id="dash-report-title-icon" class="fa-solid fa-folder-open text-primary"></i>
|
|
<span id="dash-report-title-text">Existing Adjustment Files</span>
|
|
<span id="split-filter-indicator" class="badge bg-secondary ms-2"
|
|
style="font-size: 0.75rem; vertical-align: middle;">All Files</span>
|
|
</h4>
|
|
<div class="d-flex align-items-center gap-3">
|
|
<div class="btn-group" role="group" aria-label="Dashboard Report Toggle">
|
|
<input type="radio" class="btn-check" name="dash-view-mode" id="mode-details"
|
|
autocomplete="off" checked onclick="setDashboardViewMode('details')">
|
|
<label class="btn btn-outline-primary btn-sm px-3 rounded-start-pill"
|
|
for="mode-details">
|
|
<i class="fa-solid fa-list me-1"></i> File Details
|
|
</label>
|
|
|
|
<input type="radio" class="btn-check" name="dash-view-mode" id="mode-split"
|
|
autocomplete="off" onclick="setDashboardViewMode('split')">
|
|
<label class="btn btn-outline-primary btn-sm px-3 rounded-end-pill"
|
|
for="mode-split">
|
|
<i class="fa-solid fa-table-list me-1"></i> Split-up
|
|
</label>
|
|
</div>
|
|
<button class="btn btn-sm btn-success px-4 py-2 rounded-pill fw-semibold"
|
|
id="btn-dash-export" onclick="exportDashboardData()">
|
|
<i class="fa-solid fa-file-excel me-1"></i> Export Excel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Dropdown filters for split-up report / details -->
|
|
<div class="d-flex align-items-center gap-3 mb-3 flex-wrap bg-light p-2 rounded">
|
|
<div class="d-flex align-items-center gap-2">
|
|
<label for="split-filter-vendor" class="fw-bold text-secondary mb-0"
|
|
style="font-size: 0.85rem;">Vendor:</label>
|
|
<select id="split-filter-vendor" class="form-control-custom"
|
|
style="width: 200px; height: 36px; padding: 2px 10px;"
|
|
onchange="onDashboardFilterChange()">
|
|
<option value="All">All Vendors</option>
|
|
</select>
|
|
</div>
|
|
<div class="d-flex align-items-center gap-2">
|
|
<label for="split-filter-advance-type" class="fw-bold text-secondary mb-0"
|
|
style="font-size: 0.85rem;">Advance Type:</label>
|
|
<select id="split-filter-advance-type" class="form-control-custom"
|
|
style="width: 200px; height: 36px; padding: 2px 10px;"
|
|
onchange="onDashboardFilterChange()">
|
|
<option value="All">All Advance Types</option>
|
|
</select>
|
|
</div>
|
|
<div class="d-flex align-items-center gap-2 ms-auto" id="dash-search-container">
|
|
<input type="text" id="dash-table-search" class="form-control-custom"
|
|
style="width: 220px; height: 36px; padding: 2px 10px;"
|
|
placeholder="Search files/vendors..." oninput="renderDashboardDetails()">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Details Table Mode -->
|
|
<div id="panel-details-content">
|
|
<div class="table-responsive table-responsive-custom">
|
|
<table class="table table-custom text-nowrap align-middle" id="dash-records-table">
|
|
<thead>
|
|
<tr>
|
|
<th class="text-center">S.No</th>
|
|
<th class="text-center" style="cursor: pointer;"
|
|
onclick="toggleDashSort('fin_year')">
|
|
Fin Year <span id="dash-sort-icon-fin_year"><i
|
|
class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
|
|
</th>
|
|
<th>File Num</th>
|
|
<th class="text-center">Advance Type</th>
|
|
<!-- <th class="text-center">Fund Type</th> -->
|
|
<!-- <th class="text-center">A/C Code</th> -->
|
|
<th class="text-center">File Available</th>
|
|
<th class="text-end" style="cursor: pointer;"
|
|
onclick="toggleDashSort('bal_amt')">
|
|
Bal Amount <span id="dash-sort-icon-bal_amt"><i
|
|
class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
|
|
</th>
|
|
<th class="text-end" style="cursor: pointer;"
|
|
onclick="toggleDashSort('pi_amt')">
|
|
PI Amount <span id="dash-sort-icon-pi_amt"><i
|
|
class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
|
|
</th>
|
|
<th class="text-end" style="cursor: pointer;"
|
|
onclick="toggleDashSort('fi_amt')">
|
|
FI Amount <span id="dash-sort-icon-fi_amt"><i
|
|
class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
|
|
</th>
|
|
<!-- <th>EJV Number</th> -->
|
|
<th
|
|
style="white-space: normal !important; min-width: 200px; max-width: 350px;">
|
|
Remarks</th>
|
|
<th style="cursor: pointer;" onclick="toggleDashSort('cdate')">
|
|
Created Date <span id="dash-sort-icon-cdate"><i
|
|
class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
|
|
</th>
|
|
<th style="cursor: pointer;" onclick="toggleDashSort('updated_date')">
|
|
Last Updated <span id="dash-sort-icon-updated_date"><i
|
|
class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
|
|
</th>
|
|
<th>Updated By</th>
|
|
<th class="text-center">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="dash-records-table-body">
|
|
<!-- Loaded dynamically -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Dashboard Pagination Controls -->
|
|
<div class="d-flex justify-content-between align-items-center mt-4 flex-wrap gap-3">
|
|
<div class="d-flex align-items-center gap-2">
|
|
<span class="text-muted" style="font-size: 0.9rem;">Show</span>
|
|
<select id="dash-pagination-page-size" class="form-control-custom"
|
|
style="width: 80px; height: 38px; padding: 4px 8px; font-size: 0.9rem;"
|
|
onchange="changeDashPageSize()">
|
|
<option value="15">15</option>
|
|
<option value="30">30</option>
|
|
<option value="50">50</option>
|
|
<option value="All">All</option>
|
|
</select>
|
|
<span class="text-muted" style="font-size: 0.9rem;">entries</span>
|
|
</div>
|
|
<div class="text-muted fw-semibold" id="dash-pagination-info"
|
|
style="font-size: 0.9rem;">
|
|
Showing 0 to 0 of 0 entries
|
|
</div>
|
|
<nav aria-label="Page navigation">
|
|
<ul class="pagination pagination-sm mb-0" id="dash-pagination-buttons">
|
|
<!-- Dynamic pagination links -->
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Split-up Table Mode -->
|
|
<div id="panel-split-content" style="display: none;"></div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<!-- 2. VIEW PANEL: EXISTING FILES LIST -->
|
|
<div class="view-panel" id="panel-list">
|
|
<div class="card-custom">
|
|
<div class="d-flex justify-content-between align-items-center mb-4 flex-wrap gap-3">
|
|
<div class="section-header-title mb-0">
|
|
<i class="fa-solid fa-folder-open text-primary"></i> <span id="list-title">Existing
|
|
Adjustment Files</span>
|
|
</div>
|
|
<div class="d-flex gap-2 align-items-center flex-wrap">
|
|
<select id="filter-vendor" class="form-control-custom"
|
|
style="width: 180px; height: 40px;" onchange="filterRecordsList()">
|
|
<option value="All">All Vendors</option>
|
|
</select>
|
|
<select id="filter-adjustment-status" class="form-control-custom"
|
|
style="width: 180px; height: 40px;" onchange="filterRecordsList()">
|
|
<option value="All">All Statuses</option>
|
|
<option value="Adjusted">Adjusted Only</option>
|
|
<option value="Pending">Pending/Not Adjusted</option>
|
|
</select>
|
|
<select id="filter-advance-type" class="form-control-custom"
|
|
style="width: 180px; height: 40px;" onchange="filterRecordsList()">
|
|
<option value="All">All Advance Types</option>
|
|
</select>
|
|
<select id="filter-fund-type" class="form-control-custom"
|
|
style="width: 180px; height: 40px;" onchange="filterRecordsList()">
|
|
<option value="All">All Fund Types</option>
|
|
</select>
|
|
<select id="filter-file-available" class="form-control-custom"
|
|
style="width: 180px; height: 40px;" onchange="filterRecordsList()">
|
|
<option value="All">All Availability</option>
|
|
<option value="Available">Available Files Only</option>
|
|
<option value="NotAvailable">Not Available Files Only</option>
|
|
</select>
|
|
<input type="text" id="table-search" class="form-control-custom"
|
|
style="width: 220px; height: 40px;" placeholder="Search files/vendors..."
|
|
oninput="filterRecordsList()">
|
|
<button class="btn-custom btn-custom-primary py-2" onclick="exportToExcel()">
|
|
<i class="fa-solid fa-file-excel"></i> Export Excel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Data Table -->
|
|
<div class="table-responsive table-responsive-custom">
|
|
<table class="table table-custom text-nowrap align-middle" id="main-records-table">
|
|
<thead>
|
|
<tr>
|
|
<th class="text-center">S.No</th>
|
|
<!-- <th>Vendor Name</th> -->
|
|
<th class="text-center" style="cursor: pointer;"
|
|
onclick="toggleSort('fin_year')">
|
|
Fin Year <span id="sort-icon-fin_year"><i
|
|
class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
|
|
</th>
|
|
<th>File Num</th>
|
|
<th class="text-center">Advance Type</th>
|
|
<th class="text-center">Fund Type</th>
|
|
<th class="text-center">A/C Code</th>
|
|
<th class="text-center">File Available</th>
|
|
<th class="text-end" style="cursor: pointer;" onclick="toggleSort('bal_amt')">
|
|
Bal Amount <span id="sort-icon-bal_amt"><i
|
|
class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
|
|
</th>
|
|
<th class="text-end" style="cursor: pointer;" onclick="toggleSort('pi_amt')">
|
|
PI Amount <span id="sort-icon-pi_amt"><i
|
|
class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
|
|
</th>
|
|
<th class="text-end" style="cursor: pointer;" onclick="toggleSort('fi_amt')">
|
|
FI Amount <span id="sort-icon-fi_amt"><i
|
|
class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
|
|
</th>
|
|
<th>EJV Number</th>
|
|
<th style="cursor: pointer;" onclick="toggleSort('cdate')">
|
|
Created Date <span id="sort-icon-cdate"><i
|
|
class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
|
|
</th>
|
|
<th style="cursor: pointer;" onclick="toggleSort('updated_date')">
|
|
Last Updated <span id="sort-icon-updated_date"><i
|
|
class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
|
|
</th>
|
|
<th>Updated By</th>
|
|
<th class="text-center">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="records-table-body">
|
|
<!-- Loaded dynamically -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Pagination Controls -->
|
|
<div class="d-flex justify-content-between align-items-center mt-4 flex-wrap gap-3">
|
|
<div class="d-flex align-items-center gap-2">
|
|
<span class="text-muted" style="font-size: 0.9rem;">Show</span>
|
|
<select id="pagination-page-size" class="form-control-custom"
|
|
style="width: 80px; height: 38px; padding: 4px 8px; font-size: 0.9rem;"
|
|
onchange="changePageSize()">
|
|
<option value="15">15</option>
|
|
<option value="30">30</option>
|
|
<option value="50">50</option>
|
|
<option value="All">All</option>
|
|
</select>
|
|
<span class="text-muted" style="font-size: 0.9rem;">entries</span>
|
|
</div>
|
|
<div class="text-muted fw-semibold" id="pagination-info" style="font-size: 0.9rem;">
|
|
Showing 0 to 0 of 0 entries
|
|
</div>
|
|
<nav aria-label="Page navigation">
|
|
<ul class="pagination pagination-sm mb-0" id="pagination-buttons">
|
|
<!-- Dynamic pagination links -->
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 3. VIEW PANEL: ELCOT OC COPIES VIEW -->
|
|
<div class="view-panel" id="panel-oc">
|
|
<div class="card-custom">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<div class="section-header-title mb-0">
|
|
<i class="fa-solid fa-copy text-primary"></i> Elcot OC Files Registry
|
|
</div>
|
|
<div class="d-flex gap-2">
|
|
<input type="text" id="oc-table-search" class="form-control-custom"
|
|
style="width: 250px; height: 40px;" placeholder="Search ELCOT copies..."
|
|
oninput="filterOcList()">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- OC Copies Data Table -->
|
|
<div class="table-responsive table-responsive-custom">
|
|
<table class="table table-custom text-nowrap align-middle">
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Vendor</th>
|
|
<th>Fin Year</th>
|
|
<th>File Num</th>
|
|
<th>Original File</th>
|
|
<th>PO File</th>
|
|
<th>DC File</th>
|
|
<th>Inward File</th>
|
|
<th>Stock File</th>
|
|
<th>Temp Bill</th>
|
|
<th>RTGS File</th>
|
|
<th>EJV File</th>
|
|
<th>Final AS File</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="oc-table-body">
|
|
<!-- Loaded dynamically -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 4. VIEW PANEL: UPDATE EJV NUMBER -->
|
|
<div class="view-panel" id="panel-ejv">
|
|
<div class="card-custom p-4">
|
|
<div class="d-flex justify-content-between align-items-center mb-4 pb-2 border-bottom">
|
|
<button class="btn btn-outline-secondary btn-sm px-3 rounded-pill"
|
|
onclick="switchView('list')">
|
|
<i class="fa-solid fa-arrow-left"></i> Back
|
|
</button>
|
|
<h4 class="text-primary fw-bold mb-0 text-center flex-grow-1"
|
|
style="font-family: 'Outfit', sans-serif;">Update EJV Number</h4>
|
|
<div style="width: 80px;"></div> <!-- Spacer for centering -->
|
|
</div>
|
|
<form id="ejv-update-form" onsubmit="submitEJVForm(event)" enctype="multipart/form-data">
|
|
<div class="row g-4 justify-content-center">
|
|
<!-- File Number Selector -->
|
|
<div class="col-md-5">
|
|
<label for="ejv-file-num-input" class="form-label fw-bold">Select File Number :
|
|
*</label>
|
|
<input type="text" id="ejv-file-num-input" class="form-control-custom"
|
|
list="file-numbers-list" placeholder="Type or select a file number" required
|
|
oninput="handleEJVFileNumberInput()">
|
|
<datalist id="file-numbers-list"></datalist>
|
|
<input type="hidden" id="ejv-file-id" name="id">
|
|
</div>
|
|
|
|
<!-- Adjustment Order Uploaded Status -->
|
|
<div class="col-md-5">
|
|
<label for="ejv-adj-order-status" class="form-label fw-bold">Adjustment Order
|
|
Uploaded :</label>
|
|
<input type="text" id="ejv-adj-order-status" class="form-control-custom" readonly
|
|
disabled value="No">
|
|
</div>
|
|
|
|
<!-- EJV Number File -->
|
|
<div class="col-md-5">
|
|
<label class="form-label fw-bold">EJV Number File : *</label>
|
|
<div class="file-upload-wrapper">
|
|
<div class="file-upload-btn-custom">
|
|
<i class="fa-solid fa-cloud-arrow-up"></i> Choose File
|
|
</div>
|
|
<input type="file" id="ejv-num-file" name="ejvFile" accept=".pdf"
|
|
onchange="indicateFileSelected(this)" required disabled>
|
|
<div class="file-selected-indicator"><i class="fa-solid fa-check"></i>
|
|
<span></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- EJV Number -->
|
|
<div class="col-md-5">
|
|
<label for="ejv-number-input" class="form-label fw-bold">EJV Number : *</label>
|
|
<input type="text" id="ejv-number-input" name="ejv_num" class="form-control-custom"
|
|
placeholder="Enter EJV Number" required disabled>
|
|
</div>
|
|
|
|
<!-- Submit Button -->
|
|
<div class="col-12 text-center mt-4">
|
|
<button type="submit" class="btn-custom btn-custom-primary px-5 py-2.5">
|
|
<i class="fa-solid fa-paper-plane me-2"></i>Submit
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal: New File Registration / Edit File -->
|
|
<div class="modal fade" id="registrationModal" tabindex="-1" aria-labelledby="registrationModalLabel"
|
|
aria-hidden="true">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content" style="border-radius: 16px;">
|
|
<div class="modal-header bg-dark text-white" style="border-radius: 16px 16px 0 0;">
|
|
<h5 class="modal-title" id="registrationModalLabel">
|
|
<i class="fa-solid fa-file-signature text-primary"></i> <span id="registration-title">New File
|
|
Registration</span>
|
|
</h5>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"
|
|
aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body p-4">
|
|
<form id="adjustment-registration-form" onsubmit="submitRegistrationForm(event)"
|
|
enctype="multipart/form-data">
|
|
<!-- Hidden ID input for editing -->
|
|
<input type="hidden" id="form-id" name="id">
|
|
|
|
<div class="row g-4">
|
|
<!-- File Number -->
|
|
<div class="col-md-6">
|
|
<label for="form-file-data-num" class="form-label">File Number : *</label>
|
|
<input type="text" id="form-file-data-num" name="file_data_num"
|
|
class="form-control-custom" placeholder="Enter File Number" required>
|
|
</div>
|
|
|
|
<!-- Financial Year -->
|
|
<div class="col-md-6" id="form-fin-year-container">
|
|
<label for="form-fin-year" class="form-label">Financial Year : *</label>
|
|
<select id="form-fin-year" name="fin_year" class="form-control-custom" required>
|
|
<option value="">- Select Year -</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Subject -->
|
|
<div class="col-12">
|
|
<label for="form-subject" class="form-label">Subject :</label>
|
|
<input type="text" id="form-subject" name="subject" class="form-control-custom"
|
|
placeholder="Enter Subject">
|
|
</div>
|
|
|
|
<!-- AS File -->
|
|
<div class="col-md-6">
|
|
<label class="form-label">AS :</label>
|
|
<div class="file-upload-wrapper">
|
|
<div class="file-upload-btn-custom">
|
|
<i class="fa-solid fa-cloud-arrow-up"></i> Choose File
|
|
</div>
|
|
<input type="file" id="form-as-file" name="asFile" accept=".pdf"
|
|
onchange="indicateFileSelected(this)">
|
|
<div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- AS Signed Date -->
|
|
<div class="col-md-6">
|
|
<label for="form-as-date" class="form-label">AS Signed Date :</label>
|
|
<input type="date" id="form-as-date" name="as_file_gen_date"
|
|
class="form-control-custom">
|
|
</div>
|
|
|
|
<!-- Proforma Invoice File -->
|
|
<div class="col-md-6">
|
|
<label class="form-label">Proforma Invoice :</label>
|
|
<div class="file-upload-wrapper">
|
|
<div class="file-upload-btn-custom">
|
|
<i class="fa-solid fa-file-invoice"></i> Choose File
|
|
</div>
|
|
<input type="file" id="form-pi-file" name="piFile" accept=".pdf"
|
|
onchange="indicateFileSelected(this)">
|
|
<div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Proforma Invoice Amount -->
|
|
<div class="col-md-6">
|
|
<label for="form-pi-amt" class="form-label">Proforma Invoice Amount :</label>
|
|
<input type="number" step="0.01" id="form-pi-amt" name="pi_amt"
|
|
class="form-control-custom" placeholder="Enter PI amount"
|
|
oninput="calculateBalance()">
|
|
</div>
|
|
|
|
<!-- Proforma Invoice Generated Date -->
|
|
<div class="col-md-6">
|
|
<label for="form-pi-gen-date" class="form-label">Proforma Invoice Generated Date
|
|
:</label>
|
|
<input type="date" id="form-pi-gen-date" name="pi_file_gen_date"
|
|
class="form-control-custom">
|
|
</div>
|
|
|
|
<!-- Advance Type -->
|
|
<div class="col-md-6">
|
|
<label for="form-adv-type" class="form-label">Advance Type : *</label>
|
|
<select id="form-adv-type" name="adv_type_id" class="form-control-custom" required
|
|
onchange="toggleVendorNameVisibility()">
|
|
<option value="">- Select Type -</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-md-6" id="vendor-name-container" style="display: none;">
|
|
<label for="form-vend-name" class="form-label">Vendor Name *</label>
|
|
<input type="text" id="form-vend-name" name="vend_name" class="form-control-custom"
|
|
placeholder="Enter Vendor Name">
|
|
</div>
|
|
|
|
<!-- Fund Type -->
|
|
<div class="col-md-6">
|
|
<label for="form-fund-type" class="form-label">Fund : *</label>
|
|
<select id="form-fund-type" name="fund_type_id" class="form-control-custom" required>
|
|
<option value="">-- Select Fund --</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- A/C Code -->
|
|
<div class="col-md-6">
|
|
<label for="form-ac-code" class="form-label">A/C Code :</label>
|
|
<input type="text" id="form-ac-code" name="ac_code" class="form-control-custom"
|
|
placeholder="Enter A/C Code">
|
|
</div>
|
|
|
|
<!-- Has File Available -->
|
|
<div class="col-md-6">
|
|
<label for="form-file-available-status" class="form-label">Has File Available :</label>
|
|
<select id="form-file-available-status" name="file_available_status"
|
|
class="form-control-custom">
|
|
<option value="">- Select -</option>
|
|
<option value="Yes">Yes</option>
|
|
<option value="No">No</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- RTGS File -->
|
|
<div class="col-md-6 edit-only-field">
|
|
<label class="form-label">RTGS File :</label>
|
|
<div class="file-upload-wrapper">
|
|
<div class="file-upload-btn-custom">
|
|
<i class="fa-solid fa-cloud-arrow-up"></i> Choose File
|
|
</div>
|
|
<input type="file" id="form-rtgs-file" name="rtgsFile" accept=".pdf"
|
|
onchange="indicateFileSelected(this)">
|
|
<div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- RTGS Upload Date -->
|
|
<div class="col-md-6 edit-only-field">
|
|
<label for="form-rtgs-date" class="form-label">RTGS Upload Date :</label>
|
|
<input type="date" id="form-rtgs-date" name="rtgs_date" class="form-control-custom">
|
|
</div>
|
|
|
|
<!-- Purchase Order File -->
|
|
<div class="col-md-6 edit-only-field">
|
|
<label class="form-label">Purchase Order File :</label>
|
|
<div class="file-upload-wrapper">
|
|
<div class="file-upload-btn-custom">
|
|
<i class="fa-solid fa-cloud-arrow-up"></i> Choose File
|
|
</div>
|
|
<input type="file" id="form-po-file" name="poFile" accept=".pdf"
|
|
onchange="indicateFileSelected(this)">
|
|
<div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Delivery Challan File -->
|
|
<div class="col-md-6 edit-only-field">
|
|
<label class="form-label">Delivery Challan File :</label>
|
|
<div class="file-upload-wrapper">
|
|
<div class="file-upload-btn-custom">
|
|
<i class="fa-solid fa-cloud-arrow-up"></i> Choose File
|
|
</div>
|
|
<input type="file" id="form-dc-file" name="dcFile" accept=".pdf"
|
|
onchange="indicateFileSelected(this)">
|
|
<div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- DC Completed Date -->
|
|
<div class="col-md-6 edit-only-field">
|
|
<label for="form-dc-completed-date" class="form-label">DC Completed Date :</label>
|
|
<input type="date" id="form-dc-completed-date" name="dc_completed_date"
|
|
class="form-control-custom">
|
|
</div>
|
|
|
|
<!-- Stock Register Copy File -->
|
|
<div class="col-md-6 edit-only-field">
|
|
<label class="form-label">Stock Register Copy File :</label>
|
|
<div class="file-upload-wrapper">
|
|
<div class="file-upload-btn-custom">
|
|
<i class="fa-solid fa-cloud-arrow-up"></i> Choose File
|
|
</div>
|
|
<input type="file" id="form-sr-file" name="srFile" accept=".pdf"
|
|
onchange="indicateFileSelected(this)">
|
|
<div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Installation Report File -->
|
|
<div class="col-md-6 edit-only-field">
|
|
<label class="form-label">Installation Report File :</label>
|
|
<div class="file-upload-wrapper">
|
|
<div class="file-upload-btn-custom">
|
|
<i class="fa-solid fa-cloud-arrow-up"></i> Choose File
|
|
</div>
|
|
<input type="file" id="form-ir-file" name="irFile" accept=".pdf"
|
|
onchange="indicateFileSelected(this)">
|
|
<div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Final Invoice File -->
|
|
<div class="col-md-6 edit-only-field">
|
|
<label class="form-label">Final Invoice File :</label>
|
|
<div class="file-upload-wrapper">
|
|
<div class="file-upload-btn-custom">
|
|
<i class="fa-solid fa-cloud-arrow-up"></i> Choose File
|
|
</div>
|
|
<input type="file" id="form-fi-file" name="fiFile" accept=".pdf"
|
|
onchange="indicateFileSelected(this)">
|
|
<div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Final Invoice Amount -->
|
|
<div class="col-md-6 edit-only-field">
|
|
<label for="form-fi-amt" class="form-label">Final Invoice Amount :</label>
|
|
<input type="number" step="0.01" id="form-fi-amt" name="fi_amt"
|
|
class="form-control-custom" placeholder="Enter FI amount"
|
|
oninput="calculateBalance()">
|
|
</div>
|
|
|
|
<!-- Final Invoice Generated Date -->
|
|
<div class="col-md-6 edit-only-field">
|
|
<label for="form-fi-gen-date" class="form-label">Final Invoice Generated Date :</label>
|
|
<input type="date" id="form-fi-gen-date" name="fi_file_gen_date"
|
|
class="form-control-custom">
|
|
</div>
|
|
|
|
<!-- Balance Amount -->
|
|
<div class="col-md-6 edit-only-field">
|
|
<label for="form-bal-amt" class="form-label">Balance Amount :</label>
|
|
<input type="number" step="0.01" id="form-bal-amt" name="bal_amt"
|
|
class="form-control-custom" placeholder="0" readonly>
|
|
</div>
|
|
|
|
<!-- Adjustment Order File -->
|
|
<div class="col-md-6 edit-only-field">
|
|
<label class="form-label">Adjustment Order :</label>
|
|
<div class="file-upload-wrapper">
|
|
<div class="file-upload-btn-custom">
|
|
<i class="fa-solid fa-cloud-arrow-up"></i> Choose File
|
|
</div>
|
|
<input type="file" id="form-as-final-file" name="asFinalFile" accept=".pdf"
|
|
onchange="indicateFileSelected(this)">
|
|
<div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Adjustment Order Signed Date -->
|
|
<div class="col-md-6 edit-only-field">
|
|
<label for="form-as-final-date" class="form-label">Adjustment Order Signed Date
|
|
:</label>
|
|
<input type="date" id="form-as-final-date" name="as_final_file_gen_date"
|
|
class="form-control-custom">
|
|
</div>
|
|
|
|
<!-- Treasury Challan File -->
|
|
<div class="col-md-6 edit-only-field">
|
|
<label class="form-label">Treasury Challan :</label>
|
|
<div class="file-upload-wrapper">
|
|
<div class="file-upload-btn-custom">
|
|
<i class="fa-solid fa-cloud-arrow-up"></i> Choose File
|
|
</div>
|
|
<input type="file" id="form-tc-file" name="tcFile" accept=".pdf"
|
|
onchange="indicateFileSelected(this)">
|
|
<div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Treasury Challan Signed Date -->
|
|
<div class="col-md-6 edit-only-field">
|
|
<label for="form-tc-date" class="form-label">Treasury Challan Signed Date :</label>
|
|
<input type="date" id="form-tc-date" name="tc_file_gen_date"
|
|
class="form-control-custom">
|
|
</div>
|
|
|
|
<!-- Remarks -->
|
|
<div class="col-12">
|
|
<label for="form-file-remark" class="form-label">Remarks :*</label>
|
|
<textarea id="form-file-remark" name="file_remark" class="form-control-custom"
|
|
style="height: 100px; padding: 12px;" placeholder="Remarks :*" required></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-flex justify-content-center gap-3 mt-5">
|
|
<button type="submit" class="btn-custom btn-custom-primary px-5 py-2.5 fs-6"
|
|
style="background-color: #005a70 !important;">
|
|
<i class="fa-solid fa-circle-check"></i> Submit
|
|
</button>
|
|
<button type="button" class="btn-custom btn-custom-secondary px-5 py-2.5 fs-6"
|
|
data-bs-dismiss="modal">
|
|
<i class="fa-solid fa-circle-xmark"></i> Cancel
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal: View Document Details / Checklist Timeline -->
|
|
<div class="modal fade" id="viewDetailsModal" tabindex="-1" aria-labelledby="viewDetailsModalLabel"
|
|
aria-hidden="true">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content" style="border-radius: 16px;">
|
|
<div class="modal-header bg-dark text-white" style="border-radius: 16px 16px 0 0;">
|
|
<h5 class="modal-title" id="viewDetailsModalLabel"><i class="fa-solid fa-circle-info text-info"></i>
|
|
File Complete Lifecycle</h5>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"
|
|
aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body p-4" id="modal-details-content">
|
|
<!-- Dynamic Checklist will load here -->
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal: Latest Updates Files List -->
|
|
<div class="modal fade" id="latestUpdatesModal" tabindex="-1" aria-labelledby="latestUpdatesModalLabel"
|
|
aria-hidden="true">
|
|
<div class="modal-dialog modal-xl modal-dialog-centered">
|
|
<div class="modal-content" style="border-radius: 16px;">
|
|
<div class="modal-header bg-dark text-white" style="border-radius: 16px 16px 0 0;">
|
|
<h5 class="modal-title" id="latestUpdatesModalLabel">
|
|
<i class="fa-solid fa-bell text-warning me-2"></i> <span id="latest-updates-modal-title">Latest
|
|
Updates</span>
|
|
</h5>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"
|
|
aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body p-4">
|
|
<div class="table-responsive table-responsive-custom">
|
|
<table class="table table-custom text-nowrap align-middle">
|
|
<thead>
|
|
<tr>
|
|
<th class="text-center">S.No</th>
|
|
<th class="text-center">Fin Year</th>
|
|
<th>File Num</th>
|
|
<th>Vendor Name</th>
|
|
<th class="text-center">Advance Type</th>
|
|
<th class="text-center">Update Status</th>
|
|
<th class="text-center">File Available</th>
|
|
<th class="text-end">Bal Amount</th>
|
|
<th>Remarks</th>
|
|
<th>createdDate</th>
|
|
<th>Updated By</th>
|
|
<th>Updated Date</th>
|
|
<th class="text-center">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="latest-updates-modal-body">
|
|
<!-- Loaded dynamically -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bootstrap Bundle with Popper -->
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
|
|
|
<!-- SPA Application JS Logic -->
|
|
<script th:inline="javascript">
|
|
const contextPath = /*[[@{/}]]*/ '';
|
|
let allRecords = [];
|
|
let allRecordsIncludingDeleted = [];
|
|
let filteredRecords = [];
|
|
let activeVendorFilter = 'All';
|
|
let currentTab = 'counts';
|
|
let currentSplitFilter = 'All';
|
|
let detailsModalObj = null;
|
|
let registrationModalObj = null;
|
|
let currentPage = 1;
|
|
let pageSize = 15;
|
|
let sortField = 'fin_year';
|
|
let sortDirection = 'desc';
|
|
|
|
// Dashboard specific view state
|
|
let dashboardViewMode = 'details';
|
|
let dashCurrentPage = 1;
|
|
let dashPageSize = 15;
|
|
let dashSortField = 'fin_year';
|
|
let dashSortDirection = 'desc';
|
|
let dashFilteredRecords = [];
|
|
|
|
// On document load
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
populateFinancialYears();
|
|
loadInitialDropdowns();
|
|
loadRecordsData();
|
|
detailsModalObj = new bootstrap.Modal(document.getElementById('viewDetailsModal'));
|
|
registrationModalObj = new bootstrap.Modal(document.getElementById('registrationModal'));
|
|
});
|
|
|
|
function showLoader(show) {
|
|
document.getElementById('loader-overlay').style.display = show ? 'flex' : 'none';
|
|
}
|
|
|
|
// Generate Financial Years dynamically from 2005 - 2006 to current
|
|
function populateFinancialYears() {
|
|
const selectFilter = document.getElementById('filter-fin-year');
|
|
const selectForm = document.getElementById('form-fin-year');
|
|
|
|
if (selectFilter) {
|
|
selectFilter.innerHTML = '<option value="All">All</option>';
|
|
}
|
|
if (selectForm) {
|
|
selectForm.innerHTML = '<option value="">- Select Year -</option>';
|
|
}
|
|
|
|
const startYear = 2005;
|
|
const currentYear = new Date().getFullYear() + 2;
|
|
|
|
for (let y = startYear; y <= currentYear; y++) {
|
|
const fy = `${y} - ${y + 1}`;
|
|
|
|
if (selectFilter) {
|
|
const opt = document.createElement('option');
|
|
opt.value = fy;
|
|
opt.textContent = fy;
|
|
selectFilter.appendChild(opt);
|
|
}
|
|
|
|
if (selectForm) {
|
|
const opt = document.createElement('option');
|
|
opt.value = fy;
|
|
opt.textContent = fy;
|
|
selectForm.appendChild(opt);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Toggle SPA tabs
|
|
function switchView(tabName) {
|
|
currentTab = tabName;
|
|
document.querySelectorAll('.nav-pill-btn').forEach(btn => btn.classList.remove('active'));
|
|
document.querySelectorAll('.view-panel').forEach(panel => panel.classList.remove('active'));
|
|
|
|
if (tabName === 'counts') {
|
|
document.getElementById('tab-counts').classList.add('active');
|
|
document.getElementById('panel-counts').classList.add('active');
|
|
renderCountsDashboard();
|
|
setDashboardViewMode(dashboardViewMode);
|
|
} else if (tabName === 'list') {
|
|
document.getElementById('tab-list').classList.add('active');
|
|
document.getElementById('panel-list').classList.add('active');
|
|
filterRecordsList();
|
|
} else if (tabName === 'oc') {
|
|
document.getElementById('tab-oc').classList.add('active');
|
|
document.getElementById('panel-oc').classList.add('active');
|
|
filterOcList();
|
|
} else if (tabName === 'ejv') {
|
|
document.getElementById('tab-ejv').classList.add('active');
|
|
document.getElementById('panel-ejv').classList.add('active');
|
|
resetEJVForm();
|
|
}
|
|
}
|
|
|
|
// File indicator selection text
|
|
function indicateFileSelected(input) {
|
|
const indicator = input.parentElement.querySelector('.file-selected-indicator');
|
|
if (indicator) {
|
|
if (input.files && input.files[0]) {
|
|
indicator.querySelector('span').textContent = input.files[0].name;
|
|
indicator.style.display = 'inline-flex';
|
|
} else {
|
|
indicator.style.display = 'none';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Populate Select File Number for Update EJV
|
|
function populateEJVFileDropdown() {
|
|
const datalist = document.getElementById('file-numbers-list');
|
|
if (!datalist) return;
|
|
|
|
datalist.innerHTML = '';
|
|
|
|
const sortedRecords = [...allRecords].sort((a, b) => {
|
|
const numA = String(a.file_data_num || '').toLowerCase();
|
|
const numB = String(b.file_data_num || '').toLowerCase();
|
|
return numA.localeCompare(numB);
|
|
});
|
|
|
|
sortedRecords.forEach(r => {
|
|
if (r.file_data_num) {
|
|
const opt = document.createElement('option');
|
|
opt.value = r.file_data_num;
|
|
datalist.appendChild(opt);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Handle File Number change in Update EJV view
|
|
function handleEJVFileNumberInput() {
|
|
const input = document.getElementById('ejv-file-num-input');
|
|
const hiddenId = document.getElementById('ejv-file-id');
|
|
const statusInput = document.getElementById('ejv-adj-order-status');
|
|
const fileInput = document.getElementById('ejv-num-file');
|
|
const numInput = document.getElementById('ejv-number-input');
|
|
|
|
if (!input || !hiddenId || !statusInput || !fileInput || !numInput) return;
|
|
|
|
const typedVal = input.value.trim().toLowerCase();
|
|
if (!typedVal) {
|
|
hiddenId.value = '';
|
|
statusInput.value = '';
|
|
fileInput.value = '';
|
|
fileInput.setAttribute('disabled', 'true');
|
|
fileInput.required = false;
|
|
numInput.value = '';
|
|
numInput.setAttribute('disabled', 'true');
|
|
|
|
const container = fileInput.closest('.file-upload-wrapper');
|
|
if (container) {
|
|
const ind = container.querySelector('.file-selected-indicator');
|
|
if (ind) ind.style.display = 'none';
|
|
}
|
|
return;
|
|
}
|
|
|
|
const r = allRecords.find(item => String(item.file_data_num || '').trim().toLowerCase() === typedVal);
|
|
if (!r) {
|
|
hiddenId.value = '';
|
|
statusInput.value = '';
|
|
fileInput.value = '';
|
|
fileInput.setAttribute('disabled', 'true');
|
|
fileInput.required = false;
|
|
numInput.value = '';
|
|
numInput.setAttribute('disabled', 'true');
|
|
|
|
const container = fileInput.closest('.file-upload-wrapper');
|
|
if (container) {
|
|
const ind = container.querySelector('.file-selected-indicator');
|
|
if (ind) ind.style.display = 'none';
|
|
}
|
|
return;
|
|
}
|
|
|
|
hiddenId.value = r.id;
|
|
|
|
// Check condition: as_final_file_gen_date and as_final_file_path is not null/empty/'-'
|
|
const finalPath = r.as_final_file_path;
|
|
const finalDate = r.as_final_file_gen_date;
|
|
|
|
const isFinalUploaded = finalPath && finalPath !== '-' && finalPath !== 'null' && String(finalPath).trim() !== '' &&
|
|
finalDate && finalDate !== '-' && finalDate !== 'null' && String(finalDate).trim() !== '';
|
|
|
|
if (isFinalUploaded) {
|
|
statusInput.value = 'Yes';
|
|
fileInput.removeAttribute('disabled');
|
|
numInput.removeAttribute('disabled');
|
|
|
|
numInput.value = (r.ejv_num && r.ejv_num !== '-') ? r.ejv_num : '';
|
|
|
|
const hasExistingFile = r.ejv_num_file_path && r.ejv_num_file_path !== '-' && r.ejv_num_file_path !== 'null';
|
|
fileInput.required = !hasExistingFile;
|
|
|
|
const container = fileInput.closest('.file-upload-wrapper');
|
|
if (container) {
|
|
const ind = container.querySelector('.file-selected-indicator');
|
|
if (ind) {
|
|
if (hasExistingFile) {
|
|
ind.style.display = 'inline-block';
|
|
const fileName = r.ejv_num_file_path.substring(r.ejv_num_file_path.lastIndexOf('/') + 1);
|
|
ind.querySelector('span').textContent = 'Existing: ' + fileName;
|
|
} else {
|
|
ind.style.display = 'none';
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
statusInput.value = 'No';
|
|
fileInput.value = '';
|
|
fileInput.setAttribute('disabled', 'true');
|
|
fileInput.required = false;
|
|
numInput.value = '';
|
|
numInput.setAttribute('disabled', 'true');
|
|
|
|
const container = fileInput.closest('.file-upload-wrapper');
|
|
if (container) {
|
|
const ind = container.querySelector('.file-selected-indicator');
|
|
if (ind) ind.style.display = 'none';
|
|
}
|
|
|
|
Swal.fire({
|
|
title: 'Upload Locked',
|
|
text: 'Adjustment Order (Final AS Copy) is not uploaded for this file. EJV upload is locked.',
|
|
icon: 'warning',
|
|
confirmButtonColor: '#0a4b64'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Submit EJV Update Form
|
|
function submitEJVForm(event) {
|
|
event.preventDefault();
|
|
|
|
const fileSelect = document.getElementById('ejv-file-id');
|
|
const fileInput = document.getElementById('ejv-num-file');
|
|
const numInput = document.getElementById('ejv-number-input');
|
|
const statusInput = document.getElementById('ejv-adj-order-status');
|
|
|
|
if (!fileSelect || !fileSelect.value) {
|
|
Swal.fire({ title: 'Error', text: 'Please select a valid registered file number.', icon: 'error' });
|
|
return;
|
|
}
|
|
if (statusInput.value !== 'Yes') {
|
|
Swal.fire({ title: 'Locked', text: 'EJV upload is locked because the Adjustment Order is missing.', icon: 'warning' });
|
|
return;
|
|
}
|
|
|
|
const ejvNum = numInput.value.trim();
|
|
if (!ejvNum) {
|
|
Swal.fire({ title: 'Error', text: 'Please enter EJV Number.', icon: 'error' });
|
|
return;
|
|
}
|
|
|
|
const fileId = parseInt(fileSelect.value);
|
|
const r = allRecords.find(item => item.id === fileId);
|
|
const hasExistingFile = r && r.ejv_num_file_path && r.ejv_num_file_path !== '-' && r.ejv_num_file_path !== 'null';
|
|
|
|
if (!hasExistingFile && (!fileInput.files || fileInput.files.length === 0)) {
|
|
Swal.fire({ title: 'Error', text: 'Please choose an EJV file.', icon: 'error' });
|
|
return;
|
|
}
|
|
|
|
showLoader(true);
|
|
|
|
const form = document.getElementById('ejv-update-form');
|
|
const formData = new FormData(form);
|
|
|
|
const csrfMeta = document.querySelector("meta[name='_csrf']");
|
|
const csrfHeaderMeta = document.querySelector("meta[name='_csrf_header']");
|
|
const headers = {};
|
|
if (csrfMeta && csrfHeaderMeta) {
|
|
headers[csrfHeaderMeta.getAttribute("content")] = csrfMeta.getAttribute("content");
|
|
}
|
|
|
|
fetch(contextPath + 'gccadjustmentmanagement/api/update-ejv', {
|
|
method: 'POST',
|
|
headers: headers,
|
|
body: formData
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
showLoader(false);
|
|
if (data.success) {
|
|
Swal.fire({
|
|
title: 'Success!',
|
|
text: data.message,
|
|
icon: 'success',
|
|
confirmButtonColor: '#0a4b64'
|
|
}).then(() => {
|
|
resetEJVForm();
|
|
loadRecordsData();
|
|
switchView('list');
|
|
});
|
|
} else {
|
|
Swal.fire({
|
|
title: 'Failed!',
|
|
text: data.error || 'Failed to save EJV details.',
|
|
icon: 'error',
|
|
confirmButtonColor: '#ef4444'
|
|
});
|
|
}
|
|
})
|
|
.catch(err => {
|
|
showLoader(false);
|
|
Swal.fire({
|
|
title: 'Error!',
|
|
text: 'An error occurred during submission: ' + err,
|
|
icon: 'error',
|
|
confirmButtonColor: '#ef4444'
|
|
});
|
|
});
|
|
}
|
|
|
|
// Reset EJV Form
|
|
function resetEJVForm() {
|
|
const form = document.getElementById('ejv-update-form');
|
|
if (form) form.reset();
|
|
const input = document.getElementById('ejv-file-num-input');
|
|
if (input) input.value = '';
|
|
const hiddenId = document.getElementById('ejv-file-id');
|
|
if (hiddenId) hiddenId.value = '';
|
|
handleEJVFileNumberInput();
|
|
}
|
|
|
|
// Fetch Dropdown options (Advance Types & Fund Types)
|
|
function loadInitialDropdowns() {
|
|
fetch(contextPath + 'gccadjustmentmanagement/api/advance-types')
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
const sel = document.getElementById('form-adv-type');
|
|
sel.innerHTML = '<option value="">- Select Type -</option>';
|
|
|
|
const filSel = document.getElementById('filter-advance-type');
|
|
if (filSel) filSel.innerHTML = '<option value="All">All Advance Types</option>';
|
|
|
|
const splitFilSel = document.getElementById('split-filter-advance-type');
|
|
if (splitFilSel) splitFilSel.innerHTML = '<option value="All">All Advance Types</option>';
|
|
|
|
data.forEach(t => {
|
|
sel.innerHTML += `<option value="${t.id}">${t.adv_type}</option>`;
|
|
if (filSel) filSel.innerHTML += `<option value="${t.id}">${t.adv_type}</option>`;
|
|
if (splitFilSel) splitFilSel.innerHTML += `<option value="${t.id}">${t.adv_type}</option>`;
|
|
});
|
|
});
|
|
|
|
fetch(contextPath + 'gccadjustmentmanagement/api/fund-types')
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
const sel = document.getElementById('form-fund-type');
|
|
sel.innerHTML = '<option value="">- Select Type -</option>';
|
|
|
|
const filSel = document.getElementById('filter-fund-type');
|
|
if (filSel) filSel.innerHTML = '<option value="All">All Fund Types</option>';
|
|
|
|
data.forEach(t => {
|
|
sel.innerHTML += `<option value="${t.id}">${t.fund_type}</option>`;
|
|
if (filSel) filSel.innerHTML += `<option value="${t.id}">${t.fund_type}</option>`;
|
|
});
|
|
});
|
|
}
|
|
|
|
// Populate the vendor dropdown filter dynamically
|
|
function populateVendorFilterDropdown() {
|
|
const select = document.getElementById('filter-vendor');
|
|
const splitSelect = document.getElementById('split-filter-vendor');
|
|
|
|
const curVal = select ? select.value : 'All';
|
|
const curSplitVal = splitSelect ? splitSelect.value : 'All';
|
|
|
|
if (select) select.innerHTML = '<option value="All">All Vendors</option>';
|
|
if (splitSelect) splitSelect.innerHTML = '<option value="All">All Vendors</option>';
|
|
|
|
const vendors = new Set();
|
|
allRecords.forEach(r => {
|
|
let v = String(r.vend_name || '').trim();
|
|
if (v !== '') vendors.add(v);
|
|
});
|
|
|
|
const sortedVendors = Array.from(vendors).sort();
|
|
sortedVendors.forEach(v => {
|
|
if (select) {
|
|
const opt = document.createElement('option');
|
|
opt.value = v;
|
|
opt.textContent = v;
|
|
select.appendChild(opt);
|
|
}
|
|
if (splitSelect) {
|
|
const opt = document.createElement('option');
|
|
opt.value = v;
|
|
opt.textContent = v;
|
|
splitSelect.appendChild(opt);
|
|
}
|
|
});
|
|
|
|
if (select && Array.from(select.options).some(opt => opt.value === curVal)) {
|
|
select.value = curVal;
|
|
}
|
|
if (splitSelect && Array.from(splitSelect.options).some(opt => opt.value === curSplitVal)) {
|
|
splitSelect.value = curSplitVal;
|
|
}
|
|
}
|
|
|
|
// Load all database files
|
|
function loadRecordsData() {
|
|
showLoader(true);
|
|
Promise.all([
|
|
fetch(contextPath + 'gccadjustmentmanagement/api/list').then(res => res.json()),
|
|
fetch(contextPath + 'gccadjustmentmanagement/api/list-all').then(res => res.json())
|
|
])
|
|
.then(([activeData, allData]) => {
|
|
allRecords = activeData;
|
|
allRecordsIncludingDeleted = allData;
|
|
populateVendorFilterDropdown();
|
|
populateEJVFileDropdown();
|
|
renderCountsDashboard();
|
|
updateLatestUpdateBadge();
|
|
renderSplitUpReport();
|
|
renderDashboardDetails();
|
|
filterRecordsList();
|
|
showLoader(false);
|
|
})
|
|
.catch(err => {
|
|
showLoader(false);
|
|
console.error("Error loading records:", err);
|
|
});
|
|
}
|
|
|
|
// Render dynamic counts badges in Dashboard Landing view
|
|
function renderCountsDashboard() {
|
|
const fyFilter = document.getElementById('filter-fin-year').value;
|
|
const cleanFy = str => String(str || '').replace(/\s+/g, '');
|
|
|
|
let records = allRecords;
|
|
if (fyFilter !== 'All') {
|
|
records = records.filter(r => cleanFy(r.fin_year) === cleanFy(fyFilter));
|
|
}
|
|
|
|
// Calculate vendor and adjustment status counts
|
|
const counts = {};
|
|
let total = 0;
|
|
let adjustedCount = 0;
|
|
let pendingCount = 0;
|
|
|
|
records.forEach(r => {
|
|
let v = String(r.vend_name || '').trim();
|
|
if (v === '') v = '-';
|
|
counts[v] = (counts[v] || 0) + 1;
|
|
total++;
|
|
|
|
// Check adjustment status
|
|
const ejvNum = String(r.ejv_num || '').trim();
|
|
const ejvPath = String(r.ejv_num_file_path || '').trim();
|
|
const isAdjusted = (ejvNum !== '' && ejvNum !== '-') || (ejvPath !== '' && ejvPath !== '-');
|
|
if (isAdjusted) {
|
|
adjustedCount++;
|
|
} else {
|
|
pendingCount++;
|
|
}
|
|
});
|
|
|
|
const sortedVendors = Object.keys(counts).sort();
|
|
const container = document.getElementById('dashboard-counts-list');
|
|
if (!container) return;
|
|
|
|
let html = '';
|
|
|
|
// Total Files count shows records.length (representing active database files only)
|
|
const displayTotal = records.length;
|
|
|
|
// 1. Total Files Button
|
|
html += `
|
|
<div class="col-lg-4 col-md-6 col-12">
|
|
<div class="count-badge-btn justify-content-between animate-fade-in ${currentSplitFilter === 'All' ? 'active-card' : ''}"
|
|
id="card-total-files" onclick="setSplitFilter('All')">
|
|
<span class="count-card-label"><i class="fa-solid fa-folder me-2 text-primary"></i> Total Files</span>
|
|
<span class="count-card-number text-white" style="background-color: var(--primary) !important;">${displayTotal}</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// 1.1. Adjusted Files Button
|
|
html += `
|
|
<div class="col-lg-4 col-md-6 col-12">
|
|
<div class="count-badge-btn justify-content-between animate-fade-in ${currentSplitFilter === 'Adjusted' ? 'active-card' : ''}"
|
|
id="card-adjusted-files" onclick="setSplitFilter('Adjusted')">
|
|
<span class="count-card-label"><i class="fa-solid fa-circle-check me-2 text-success"></i> Adjusted Files</span>
|
|
<span class="count-card-number text-white" style="background-color: var(--success) !important;">${adjustedCount}</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// 1.2. Pending / Not Adjusted Files Button
|
|
html += `
|
|
<div class="col-lg-4 col-md-6 col-12">
|
|
<div class="count-badge-btn justify-content-between animate-fade-in ${currentSplitFilter === 'Pending' ? 'active-card' : ''}"
|
|
id="card-pending-files" onclick="setSplitFilter('Pending')">
|
|
<span class="count-card-label"><i class="fa-solid fa-circle-xmark me-2 text-danger"></i> Pending / Not Adjusted</span>
|
|
<span class="count-card-number text-white" style="background-color: var(--danger) !important;">${pendingCount}</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
// Calculate Election and Not Available Files
|
|
let electionUploaded = records.filter(r => String(r.adv_type_id) === '9').length;
|
|
let notAvailableUploaded = records.filter(r => r.file_available_status === 'No').length;
|
|
|
|
// 1.3. Election Files
|
|
html += `
|
|
<div class="col-lg-4 col-md-6 col-12">
|
|
<div class="count-badge-btn justify-content-between animate-fade-in ${currentSplitFilter === 'Election' ? 'active-card' : ''}"
|
|
id="card-election-files" onclick="setSplitFilter('Election')">
|
|
<span class="count-card-label"><i class="fa-solid fa-square-poll-vertical me-2 text-warning"></i> Election Files</span>
|
|
<span class="count-card-number text-white" style="background-color: var(--warning) !important;">${electionUploaded}</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// 1.4. Not Available Files
|
|
html += `
|
|
<div class="col-lg-4 col-md-6 col-12">
|
|
<div class="count-badge-btn justify-content-between animate-fade-in ${currentSplitFilter === 'NotAvailable' ? 'active-card' : ''}"
|
|
id="card-notavailable-files" onclick="setSplitFilter('NotAvailable')">
|
|
<span class="count-card-label"><i class="fa-solid fa-ban me-2 text-secondary"></i> Not Available Files</span>
|
|
<span class="count-card-number text-white" style="background-color: #64748b !important;">${notAvailableUploaded}</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// 2. Vendor buttons (hidden as requested)
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
function goToListViewWithFilter(vendor) {
|
|
const select = document.getElementById('filter-vendor');
|
|
if (select) {
|
|
if (vendor === 'All' || !Array.from(select.options).some(opt => opt.value === vendor)) {
|
|
select.value = 'All';
|
|
} else {
|
|
select.value = vendor;
|
|
}
|
|
}
|
|
const statusSelect = document.getElementById('filter-adjustment-status');
|
|
if (statusSelect) {
|
|
statusSelect.value = 'All';
|
|
}
|
|
const advSelect = document.getElementById('filter-advance-type');
|
|
if (advSelect) advSelect.value = 'All';
|
|
const fundSelect = document.getElementById('filter-fund-type');
|
|
if (fundSelect) fundSelect.value = 'All';
|
|
switchView('list');
|
|
}
|
|
|
|
function goToListViewWithStatusFilter(status) {
|
|
const statusSelect = document.getElementById('filter-adjustment-status');
|
|
if (statusSelect) {
|
|
statusSelect.value = status;
|
|
}
|
|
const vendorSelect = document.getElementById('filter-vendor');
|
|
if (vendorSelect) {
|
|
vendorSelect.value = 'All';
|
|
}
|
|
const advSelect = document.getElementById('filter-advance-type');
|
|
if (advSelect) advSelect.value = 'All';
|
|
const fundSelect = document.getElementById('filter-fund-type');
|
|
if (fundSelect) fundSelect.value = 'All';
|
|
switchView('list');
|
|
}
|
|
|
|
// Filter and display files list
|
|
function filterRecordsList(keepPage = false) {
|
|
if (!keepPage) {
|
|
currentPage = 1;
|
|
}
|
|
|
|
const fyFilter = document.getElementById('filter-fin-year').value;
|
|
const vendorFilter = document.getElementById('filter-vendor') ? document.getElementById('filter-vendor').value : 'All';
|
|
const searchVal = document.getElementById('table-search').value.toLowerCase().trim();
|
|
const cleanFy = str => String(str || '').replace(/\s+/g, '');
|
|
|
|
let records = allRecords;
|
|
|
|
// 1. FY Filter
|
|
if (fyFilter !== 'All') {
|
|
records = records.filter(r => cleanFy(r.fin_year) === cleanFy(fyFilter));
|
|
}
|
|
|
|
// 2. Vendor Dropdown Filter
|
|
if (vendorFilter !== 'All') {
|
|
records = records.filter(r => (r.vend_name || '').trim() === vendorFilter);
|
|
}
|
|
|
|
// 2.5. Adjustment Status Filter
|
|
const statusFilter = document.getElementById('filter-adjustment-status') ? document.getElementById('filter-adjustment-status').value : 'All';
|
|
if (statusFilter !== 'All') {
|
|
records = records.filter(r => {
|
|
const ejvNum = String(r.ejv_num || '').trim();
|
|
const ejvPath = String(r.ejv_num_file_path || '').trim();
|
|
const isAdjusted = (ejvNum !== '' && ejvNum !== '-') || (ejvPath !== '' && ejvPath !== '-');
|
|
return statusFilter === 'Adjusted' ? isAdjusted : !isAdjusted;
|
|
});
|
|
}
|
|
|
|
// 2.6. Advance Type Filter
|
|
const advTypeFilter = document.getElementById('filter-advance-type') ? document.getElementById('filter-advance-type').value : 'All';
|
|
if (advTypeFilter !== 'All') {
|
|
records = records.filter(r => String(r.adv_type_id || '').trim() === advTypeFilter);
|
|
}
|
|
|
|
// 2.7. Fund Type Filter
|
|
const fundTypeFilter = document.getElementById('filter-fund-type') ? document.getElementById('filter-fund-type').value : 'All';
|
|
if (fundTypeFilter !== 'All') {
|
|
records = records.filter(r => String(r.fund_type_id || '').trim() === fundTypeFilter);
|
|
}
|
|
|
|
// 2.8. File Available Filter
|
|
const availFilter = document.getElementById('filter-file-available') ? document.getElementById('filter-file-available').value : 'All';
|
|
if (availFilter !== 'All') {
|
|
records = records.filter(r => {
|
|
let isAvail = false;
|
|
if (r.file_available_status === 'Yes') {
|
|
isAvail = true;
|
|
} else if (r.file_available_status === 'No') {
|
|
isAvail = false;
|
|
} else {
|
|
// Check physical files
|
|
const paths = [
|
|
r.file_path, r.po_file_path, r.dc_file_path, r.ir_file_path,
|
|
r.sr_filepath, r.pi_file_path, r.fi_file_path, r.tc_file_path,
|
|
r.rtgs_file_path, r.ejv_num_file_path, r.as_file_path, r.as_final_file_path
|
|
];
|
|
isAvail = paths.some(p => p && p !== '-' && p !== 'null' && String(p).trim() !== '');
|
|
}
|
|
return availFilter === 'Available' ? isAvail : !isAvail;
|
|
});
|
|
}
|
|
|
|
// 3. Search input filter
|
|
if (searchVal !== '') {
|
|
records = records.filter(r =>
|
|
String(r.vend_name || '').toLowerCase().includes(searchVal) ||
|
|
String(r.file_data_num || '').toLowerCase().includes(searchVal) ||
|
|
String(r.subject || '').toLowerCase().includes(searchVal) ||
|
|
String(r.ejv_num || '').toLowerCase().includes(searchVal)
|
|
);
|
|
}
|
|
|
|
// 4. Sort records dynamically
|
|
records.sort((a, b) => {
|
|
let valA, valB;
|
|
if (sortField === 'fin_year') {
|
|
valA = String(a.fin_year || '').trim();
|
|
valB = String(b.fin_year || '').trim();
|
|
} else if (sortField === 'bal_amt') {
|
|
valA = Number(a.bal_amt || 0);
|
|
valB = Number(b.bal_amt || 0);
|
|
} else if (sortField === 'pi_amt') {
|
|
valA = Number(a.pi_amt || 0);
|
|
valB = Number(b.pi_amt || 0);
|
|
} else if (sortField === 'fi_amt') {
|
|
valA = Number(a.fi_amt || 0);
|
|
valB = Number(b.fi_amt || 0);
|
|
} else if (sortField === 'updated_date') {
|
|
valA = new Date(a.updated_date || a.cdate).getTime();
|
|
valB = new Date(b.updated_date || b.cdate).getTime();
|
|
} else {
|
|
valA = a.id;
|
|
valB = b.id;
|
|
}
|
|
|
|
if (valA === valB) return b.id - a.id; // secondary sort by ID descending
|
|
|
|
if (sortDirection === 'asc') {
|
|
if (typeof valA === 'string') {
|
|
return valA.localeCompare(valB);
|
|
}
|
|
return valA > valB ? 1 : -1;
|
|
} else {
|
|
if (typeof valA === 'string') {
|
|
return valB.localeCompare(valA);
|
|
}
|
|
return valA < valB ? 1 : -1;
|
|
}
|
|
});
|
|
|
|
// Store filtered records globally for Excel export
|
|
filteredRecords = records;
|
|
|
|
const totalRecords = records.length;
|
|
const startIndex = (currentPage - 1) * pageSize;
|
|
const endIndex = Math.min(startIndex + pageSize, totalRecords);
|
|
const paginatedRecords = records.slice(startIndex, endIndex);
|
|
|
|
if (currentTab === 'list') {
|
|
renderTableData(paginatedRecords, startIndex + 1);
|
|
renderPagination(totalRecords);
|
|
updateSortIcons();
|
|
} else if (currentTab === 'oc') {
|
|
// For OC Copy list, show records with ELCOT vendor primarily or filter ELCOT
|
|
const elcotRecords = records.filter(r => String(r.vend_name || '').toUpperCase().includes('ELCOT'));
|
|
renderOcTableData(elcotRecords);
|
|
}
|
|
}
|
|
|
|
function changePageSize() {
|
|
const val = document.getElementById('pagination-page-size').value;
|
|
if (val === 'All') {
|
|
pageSize = 999999;
|
|
} else {
|
|
pageSize = parseInt(val, 10);
|
|
}
|
|
currentPage = 1;
|
|
filterRecordsList(true);
|
|
}
|
|
|
|
function renderPagination(totalRecords) {
|
|
const info = document.getElementById('pagination-info');
|
|
const buttons = document.getElementById('pagination-buttons');
|
|
if (!info || !buttons) return;
|
|
|
|
const totalPages = Math.ceil(totalRecords / pageSize);
|
|
const startRange = totalRecords === 0 ? 0 : (currentPage - 1) * pageSize + 1;
|
|
const endRange = Math.min(currentPage * pageSize, totalRecords);
|
|
|
|
info.textContent = `Showing ${startRange} to ${endRange} of ${totalRecords} entries`;
|
|
|
|
buttons.innerHTML = '';
|
|
|
|
if (totalPages <= 1) {
|
|
return;
|
|
}
|
|
|
|
// Previous Button
|
|
const prevLi = document.createElement('li');
|
|
prevLi.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`;
|
|
prevLi.innerHTML = `<a class="page-link" href="javascript:void(0)" onclick="goToPage(${currentPage - 1})">«</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>
|