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

4124 lines
191 KiB

​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
  1. <!DOCTYPE html>
  2. <html xmlns:th="http://www.thymeleaf.org" lang="en">
  3. <head>
  4. <script th:inline="javascript">
  5. (function () {
  6. const currentUser = /*[[${session.adj_usrName}]]*/ null;
  7. if (currentUser && sessionStorage.getItem("adj_usrName") !== currentUser) {
  8. window.location.replace(/*[[@{/gccadjustmentmanagement/login}]]*/ '/gccadjustmentmanagement/login');
  9. }
  10. })();
  11. </script>
  12. <meta charset="UTF-8">
  13. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  14. <title>Adjustment File Management System</title>
  15. <!-- CSRF Tokens -->
  16. <meta th:if="${_csrf != null}" name="_csrf" th:content="${_csrf.token}" />
  17. <meta th:if="${_csrf != null}" name="_csrf_header" th:content="${_csrf.headerName}" />
  18. <!-- Google Fonts -->
  19. <link
  20. 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"
  21. rel="stylesheet">
  22. <!-- Bootstrap & FontAwesome -->
  23. <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
  24. <link rel="stylesheet" th:href="@{/assets/font-awesome-6.5.2/css/all.min.css}" type="text/css">
  25. <!-- SweetAlert2 -->
  26. <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
  27. <!-- SheetJS (Excel Export) -->
  28. <script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
  29. <!-- Core Style Design System -->
  30. <style>
  31. :root {
  32. --primary: #0a4b64;
  33. --primary-light: #f0f6f8;
  34. --primary-hover: #083c50;
  35. --accent: #e28b12;
  36. --accent-light: #fef8eb;
  37. --slate-50: #f8fafc;
  38. --slate-100: #f1f5f9;
  39. --slate-200: #e2e8f0;
  40. --slate-300: #cbd5e1;
  41. --slate-400: #94a3b8;
  42. --slate-600: #475569;
  43. --slate-700: #334155;
  44. --slate-800: #1e293b;
  45. --success: #10b981;
  46. --success-light: #ecfdf5;
  47. --warning: #f59e0b;
  48. --warning-light: #fffbeb;
  49. --danger: #ef4444;
  50. --danger-light: #fef2f2;
  51. --card-shadow: 0 10px 30px rgba(0, 0, 0, 0.03);
  52. --hover-shadow: 0 15px 35px rgba(10, 75, 100, 0.08);
  53. --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  54. }
  55. body {
  56. background-color: #f3f7f9;
  57. font-family: 'Poppins', sans-serif;
  58. color: var(--slate-700);
  59. min-height: 100vh;
  60. overflow-x: hidden;
  61. }
  62. /* Premium Header Styling */
  63. .header {
  64. background: linear-gradient(135deg, #06232e, #0a4b64);
  65. color: #fff;
  66. padding: 15px 30px;
  67. display: flex;
  68. justify-content: space-between;
  69. align-items: center;
  70. box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
  71. position: relative;
  72. }
  73. .logo-left {
  74. display: flex;
  75. align-items: center;
  76. gap: 15px;
  77. }
  78. .logo-left img {
  79. width: 50px;
  80. height: 50px;
  81. filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.2));
  82. }
  83. .logo-text h2 {
  84. font-family: 'Outfit', sans-serif;
  85. font-size: 1.1rem;
  86. font-weight: 800;
  87. margin: 0;
  88. line-height: 1.2;
  89. letter-spacing: 0.5px;
  90. text-transform: uppercase;
  91. }
  92. .logo-text p {
  93. font-size: 0.7rem;
  94. margin: 0;
  95. color: #8ed0e8;
  96. font-weight: 500;
  97. }
  98. .header-title {
  99. position: absolute;
  100. left: 50%;
  101. transform: translateX(-50%);
  102. text-align: center;
  103. z-index: 1;
  104. }
  105. .header-title h2 {
  106. font-family: 'Outfit', sans-serif;
  107. font-size: 1.4rem;
  108. font-weight: 800;
  109. margin: 0;
  110. color: #ffffff;
  111. letter-spacing: 0.5px;
  112. text-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
  113. }
  114. .user-menu {
  115. display: flex;
  116. align-items: center;
  117. gap: 15px;
  118. z-index: 2;
  119. }
  120. .user-badge {
  121. background: rgba(255, 255, 255, 0.12);
  122. padding: 8px 16px;
  123. border-radius: 30px;
  124. font-size: 0.85rem;
  125. font-weight: 600;
  126. display: flex;
  127. align-items: center;
  128. gap: 8px;
  129. border: 1px solid rgba(255, 255, 255, 0.1);
  130. }
  131. .btn-logout {
  132. background: #ef4444;
  133. color: #fff;
  134. border: none;
  135. padding: 8px 18px;
  136. border-radius: 30px;
  137. font-size: 0.85rem;
  138. font-weight: 600;
  139. cursor: pointer;
  140. transition: var(--transition);
  141. text-decoration: none;
  142. display: inline-flex;
  143. align-items: center;
  144. gap: 6px;
  145. box-shadow: 0 4px 10px rgba(239, 68, 68, 0.2);
  146. }
  147. .btn-logout:hover {
  148. background: #dc2626;
  149. color: #fff;
  150. transform: translateY(-2px);
  151. box-shadow: 0 6px 15px rgba(220, 38, 38, 0.3);
  152. }
  153. /* Top Module Navigation (Pills style from screenshot) */
  154. .top-navbar {
  155. background-color: #ffffff;
  156. border-bottom: 1px solid var(--slate-200);
  157. padding: 10px 30px;
  158. display: flex;
  159. justify-content: space-between;
  160. align-items: center;
  161. }
  162. .nav-pills-container {
  163. display: flex;
  164. gap: 12px;
  165. }
  166. .nav-pill-btn {
  167. background: none;
  168. border: 1px solid var(--slate-200);
  169. padding: 8px 20px;
  170. border-radius: 20px;
  171. font-size: 0.9rem;
  172. font-weight: 600;
  173. color: var(--slate-600);
  174. cursor: pointer;
  175. transition: var(--transition);
  176. display: flex;
  177. align-items: center;
  178. gap: 8px;
  179. }
  180. .nav-pill-btn:hover {
  181. background-color: var(--slate-50);
  182. color: var(--primary);
  183. border-color: var(--slate-300);
  184. }
  185. .nav-pill-btn.active {
  186. background: var(--primary);
  187. color: #ffffff;
  188. border-color: var(--primary);
  189. box-shadow: 0 4px 12px rgba(10, 75, 100, 0.2);
  190. }
  191. /* App Container & Panels */
  192. .app-content {
  193. padding: 30px;
  194. max-width: 1800px;
  195. margin: 0 auto;
  196. }
  197. .view-panel {
  198. display: none;
  199. animation: fadeIn 0.4s ease forwards;
  200. }
  201. .view-panel.active {
  202. display: block;
  203. }
  204. @keyframes fadeIn {
  205. from {
  206. opacity: 0;
  207. transform: translateY(15px);
  208. }
  209. to {
  210. opacity: 1;
  211. transform: translateY(0);
  212. }
  213. }
  214. /* Sidebar & Vendor Filtering Cards (Left side) */
  215. .filter-sidebar {
  216. background-color: #ffffff;
  217. border-radius: 20px;
  218. padding: 24px;
  219. box-shadow: var(--card-shadow);
  220. border: 1px solid rgba(10, 75, 100, 0.05);
  221. margin-bottom: 30px;
  222. }
  223. .sidebar-header-title {
  224. font-family: 'Outfit', sans-serif;
  225. font-size: 1.1rem;
  226. font-weight: 800;
  227. color: var(--primary);
  228. margin-bottom: 20px;
  229. display: flex;
  230. align-items: center;
  231. gap: 10px;
  232. border-bottom: 1.5px solid var(--slate-100);
  233. padding-bottom: 10px;
  234. }
  235. .vendor-pill {
  236. background-color: var(--slate-50);
  237. border: 1px solid var(--slate-200);
  238. padding: 10px 16px;
  239. border-radius: 12px;
  240. margin-bottom: 10px;
  241. font-size: 0.88rem;
  242. font-weight: 600;
  243. color: var(--slate-700);
  244. cursor: pointer;
  245. transition: var(--transition);
  246. display: flex;
  247. justify-content: space-between;
  248. align-items: center;
  249. }
  250. .vendor-pill:hover,
  251. .vendor-pill.active {
  252. background-color: var(--primary-light);
  253. border-color: var(--primary);
  254. color: var(--primary);
  255. transform: translateX(3px);
  256. }
  257. .vendor-count-badge {
  258. background-color: var(--primary);
  259. color: #ffffff;
  260. padding: 3px 8px;
  261. font-size: 0.75rem;
  262. font-weight: 700;
  263. border-radius: 20px;
  264. }
  265. .vendor-pill:hover .vendor-count-badge,
  266. .vendor-pill.active .vendor-count-badge {
  267. background-color: var(--accent);
  268. }
  269. /* Styling Cards */
  270. .card-custom {
  271. background: #ffffff;
  272. border-radius: 20px;
  273. box-shadow: var(--card-shadow);
  274. border: 1px solid rgba(10, 75, 100, 0.05);
  275. padding: 30px;
  276. margin-bottom: 30px;
  277. transition: var(--transition);
  278. }
  279. .card-custom:hover {
  280. box-shadow: var(--hover-shadow);
  281. }
  282. .section-header-title {
  283. font-family: 'Outfit', sans-serif;
  284. font-size: 1.5rem;
  285. font-weight: 800;
  286. color: var(--primary);
  287. margin-bottom: 25px;
  288. display: flex;
  289. align-items: center;
  290. gap: 12px;
  291. }
  292. .form-section-title {
  293. font-family: 'Outfit', sans-serif;
  294. font-size: 1.15rem;
  295. font-weight: 700;
  296. color: var(--primary);
  297. margin-top: 15px;
  298. margin-bottom: 20px;
  299. padding-bottom: 8px;
  300. border-bottom: 2px solid var(--primary-light);
  301. display: flex;
  302. align-items: center;
  303. gap: 10px;
  304. }
  305. /* Form Inputs */
  306. .form-label {
  307. font-size: 0.85rem;
  308. font-weight: 700;
  309. color: var(--slate-600);
  310. margin-bottom: 6px;
  311. text-transform: uppercase;
  312. letter-spacing: 0.3px;
  313. }
  314. .form-control-custom {
  315. height: 46px;
  316. border-radius: 10px;
  317. border: 1.5px solid var(--slate-200);
  318. padding: 8px 16px;
  319. font-size: 0.92rem;
  320. font-weight: 500;
  321. color: var(--slate-800);
  322. background-color: var(--slate-50);
  323. transition: var(--transition);
  324. width: 100%;
  325. }
  326. .form-control-custom:focus {
  327. outline: none;
  328. border-color: var(--primary);
  329. background-color: #ffffff;
  330. box-shadow: 0 0 0 3px rgba(10, 75, 100, 0.15);
  331. }
  332. /* Premium File Upload Button */
  333. .file-upload-wrapper {
  334. position: relative;
  335. margin-bottom: 15px;
  336. }
  337. .file-upload-btn-custom {
  338. height: 46px;
  339. border-radius: 10px;
  340. border: 1.5px dashed var(--slate-300);
  341. background-color: var(--slate-50);
  342. display: flex;
  343. align-items: center;
  344. justify-content: center;
  345. gap: 8px;
  346. font-size: 0.88rem;
  347. font-weight: 600;
  348. color: var(--slate-600);
  349. cursor: pointer;
  350. transition: var(--transition);
  351. }
  352. .file-upload-wrapper input[type="file"] {
  353. position: absolute;
  354. top: 0;
  355. left: 0;
  356. width: 100%;
  357. height: 100%;
  358. opacity: 0;
  359. cursor: pointer;
  360. }
  361. .file-upload-wrapper:hover .file-upload-btn-custom {
  362. background-color: var(--primary-light);
  363. border-color: var(--primary);
  364. color: var(--primary);
  365. }
  366. .file-selected-indicator {
  367. font-size: 0.72rem;
  368. color: var(--success);
  369. font-weight: 600;
  370. margin-top: 4px;
  371. display: none;
  372. align-items: center;
  373. gap: 4px;
  374. }
  375. /* Buttons styling */
  376. .btn-custom {
  377. padding: 10px 24px;
  378. border-radius: 10px;
  379. font-size: 0.92rem;
  380. font-weight: 600;
  381. transition: var(--transition);
  382. display: inline-flex;
  383. align-items: center;
  384. gap: 8px;
  385. border: none;
  386. }
  387. .btn-custom-primary {
  388. background-color: var(--primary);
  389. color: #ffffff;
  390. box-shadow: 0 4px 10px rgba(10, 75, 100, 0.2);
  391. }
  392. .btn-custom-primary:hover {
  393. background-color: var(--primary-hover);
  394. transform: translateY(-2px);
  395. box-shadow: 0 6px 15px rgba(10, 75, 100, 0.3);
  396. color: #ffffff;
  397. }
  398. .btn-custom-secondary {
  399. background-color: var(--slate-200);
  400. color: var(--slate-700);
  401. }
  402. .btn-custom-secondary:hover {
  403. background-color: var(--slate-300);
  404. transform: translateY(-2px);
  405. }
  406. /* Table styles */
  407. .table-responsive-custom {
  408. border-radius: 12px;
  409. overflow-x: auto;
  410. border: 1px solid var(--slate-200);
  411. }
  412. .table-responsive-custom::-webkit-scrollbar {
  413. height: 8px;
  414. }
  415. .table-responsive-custom::-webkit-scrollbar-track {
  416. background: var(--slate-100);
  417. border-radius: 10px;
  418. }
  419. .table-responsive-custom::-webkit-scrollbar-thumb {
  420. background: var(--slate-300);
  421. border-radius: 10px;
  422. transition: var(--transition);
  423. }
  424. .table-responsive-custom::-webkit-scrollbar-thumb:hover {
  425. background: var(--primary);
  426. }
  427. .table-custom {
  428. margin-bottom: 0;
  429. font-size: 0.88rem;
  430. }
  431. .table-custom th {
  432. background-color: var(--primary) !important;
  433. color: #ffffff !important;
  434. font-family: 'Outfit', sans-serif;
  435. font-weight: 600;
  436. padding: 14px 16px;
  437. border: none;
  438. vertical-align: middle;
  439. white-space: nowrap;
  440. }
  441. .table-custom td {
  442. padding: 14px 16px;
  443. vertical-align: middle;
  444. border-bottom: 1px solid var(--slate-200);
  445. white-space: nowrap;
  446. color: var(--slate-800);
  447. }
  448. .table-custom tr:last-child td {
  449. border-bottom: none;
  450. }
  451. .table-custom tbody tr:hover {
  452. background-color: var(--slate-50) !important;
  453. }
  454. /* Action Buttons */
  455. .action-btn-group {
  456. display: flex;
  457. gap: 6px;
  458. }
  459. .action-btn {
  460. width: 32px;
  461. height: 32px;
  462. border-radius: 8px;
  463. display: inline-flex;
  464. align-items: center;
  465. justify-content: center;
  466. color: #ffffff;
  467. text-decoration: none;
  468. transition: var(--transition);
  469. border: none;
  470. font-size: 0.85rem;
  471. }
  472. .action-btn:hover {
  473. transform: scale(1.1);
  474. color: #ffffff;
  475. }
  476. .btn-view {
  477. background-color: var(--primary);
  478. }
  479. .btn-edit {
  480. background-color: var(--accent);
  481. }
  482. .btn-delete {
  483. background-color: var(--danger);
  484. }
  485. /* Loader Overlay */
  486. #loader-overlay {
  487. position: fixed;
  488. top: 0;
  489. left: 0;
  490. width: 100vw;
  491. height: 100vh;
  492. background-color: rgba(6, 35, 46, 0.4);
  493. display: none;
  494. justify-content: center;
  495. align-items: center;
  496. z-index: 9999;
  497. backdrop-filter: blur(4px);
  498. }
  499. .loader-spinner {
  500. width: 55px;
  501. height: 55px;
  502. border: 5px solid rgba(255, 255, 255, 0.2);
  503. border-top: 5px solid #ffffff;
  504. border-radius: 50%;
  505. animation: spin 1s linear infinite;
  506. }
  507. @keyframes spin {
  508. 0% {
  509. transform: rotate(0deg);
  510. }
  511. 100% {
  512. transform: rotate(360deg);
  513. }
  514. }
  515. /* Document download/view labels */
  516. .doc-link {
  517. font-size: 0.8rem;
  518. color: var(--primary);
  519. text-decoration: none;
  520. font-weight: 600;
  521. display: inline-flex;
  522. align-items: center;
  523. gap: 4px;
  524. border: 1px solid var(--primary);
  525. padding: 3px 8px;
  526. border-radius: 6px;
  527. background-color: #ffffff;
  528. transition: var(--transition);
  529. }
  530. .doc-link:hover {
  531. background-color: var(--primary);
  532. color: #ffffff;
  533. }
  534. .doc-missing {
  535. font-size: 0.78rem;
  536. color: var(--slate-400);
  537. font-style: italic;
  538. }
  539. .count-badge-btn {
  540. background-color: #ffffff;
  541. border: 1px solid var(--slate-300);
  542. padding: 8px 16px;
  543. border-radius: 12px;
  544. font-size: 0.9rem;
  545. font-weight: 600;
  546. color: var(--primary);
  547. cursor: pointer;
  548. transition: var(--transition);
  549. display: inline-flex;
  550. align-items: center;
  551. justify-content: space-between;
  552. box-shadow: 0 4px 10px rgba(0, 0, 0, 0.02);
  553. text-decoration: none;
  554. width: 100%;
  555. }
  556. .count-badge-btn:hover {
  557. background-color: var(--primary-light);
  558. border-color: var(--primary);
  559. color: var(--primary);
  560. transform: translateY(-1px);
  561. box-shadow: 0 4px 12px rgba(10, 75, 100, 0.08);
  562. }
  563. .count-badge-btn.active-card {
  564. background-color: var(--primary-light) !important;
  565. border: 2px solid var(--primary) !important;
  566. box-shadow: 0 4px 12px rgba(10, 75, 100, 0.12) !important;
  567. transform: translateY(-1px) scale(1.01);
  568. }
  569. .count-card-label {
  570. font-family: 'Outfit', sans-serif;
  571. font-size: 0.92rem;
  572. font-weight: 700;
  573. display: flex;
  574. align-items: center;
  575. gap: 8px;
  576. }
  577. .count-card-number {
  578. background-color: var(--primary-light);
  579. color: var(--primary);
  580. padding: 2px 10px;
  581. border-radius: 12px;
  582. font-size: 0.82rem;
  583. font-weight: 800;
  584. transition: var(--transition);
  585. }
  586. .count-badge-btn:hover .count-card-number {
  587. background-color: var(--primary);
  588. color: #ffffff;
  589. }
  590. /* Pagination custom styles */
  591. .pagination .page-link {
  592. color: var(--primary);
  593. border-color: var(--slate-200);
  594. background-color: #ffffff;
  595. transition: var(--transition);
  596. }
  597. .pagination .page-item.active .page-link {
  598. background-color: var(--primary) !important;
  599. border-color: var(--primary) !important;
  600. color: #ffffff !important;
  601. }
  602. .pagination .page-link:hover {
  603. background-color: var(--primary-light);
  604. color: var(--primary);
  605. border-color: var(--primary);
  606. }
  607. .pagination .page-item.disabled .page-link {
  608. color: var(--slate-400);
  609. background-color: var(--slate-50);
  610. border-color: var(--slate-200);
  611. }
  612. /* Split-up compact table cells styling */
  613. #panel-split-content table th,
  614. #panel-split-content table td {
  615. padding: 5px 8px !important;
  616. font-size: 0.88rem !important;
  617. }
  618. /* Mobile and Tablet Responsiveness */
  619. @media (max-width: 991px) {
  620. .header {
  621. flex-direction: column;
  622. gap: 15px;
  623. padding: 15px 20px;
  624. align-items: center;
  625. text-align: center;
  626. }
  627. .logo-left {
  628. flex-direction: column;
  629. gap: 5px;
  630. text-align: center;
  631. }
  632. .logo-text h2 {
  633. font-size: 0.95rem;
  634. }
  635. .header-title {
  636. position: static;
  637. transform: none;
  638. margin: 10px 0;
  639. }
  640. .header-title h2 {
  641. font-size: 1.15rem;
  642. }
  643. .user-menu {
  644. flex-direction: row;
  645. justify-content: center;
  646. width: 100%;
  647. gap: 10px;
  648. }
  649. .user-badge {
  650. padding: 6px 12px;
  651. font-size: 0.8rem;
  652. }
  653. .btn-logout {
  654. padding: 6px 14px;
  655. font-size: 0.8rem;
  656. }
  657. .top-navbar {
  658. padding: 10px 15px;
  659. overflow-x: auto;
  660. display: block;
  661. }
  662. .nav-pills-container {
  663. display: inline-flex;
  664. flex-wrap: nowrap;
  665. padding-bottom: 5px;
  666. width: max-content;
  667. }
  668. .nav-pill-btn {
  669. padding: 6px 14px;
  670. font-size: 0.8rem;
  671. white-space: nowrap;
  672. }
  673. }
  674. @media (max-width: 767px) {
  675. .app-content {
  676. padding: 12px;
  677. }
  678. .card-custom {
  679. padding: 15px;
  680. border-radius: 12px;
  681. margin-bottom: 15px;
  682. }
  683. .section-header-title {
  684. font-size: 1.2rem;
  685. margin-bottom: 15px;
  686. flex-direction: column;
  687. align-items: flex-start;
  688. gap: 5px;
  689. }
  690. .count-badge-btn {
  691. padding: 10px 14px;
  692. }
  693. .count-card-label {
  694. font-size: 0.82rem;
  695. }
  696. .count-card-number {
  697. padding: 1px 8px;
  698. font-size: 0.78rem;
  699. }
  700. /* Table filters grid layout */
  701. .view-panel .card-custom .d-flex.gap-2.align-items-center.flex-wrap {
  702. width: 100%;
  703. display: grid !important;
  704. grid-template-columns: 1fr 1fr;
  705. gap: 8px !important;
  706. }
  707. .view-panel .card-custom .d-flex.gap-2.align-items-center.flex-wrap select,
  708. .view-panel .card-custom .d-flex.gap-2.align-items-center.flex-wrap input,
  709. .view-panel .card-custom .d-flex.gap-2.align-items-center.flex-wrap button {
  710. width: 100% !important;
  711. height: 38px !important;
  712. font-size: 0.85rem !important;
  713. margin: 0 !important;
  714. }
  715. .view-panel .card-custom .d-flex.gap-2.align-items-center.flex-wrap input {
  716. grid-column: span 2;
  717. }
  718. .view-panel .card-custom .d-flex.gap-2.align-items-center.flex-wrap button {
  719. grid-column: span 2;
  720. }
  721. /* Split up report filters stack */
  722. .bg-light.p-2.rounded.d-flex {
  723. display: grid !important;
  724. grid-template-columns: 1fr;
  725. gap: 10px !important;
  726. width: 100%;
  727. }
  728. .bg-light.p-2.rounded.d-flex .d-flex {
  729. width: 100%;
  730. justify-content: space-between;
  731. }
  732. .bg-light.p-2.rounded.d-flex select {
  733. width: 70% !important;
  734. }
  735. /* Pagination stacked layout */
  736. .mt-4.flex-wrap {
  737. flex-direction: column !important;
  738. align-items: center !important;
  739. text-align: center;
  740. gap: 15px !important;
  741. }
  742. }
  743. </style>
  744. </head>
  745. <body>
  746. <!-- Loader Overlay -->
  747. <div id="loader-overlay">
  748. <div class="loader-spinner"></div>
  749. </div>
  750. <!-- Header block containing title & logo -->
  751. <div class="header">
  752. <div class="logo-left">
  753. <img th:src="@{/assets/header-images/gcc1.png}" alt="Greater Chennai Corporation Logo">
  754. <div class="logo-text">
  755. <h2>GREATER</h2>
  756. <h2>CHENNAI</h2>
  757. <h2>CORPORATION</h2>
  758. <p>பெருநகர சென்னை மாநகராட்சி</p>
  759. </div>
  760. </div>
  761. <div class="header-title">
  762. <h2>Adjustment File Management System</h2>
  763. </div>
  764. <div class="user-menu" style="display: flex; align-items: center; gap: 15px;">
  765. <!-- Latest Update Badge -->
  766. <div id="latest-update-badge" class="d-flex align-items-center gap-2 p-2 px-3 rounded text-white"
  767. 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;"
  768. onclick="openLatestUpdatesModal()">
  769. <i class="fa-solid fa-bell text-warning fa-bounce animate-bounce"></i>
  770. <div>
  771. Latest Update: <span class="fw-bold text-warning" id="latest-update-count">0</span> File(s) on
  772. <span class="fw-semibold" id="latest-update-time">-</span>
  773. </div>
  774. </div>
  775. <span class="user-badge">
  776. <i class="fa-solid fa-user-shield"></i>
  777. <span th:text="${usrName}">Guest</span>
  778. </span>
  779. <a th:href="@{/gccadjustmentmanagement/logout}" class="btn-logout">
  780. <i class="fa-solid fa-right-from-bracket"></i> Logout
  781. </a>
  782. </div>
  783. </div>
  784. <!-- Top navigation tabs -->
  785. <div class="top-navbar">
  786. <div class="nav-pills-container">
  787. <button class="nav-pill-btn active" id="tab-counts" onclick="switchView('counts')">
  788. <i class="fa-solid fa-chart-simple"></i> Counts Dashboard
  789. </button>
  790. <button class="nav-pill-btn" id="tab-reg" onclick="openRegistrationModal()">
  791. <i class="fa-solid fa-file-signature"></i> New File Registration
  792. </button>
  793. <button class="nav-pill-btn" id="tab-list" onclick="switchView('list')">
  794. <i class="fa-solid fa-folder-open"></i> Existing File Details
  795. </button>
  796. <!-- <button class="nav-pill-btn" id="tab-oc" onclick="switchView('oc')">
  797. <i class="fa-solid fa-copy"></i> OC Copies Details
  798. </button> -->
  799. <button class="nav-pill-btn" id="tab-ejv" onclick="switchView('ejv')">
  800. <i class="fa-solid fa-file-pen"></i> Update EJV Number
  801. </button>
  802. </div>
  803. <div class="d-flex align-items-center gap-3" style="display: none !important;">
  804. <!-- Financial Year selector -->
  805. <label for="filter-fin-year" class="fw-bold text-secondary mb-0"
  806. style="font-size: 0.85rem; text-transform: uppercase;">Financial Year :</label>
  807. <select id="filter-fin-year" class="form-control-custom"
  808. style="width: 150px; height: 38px; padding: 4px 12px;" onchange="loadRecordsData()">
  809. <option value="All">All</option>
  810. </select>
  811. <button class="btn btn-primary btn-sm rounded-pill px-3 py-1.5 fw-semibold"
  812. onclick="loadRecordsData()">Apply</button>
  813. </div>
  814. </div>
  815. <div class="app-content">
  816. <div class="row">
  817. <!-- Main Panel (Centered Full Width) -->
  818. <div class="col-12" id="main-content-col">
  819. <!-- 0. VIEW PANEL: COUNTS DASHBOARD -->
  820. <div class="view-panel active" id="panel-counts">
  821. <div class="card-custom py-2 mb-2">
  822. <div class="d-flex justify-content-between align-items-center px-4 mb-2">
  823. <div style="width: 80px;"></div> <!-- Spacer to balance the right button -->
  824. <div class="section-header-title mb-0 text-center flex-grow-1">
  825. <i class="fa-solid fa-chart-simple text-primary"></i> Adjustment Summary Counts
  826. </div>
  827. <button class="btn btn-sm btn-outline-secondary rounded-pill px-3 py-1 fw-semibold"
  828. style="font-size: 0.8rem;" onclick="resetDashboardFilters()">
  829. <i class="fa-solid fa-arrow-rotate-left me-1"></i> Reset
  830. </button>
  831. </div>
  832. <div class="row g-2 justify-content-center mt-1" id="dashboard-counts-list"
  833. style="max-width: 1200px; margin: 0 auto;">
  834. <!-- Dynamic Counts loaded here -->
  835. </div>
  836. </div>
  837. <!-- Split-up & Details Report inside Counts Dashboard -->
  838. <div class="card-custom p-3">
  839. <div
  840. class="d-flex justify-content-between align-items-center mb-2 pb-1 border-bottom flex-wrap gap-3">
  841. <h4 class="text-primary fw-bold mb-0 text-center flex-grow-1"
  842. id="dash-report-title-container"
  843. style="font-family: 'Outfit', sans-serif; font-size: 1.2rem; display: flex; align-items: center; justify-content: center; gap: 8px;">
  844. <i id="dash-report-title-icon" class="fa-solid fa-folder-open text-primary"></i>
  845. <span id="dash-report-title-text">Existing Adjustment Files</span>
  846. <span id="split-filter-indicator" class="badge bg-secondary ms-2"
  847. style="font-size: 0.75rem; vertical-align: middle;">All Files</span>
  848. </h4>
  849. <div class="d-flex align-items-center gap-3">
  850. <div class="btn-group" role="group" aria-label="Dashboard Report Toggle">
  851. <input type="radio" class="btn-check" name="dash-view-mode" id="mode-details"
  852. autocomplete="off" checked onclick="setDashboardViewMode('details')">
  853. <label class="btn btn-outline-primary btn-sm px-3 rounded-start-pill"
  854. for="mode-details">
  855. <i class="fa-solid fa-list me-1"></i> File Details
  856. </label>
  857. <input type="radio" class="btn-check" name="dash-view-mode" id="mode-split"
  858. autocomplete="off" onclick="setDashboardViewMode('split')">
  859. <label class="btn btn-outline-primary btn-sm px-3 rounded-end-pill"
  860. for="mode-split">
  861. <i class="fa-solid fa-table-list me-1"></i> Split-up
  862. </label>
  863. </div>
  864. <button class="btn btn-sm btn-success px-4 py-2 rounded-pill fw-semibold"
  865. id="btn-dash-export" onclick="exportDashboardData()">
  866. <i class="fa-solid fa-file-excel me-1"></i> Export Excel
  867. </button>
  868. </div>
  869. </div>
  870. <!-- Dropdown filters for split-up report / details -->
  871. <div class="d-flex align-items-center gap-3 mb-3 flex-wrap bg-light p-2 rounded">
  872. <div class="d-flex align-items-center gap-2">
  873. <label for="split-filter-vendor" class="fw-bold text-secondary mb-0"
  874. style="font-size: 0.85rem;">Vendor:</label>
  875. <select id="split-filter-vendor" class="form-control-custom"
  876. style="width: 200px; height: 36px; padding: 2px 10px;"
  877. onchange="onDashboardFilterChange()">
  878. <option value="All">All Vendors</option>
  879. </select>
  880. </div>
  881. <div class="d-flex align-items-center gap-2">
  882. <label for="split-filter-advance-type" class="fw-bold text-secondary mb-0"
  883. style="font-size: 0.85rem;">Advance Type:</label>
  884. <select id="split-filter-advance-type" class="form-control-custom"
  885. style="width: 200px; height: 36px; padding: 2px 10px;"
  886. onchange="onDashboardFilterChange()">
  887. <option value="All">All Advance Types</option>
  888. </select>
  889. </div>
  890. <div class="d-flex align-items-center gap-2 ms-auto" id="dash-search-container">
  891. <input type="text" id="dash-table-search" class="form-control-custom"
  892. style="width: 220px; height: 36px; padding: 2px 10px;"
  893. placeholder="Search files/vendors..." oninput="renderDashboardDetails()">
  894. </div>
  895. </div>
  896. <!-- Details Table Mode -->
  897. <div id="panel-details-content">
  898. <div class="table-responsive table-responsive-custom">
  899. <table class="table table-custom text-nowrap align-middle" id="dash-records-table">
  900. <thead>
  901. <tr>
  902. <th class="text-center">S.No</th>
  903. <th class="text-center" style="cursor: pointer;"
  904. onclick="toggleDashSort('fin_year')">
  905. Fin Year <span id="dash-sort-icon-fin_year"><i
  906. class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
  907. </th>
  908. <th>File Num</th>
  909. <th class="text-center">Advance Type</th>
  910. <!-- <th class="text-center">Fund Type</th> -->
  911. <!-- <th class="text-center">A/C Code</th> -->
  912. <th class="text-center">File Available</th>
  913. <th class="text-end" style="cursor: pointer;"
  914. onclick="toggleDashSort('bal_amt')">
  915. Bal Amount <span id="dash-sort-icon-bal_amt"><i
  916. class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
  917. </th>
  918. <th class="text-end" style="cursor: pointer;"
  919. onclick="toggleDashSort('pi_amt')">
  920. PI Amount <span id="dash-sort-icon-pi_amt"><i
  921. class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
  922. </th>
  923. <th class="text-end" style="cursor: pointer;"
  924. onclick="toggleDashSort('fi_amt')">
  925. FI Amount <span id="dash-sort-icon-fi_amt"><i
  926. class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
  927. </th>
  928. <!-- <th>EJV Number</th> -->
  929. <th
  930. style="white-space: normal !important; min-width: 200px; max-width: 350px;">
  931. Remarks</th>
  932. <th style="cursor: pointer;" onclick="toggleDashSort('cdate')">
  933. Created Date <span id="dash-sort-icon-cdate"><i
  934. class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
  935. </th>
  936. <th style="cursor: pointer;" onclick="toggleDashSort('updated_date')">
  937. Last Updated <span id="dash-sort-icon-updated_date"><i
  938. class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
  939. </th>
  940. <th>Updated By</th>
  941. <th class="text-center">Actions</th>
  942. </tr>
  943. </thead>
  944. <tbody id="dash-records-table-body">
  945. <!-- Loaded dynamically -->
  946. </tbody>
  947. </table>
  948. </div>
  949. <!-- Dashboard Pagination Controls -->
  950. <div class="d-flex justify-content-between align-items-center mt-4 flex-wrap gap-3">
  951. <div class="d-flex align-items-center gap-2">
  952. <span class="text-muted" style="font-size: 0.9rem;">Show</span>
  953. <select id="dash-pagination-page-size" class="form-control-custom"
  954. style="width: 80px; height: 38px; padding: 4px 8px; font-size: 0.9rem;"
  955. onchange="changeDashPageSize()">
  956. <option value="15">15</option>
  957. <option value="30">30</option>
  958. <option value="50">50</option>
  959. <option value="All">All</option>
  960. </select>
  961. <span class="text-muted" style="font-size: 0.9rem;">entries</span>
  962. </div>
  963. <div class="text-muted fw-semibold" id="dash-pagination-info"
  964. style="font-size: 0.9rem;">
  965. Showing 0 to 0 of 0 entries
  966. </div>
  967. <nav aria-label="Page navigation">
  968. <ul class="pagination pagination-sm mb-0" id="dash-pagination-buttons">
  969. <!-- Dynamic pagination links -->
  970. </ul>
  971. </nav>
  972. </div>
  973. </div>
  974. <!-- Split-up Table Mode -->
  975. <div id="panel-split-content" style="display: none;"></div>
  976. </div>
  977. </div>
  978. <!-- 2. VIEW PANEL: EXISTING FILES LIST -->
  979. <div class="view-panel" id="panel-list">
  980. <div class="card-custom">
  981. <div class="d-flex justify-content-between align-items-center mb-4 flex-wrap gap-3">
  982. <div class="section-header-title mb-0">
  983. <i class="fa-solid fa-folder-open text-primary"></i> <span id="list-title">Existing
  984. Adjustment Files</span>
  985. </div>
  986. <div class="d-flex gap-2 align-items-center flex-wrap">
  987. <select id="filter-vendor" class="form-control-custom"
  988. style="width: 180px; height: 40px;" onchange="filterRecordsList()">
  989. <option value="All">All Vendors</option>
  990. </select>
  991. <select id="filter-adjustment-status" class="form-control-custom"
  992. style="width: 180px; height: 40px;" onchange="filterRecordsList()">
  993. <option value="All">All Statuses</option>
  994. <option value="Adjusted">Adjusted Only</option>
  995. <option value="Pending">Pending/Not Adjusted</option>
  996. </select>
  997. <select id="filter-advance-type" class="form-control-custom"
  998. style="width: 180px; height: 40px;" onchange="filterRecordsList()">
  999. <option value="All">All Advance Types</option>
  1000. </select>
  1001. <select id="filter-fund-type" class="form-control-custom"
  1002. style="width: 180px; height: 40px;" onchange="filterRecordsList()">
  1003. <option value="All">All Fund Types</option>
  1004. </select>
  1005. <select id="filter-file-available" class="form-control-custom"
  1006. style="width: 180px; height: 40px;" onchange="filterRecordsList()">
  1007. <option value="All">All Availability</option>
  1008. <option value="Available">Available Files Only</option>
  1009. <option value="NotAvailable">Not Available Files Only</option>
  1010. </select>
  1011. <input type="text" id="table-search" class="form-control-custom"
  1012. style="width: 220px; height: 40px;" placeholder="Search files/vendors..."
  1013. oninput="filterRecordsList()">
  1014. <button class="btn-custom btn-custom-primary py-2" onclick="exportToExcel()">
  1015. <i class="fa-solid fa-file-excel"></i> Export Excel
  1016. </button>
  1017. </div>
  1018. </div>
  1019. <!-- Data Table -->
  1020. <div class="table-responsive table-responsive-custom">
  1021. <table class="table table-custom text-nowrap align-middle" id="main-records-table">
  1022. <thead>
  1023. <tr>
  1024. <th class="text-center">S.No</th>
  1025. <!-- <th>Vendor Name</th> -->
  1026. <th class="text-center" style="cursor: pointer;"
  1027. onclick="toggleSort('fin_year')">
  1028. Fin Year <span id="sort-icon-fin_year"><i
  1029. class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
  1030. </th>
  1031. <th>File Num</th>
  1032. <th class="text-center">Advance Type</th>
  1033. <th class="text-center">Fund Type</th>
  1034. <th class="text-center">A/C Code</th>
  1035. <th class="text-center">File Available</th>
  1036. <th class="text-end" style="cursor: pointer;" onclick="toggleSort('bal_amt')">
  1037. Bal Amount <span id="sort-icon-bal_amt"><i
  1038. class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
  1039. </th>
  1040. <th class="text-end" style="cursor: pointer;" onclick="toggleSort('pi_amt')">
  1041. PI Amount <span id="sort-icon-pi_amt"><i
  1042. class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
  1043. </th>
  1044. <th class="text-end" style="cursor: pointer;" onclick="toggleSort('fi_amt')">
  1045. FI Amount <span id="sort-icon-fi_amt"><i
  1046. class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
  1047. </th>
  1048. <th>EJV Number</th>
  1049. <th style="cursor: pointer;" onclick="toggleSort('cdate')">
  1050. Created Date <span id="sort-icon-cdate"><i
  1051. class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
  1052. </th>
  1053. <th style="cursor: pointer;" onclick="toggleSort('updated_date')">
  1054. Last Updated <span id="sort-icon-updated_date"><i
  1055. class="fa-solid fa-sort text-muted opacity-50 ms-1"></i></span>
  1056. </th>
  1057. <th>Updated By</th>
  1058. <th class="text-center">Actions</th>
  1059. </tr>
  1060. </thead>
  1061. <tbody id="records-table-body">
  1062. <!-- Loaded dynamically -->
  1063. </tbody>
  1064. </table>
  1065. </div>
  1066. <!-- Pagination Controls -->
  1067. <div class="d-flex justify-content-between align-items-center mt-4 flex-wrap gap-3">
  1068. <div class="d-flex align-items-center gap-2">
  1069. <span class="text-muted" style="font-size: 0.9rem;">Show</span>
  1070. <select id="pagination-page-size" class="form-control-custom"
  1071. style="width: 80px; height: 38px; padding: 4px 8px; font-size: 0.9rem;"
  1072. onchange="changePageSize()">
  1073. <option value="15">15</option>
  1074. <option value="30">30</option>
  1075. <option value="50">50</option>
  1076. <option value="All">All</option>
  1077. </select>
  1078. <span class="text-muted" style="font-size: 0.9rem;">entries</span>
  1079. </div>
  1080. <div class="text-muted fw-semibold" id="pagination-info" style="font-size: 0.9rem;">
  1081. Showing 0 to 0 of 0 entries
  1082. </div>
  1083. <nav aria-label="Page navigation">
  1084. <ul class="pagination pagination-sm mb-0" id="pagination-buttons">
  1085. <!-- Dynamic pagination links -->
  1086. </ul>
  1087. </nav>
  1088. </div>
  1089. </div>
  1090. </div>
  1091. <!-- 3. VIEW PANEL: ELCOT OC COPIES VIEW -->
  1092. <div class="view-panel" id="panel-oc">
  1093. <div class="card-custom">
  1094. <div class="d-flex justify-content-between align-items-center mb-4">
  1095. <div class="section-header-title mb-0">
  1096. <i class="fa-solid fa-copy text-primary"></i> Elcot OC Files Registry
  1097. </div>
  1098. <div class="d-flex gap-2">
  1099. <input type="text" id="oc-table-search" class="form-control-custom"
  1100. style="width: 250px; height: 40px;" placeholder="Search ELCOT copies..."
  1101. oninput="filterOcList()">
  1102. </div>
  1103. </div>
  1104. <!-- OC Copies Data Table -->
  1105. <div class="table-responsive table-responsive-custom">
  1106. <table class="table table-custom text-nowrap align-middle">
  1107. <thead>
  1108. <tr>
  1109. <th>ID</th>
  1110. <th>Vendor</th>
  1111. <th>Fin Year</th>
  1112. <th>File Num</th>
  1113. <th>Original File</th>
  1114. <th>PO File</th>
  1115. <th>DC File</th>
  1116. <th>Inward File</th>
  1117. <th>Stock File</th>
  1118. <th>Temp Bill</th>
  1119. <th>RTGS File</th>
  1120. <th>EJV File</th>
  1121. <th>Final AS File</th>
  1122. </tr>
  1123. </thead>
  1124. <tbody id="oc-table-body">
  1125. <!-- Loaded dynamically -->
  1126. </tbody>
  1127. </table>
  1128. </div>
  1129. </div>
  1130. </div>
  1131. <!-- 4. VIEW PANEL: UPDATE EJV NUMBER -->
  1132. <div class="view-panel" id="panel-ejv">
  1133. <div class="card-custom p-4">
  1134. <div class="d-flex justify-content-between align-items-center mb-4 pb-2 border-bottom">
  1135. <button class="btn btn-outline-secondary btn-sm px-3 rounded-pill"
  1136. onclick="switchView('list')">
  1137. <i class="fa-solid fa-arrow-left"></i> Back
  1138. </button>
  1139. <h4 class="text-primary fw-bold mb-0 text-center flex-grow-1"
  1140. style="font-family: 'Outfit', sans-serif;">Update EJV Number</h4>
  1141. <div style="width: 80px;"></div> <!-- Spacer for centering -->
  1142. </div>
  1143. <form id="ejv-update-form" onsubmit="submitEJVForm(event)" enctype="multipart/form-data">
  1144. <div class="row g-4 justify-content-center">
  1145. <!-- File Number Selector -->
  1146. <div class="col-md-5">
  1147. <label for="ejv-file-num-input" class="form-label fw-bold">Select File Number :
  1148. *</label>
  1149. <input type="text" id="ejv-file-num-input" class="form-control-custom"
  1150. list="file-numbers-list" placeholder="Type or select a file number" required
  1151. oninput="handleEJVFileNumberInput()">
  1152. <datalist id="file-numbers-list"></datalist>
  1153. <input type="hidden" id="ejv-file-id" name="id">
  1154. </div>
  1155. <!-- Adjustment Order Uploaded Status -->
  1156. <div class="col-md-5">
  1157. <label for="ejv-adj-order-status" class="form-label fw-bold">Adjustment Order
  1158. Uploaded :</label>
  1159. <input type="text" id="ejv-adj-order-status" class="form-control-custom" readonly
  1160. disabled value="No">
  1161. </div>
  1162. <!-- EJV Number File -->
  1163. <div class="col-md-5">
  1164. <label class="form-label fw-bold">EJV Number File : *</label>
  1165. <div class="file-upload-wrapper">
  1166. <div class="file-upload-btn-custom">
  1167. <i class="fa-solid fa-cloud-arrow-up"></i> Choose File
  1168. </div>
  1169. <input type="file" id="ejv-num-file" name="ejvFile" accept=".pdf"
  1170. onchange="indicateFileSelected(this)" required disabled>
  1171. <div class="file-selected-indicator"><i class="fa-solid fa-check"></i>
  1172. <span></span>
  1173. </div>
  1174. </div>
  1175. </div>
  1176. <!-- EJV Number -->
  1177. <div class="col-md-5">
  1178. <label for="ejv-number-input" class="form-label fw-bold">EJV Number : *</label>
  1179. <input type="text" id="ejv-number-input" name="ejv_num" class="form-control-custom"
  1180. placeholder="Enter EJV Number" required disabled>
  1181. </div>
  1182. <!-- Submit Button -->
  1183. <div class="col-12 text-center mt-4">
  1184. <button type="submit" class="btn-custom btn-custom-primary px-5 py-2.5">
  1185. <i class="fa-solid fa-paper-plane me-2"></i>Submit
  1186. </button>
  1187. </div>
  1188. </div>
  1189. </form>
  1190. </div>
  1191. </div>
  1192. </div>
  1193. </div>
  1194. </div>
  1195. <!-- Modal: New File Registration / Edit File -->
  1196. <div class="modal fade" id="registrationModal" tabindex="-1" aria-labelledby="registrationModalLabel"
  1197. aria-hidden="true">
  1198. <div class="modal-dialog modal-lg">
  1199. <div class="modal-content" style="border-radius: 16px;">
  1200. <div class="modal-header bg-dark text-white" style="border-radius: 16px 16px 0 0;">
  1201. <h5 class="modal-title" id="registrationModalLabel">
  1202. <i class="fa-solid fa-file-signature text-primary"></i> <span id="registration-title">New File
  1203. Registration</span>
  1204. </h5>
  1205. <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"
  1206. aria-label="Close"></button>
  1207. </div>
  1208. <div class="modal-body p-4">
  1209. <form id="adjustment-registration-form" onsubmit="submitRegistrationForm(event)"
  1210. enctype="multipart/form-data">
  1211. <!-- Hidden ID input for editing -->
  1212. <input type="hidden" id="form-id" name="id">
  1213. <div class="row g-4">
  1214. <!-- File Number -->
  1215. <div class="col-md-6">
  1216. <label for="form-file-data-num" class="form-label">File Number : *</label>
  1217. <input type="text" id="form-file-data-num" name="file_data_num"
  1218. class="form-control-custom" placeholder="Enter File Number" required>
  1219. </div>
  1220. <!-- Financial Year -->
  1221. <div class="col-md-6" id="form-fin-year-container">
  1222. <label for="form-fin-year" class="form-label">Financial Year : *</label>
  1223. <select id="form-fin-year" name="fin_year" class="form-control-custom" required>
  1224. <option value="">- Select Year -</option>
  1225. </select>
  1226. </div>
  1227. <!-- Subject -->
  1228. <div class="col-12">
  1229. <label for="form-subject" class="form-label">Subject :</label>
  1230. <input type="text" id="form-subject" name="subject" class="form-control-custom"
  1231. placeholder="Enter Subject">
  1232. </div>
  1233. <!-- AS File -->
  1234. <div class="col-md-6">
  1235. <label class="form-label">AS :</label>
  1236. <div class="file-upload-wrapper">
  1237. <div class="file-upload-btn-custom">
  1238. <i class="fa-solid fa-cloud-arrow-up"></i> Choose File
  1239. </div>
  1240. <input type="file" id="form-as-file" name="asFile" accept=".pdf"
  1241. onchange="indicateFileSelected(this)">
  1242. <div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
  1243. </div>
  1244. </div>
  1245. </div>
  1246. <!-- AS Signed Date -->
  1247. <div class="col-md-6">
  1248. <label for="form-as-date" class="form-label">AS Signed Date :</label>
  1249. <input type="date" id="form-as-date" name="as_file_gen_date"
  1250. class="form-control-custom">
  1251. </div>
  1252. <!-- Proforma Invoice File -->
  1253. <div class="col-md-6">
  1254. <label class="form-label">Proforma Invoice :</label>
  1255. <div class="file-upload-wrapper">
  1256. <div class="file-upload-btn-custom">
  1257. <i class="fa-solid fa-file-invoice"></i> Choose File
  1258. </div>
  1259. <input type="file" id="form-pi-file" name="piFile" accept=".pdf"
  1260. onchange="indicateFileSelected(this)">
  1261. <div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
  1262. </div>
  1263. </div>
  1264. </div>
  1265. <!-- Proforma Invoice Amount -->
  1266. <div class="col-md-6">
  1267. <label for="form-pi-amt" class="form-label">Proforma Invoice Amount :</label>
  1268. <input type="number" step="0.01" id="form-pi-amt" name="pi_amt"
  1269. class="form-control-custom" placeholder="Enter PI amount"
  1270. oninput="calculateBalance()">
  1271. </div>
  1272. <!-- Proforma Invoice Generated Date -->
  1273. <div class="col-md-6">
  1274. <label for="form-pi-gen-date" class="form-label">Proforma Invoice Generated Date
  1275. :</label>
  1276. <input type="date" id="form-pi-gen-date" name="pi_file_gen_date"
  1277. class="form-control-custom">
  1278. </div>
  1279. <!-- Advance Type -->
  1280. <div class="col-md-6">
  1281. <label for="form-adv-type" class="form-label">Advance Type : *</label>
  1282. <select id="form-adv-type" name="adv_type_id" class="form-control-custom" required
  1283. onchange="toggleVendorNameVisibility()">
  1284. <option value="">- Select Type -</option>
  1285. </select>
  1286. </div>
  1287. <div class="col-md-6" id="vendor-name-container" style="display: none;">
  1288. <label for="form-vend-name" class="form-label">Vendor Name *</label>
  1289. <input type="text" id="form-vend-name" name="vend_name" class="form-control-custom"
  1290. placeholder="Enter Vendor Name">
  1291. </div>
  1292. <!-- Fund Type -->
  1293. <div class="col-md-6">
  1294. <label for="form-fund-type" class="form-label">Fund : *</label>
  1295. <select id="form-fund-type" name="fund_type_id" class="form-control-custom" required>
  1296. <option value="">-- Select Fund --</option>
  1297. </select>
  1298. </div>
  1299. <!-- A/C Code -->
  1300. <div class="col-md-6">
  1301. <label for="form-ac-code" class="form-label">A/C Code :</label>
  1302. <input type="text" id="form-ac-code" name="ac_code" class="form-control-custom"
  1303. placeholder="Enter A/C Code">
  1304. </div>
  1305. <!-- Has File Available -->
  1306. <div class="col-md-6">
  1307. <label for="form-file-available-status" class="form-label">Has File Available :</label>
  1308. <select id="form-file-available-status" name="file_available_status"
  1309. class="form-control-custom">
  1310. <option value="">- Select -</option>
  1311. <option value="Yes">Yes</option>
  1312. <option value="No">No</option>
  1313. </select>
  1314. </div>
  1315. <!-- RTGS File -->
  1316. <div class="col-md-6 edit-only-field">
  1317. <label class="form-label">RTGS File :</label>
  1318. <div class="file-upload-wrapper">
  1319. <div class="file-upload-btn-custom">
  1320. <i class="fa-solid fa-cloud-arrow-up"></i> Choose File
  1321. </div>
  1322. <input type="file" id="form-rtgs-file" name="rtgsFile" accept=".pdf"
  1323. onchange="indicateFileSelected(this)">
  1324. <div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
  1325. </div>
  1326. </div>
  1327. </div>
  1328. <!-- RTGS Upload Date -->
  1329. <div class="col-md-6 edit-only-field">
  1330. <label for="form-rtgs-date" class="form-label">RTGS Upload Date :</label>
  1331. <input type="date" id="form-rtgs-date" name="rtgs_date" class="form-control-custom">
  1332. </div>
  1333. <!-- Purchase Order File -->
  1334. <div class="col-md-6 edit-only-field">
  1335. <label class="form-label">Purchase Order File :</label>
  1336. <div class="file-upload-wrapper">
  1337. <div class="file-upload-btn-custom">
  1338. <i class="fa-solid fa-cloud-arrow-up"></i> Choose File
  1339. </div>
  1340. <input type="file" id="form-po-file" name="poFile" accept=".pdf"
  1341. onchange="indicateFileSelected(this)">
  1342. <div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
  1343. </div>
  1344. </div>
  1345. </div>
  1346. <!-- Delivery Challan File -->
  1347. <div class="col-md-6 edit-only-field">
  1348. <label class="form-label">Delivery Challan File :</label>
  1349. <div class="file-upload-wrapper">
  1350. <div class="file-upload-btn-custom">
  1351. <i class="fa-solid fa-cloud-arrow-up"></i> Choose File
  1352. </div>
  1353. <input type="file" id="form-dc-file" name="dcFile" accept=".pdf"
  1354. onchange="indicateFileSelected(this)">
  1355. <div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
  1356. </div>
  1357. </div>
  1358. </div>
  1359. <!-- DC Completed Date -->
  1360. <div class="col-md-6 edit-only-field">
  1361. <label for="form-dc-completed-date" class="form-label">DC Completed Date :</label>
  1362. <input type="date" id="form-dc-completed-date" name="dc_completed_date"
  1363. class="form-control-custom">
  1364. </div>
  1365. <!-- Stock Register Copy File -->
  1366. <div class="col-md-6 edit-only-field">
  1367. <label class="form-label">Stock Register Copy File :</label>
  1368. <div class="file-upload-wrapper">
  1369. <div class="file-upload-btn-custom">
  1370. <i class="fa-solid fa-cloud-arrow-up"></i> Choose File
  1371. </div>
  1372. <input type="file" id="form-sr-file" name="srFile" accept=".pdf"
  1373. onchange="indicateFileSelected(this)">
  1374. <div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
  1375. </div>
  1376. </div>
  1377. </div>
  1378. <!-- Installation Report File -->
  1379. <div class="col-md-6 edit-only-field">
  1380. <label class="form-label">Installation Report File :</label>
  1381. <div class="file-upload-wrapper">
  1382. <div class="file-upload-btn-custom">
  1383. <i class="fa-solid fa-cloud-arrow-up"></i> Choose File
  1384. </div>
  1385. <input type="file" id="form-ir-file" name="irFile" accept=".pdf"
  1386. onchange="indicateFileSelected(this)">
  1387. <div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
  1388. </div>
  1389. </div>
  1390. </div>
  1391. <!-- Final Invoice File -->
  1392. <div class="col-md-6 edit-only-field">
  1393. <label class="form-label">Final Invoice File :</label>
  1394. <div class="file-upload-wrapper">
  1395. <div class="file-upload-btn-custom">
  1396. <i class="fa-solid fa-cloud-arrow-up"></i> Choose File
  1397. </div>
  1398. <input type="file" id="form-fi-file" name="fiFile" accept=".pdf"
  1399. onchange="indicateFileSelected(this)">
  1400. <div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
  1401. </div>
  1402. </div>
  1403. </div>
  1404. <!-- Final Invoice Amount -->
  1405. <div class="col-md-6 edit-only-field">
  1406. <label for="form-fi-amt" class="form-label">Final Invoice Amount :</label>
  1407. <input type="number" step="0.01" id="form-fi-amt" name="fi_amt"
  1408. class="form-control-custom" placeholder="Enter FI amount"
  1409. oninput="calculateBalance()">
  1410. </div>
  1411. <!-- Final Invoice Generated Date -->
  1412. <div class="col-md-6 edit-only-field">
  1413. <label for="form-fi-gen-date" class="form-label">Final Invoice Generated Date :</label>
  1414. <input type="date" id="form-fi-gen-date" name="fi_file_gen_date"
  1415. class="form-control-custom">
  1416. </div>
  1417. <!-- Balance Amount -->
  1418. <div class="col-md-6 edit-only-field">
  1419. <label for="form-bal-amt" class="form-label">Balance Amount :</label>
  1420. <input type="number" step="0.01" id="form-bal-amt" name="bal_amt"
  1421. class="form-control-custom" placeholder="0" readonly>
  1422. </div>
  1423. <!-- Adjustment Order File -->
  1424. <div class="col-md-6 edit-only-field">
  1425. <label class="form-label">Adjustment Order :</label>
  1426. <div class="file-upload-wrapper">
  1427. <div class="file-upload-btn-custom">
  1428. <i class="fa-solid fa-cloud-arrow-up"></i> Choose File
  1429. </div>
  1430. <input type="file" id="form-as-final-file" name="asFinalFile" accept=".pdf"
  1431. onchange="indicateFileSelected(this)">
  1432. <div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
  1433. </div>
  1434. </div>
  1435. </div>
  1436. <!-- Adjustment Order Signed Date -->
  1437. <div class="col-md-6 edit-only-field">
  1438. <label for="form-as-final-date" class="form-label">Adjustment Order Signed Date
  1439. :</label>
  1440. <input type="date" id="form-as-final-date" name="as_final_file_gen_date"
  1441. class="form-control-custom">
  1442. </div>
  1443. <!-- Treasury Challan File -->
  1444. <div class="col-md-6 edit-only-field">
  1445. <label class="form-label">Treasury Challan :</label>
  1446. <div class="file-upload-wrapper">
  1447. <div class="file-upload-btn-custom">
  1448. <i class="fa-solid fa-cloud-arrow-up"></i> Choose File
  1449. </div>
  1450. <input type="file" id="form-tc-file" name="tcFile" accept=".pdf"
  1451. onchange="indicateFileSelected(this)">
  1452. <div class="file-selected-indicator"><i class="fa-solid fa-check"></i> <span></span>
  1453. </div>
  1454. </div>
  1455. </div>
  1456. <!-- Treasury Challan Signed Date -->
  1457. <div class="col-md-6 edit-only-field">
  1458. <label for="form-tc-date" class="form-label">Treasury Challan Signed Date :</label>
  1459. <input type="date" id="form-tc-date" name="tc_file_gen_date"
  1460. class="form-control-custom">
  1461. </div>
  1462. <!-- Remarks -->
  1463. <div class="col-12">
  1464. <label for="form-file-remark" class="form-label">Remarks :*</label>
  1465. <textarea id="form-file-remark" name="file_remark" class="form-control-custom"
  1466. style="height: 100px; padding: 12px;" placeholder="Remarks :*" required></textarea>
  1467. </div>
  1468. </div>
  1469. <div class="d-flex justify-content-center gap-3 mt-5">
  1470. <button type="submit" class="btn-custom btn-custom-primary px-5 py-2.5 fs-6"
  1471. style="background-color: #005a70 !important;">
  1472. <i class="fa-solid fa-circle-check"></i> Submit
  1473. </button>
  1474. <button type="button" class="btn-custom btn-custom-secondary px-5 py-2.5 fs-6"
  1475. data-bs-dismiss="modal">
  1476. <i class="fa-solid fa-circle-xmark"></i> Cancel
  1477. </button>
  1478. </div>
  1479. </form>
  1480. </div>
  1481. </div>
  1482. </div>
  1483. </div>
  1484. <!-- Modal: View Document Details / Checklist Timeline -->
  1485. <div class="modal fade" id="viewDetailsModal" tabindex="-1" aria-labelledby="viewDetailsModalLabel"
  1486. aria-hidden="true">
  1487. <div class="modal-dialog modal-lg">
  1488. <div class="modal-content" style="border-radius: 16px;">
  1489. <div class="modal-header bg-dark text-white" style="border-radius: 16px 16px 0 0;">
  1490. <h5 class="modal-title" id="viewDetailsModalLabel"><i class="fa-solid fa-circle-info text-info"></i>
  1491. File Complete Lifecycle</h5>
  1492. <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"
  1493. aria-label="Close"></button>
  1494. </div>
  1495. <div class="modal-body p-4" id="modal-details-content">
  1496. <!-- Dynamic Checklist will load here -->
  1497. </div>
  1498. <div class="modal-footer">
  1499. <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
  1500. </div>
  1501. </div>
  1502. </div>
  1503. </div>
  1504. <!-- Modal: Latest Updates Files List -->
  1505. <div class="modal fade" id="latestUpdatesModal" tabindex="-1" aria-labelledby="latestUpdatesModalLabel"
  1506. aria-hidden="true">
  1507. <div class="modal-dialog modal-xl modal-dialog-centered">
  1508. <div class="modal-content" style="border-radius: 16px;">
  1509. <div class="modal-header bg-dark text-white" style="border-radius: 16px 16px 0 0;">
  1510. <h5 class="modal-title" id="latestUpdatesModalLabel">
  1511. <i class="fa-solid fa-bell text-warning me-2"></i> <span id="latest-updates-modal-title">Latest
  1512. Updates</span>
  1513. </h5>
  1514. <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"
  1515. aria-label="Close"></button>
  1516. </div>
  1517. <div class="modal-body p-4">
  1518. <div class="table-responsive table-responsive-custom">
  1519. <table class="table table-custom text-nowrap align-middle">
  1520. <thead>
  1521. <tr>
  1522. <th class="text-center">S.No</th>
  1523. <th class="text-center">Fin Year</th>
  1524. <th>File Num</th>
  1525. <th>Vendor Name</th>
  1526. <th class="text-center">Advance Type</th>
  1527. <th class="text-center">Update Status</th>
  1528. <th class="text-center">File Available</th>
  1529. <th class="text-end">Bal Amount</th>
  1530. <th>Remarks</th>
  1531. <th>createdDate</th>
  1532. <th>Updated By</th>
  1533. <th>Updated Date</th>
  1534. <th class="text-center">Actions</th>
  1535. </tr>
  1536. </thead>
  1537. <tbody id="latest-updates-modal-body">
  1538. <!-- Loaded dynamically -->
  1539. </tbody>
  1540. </table>
  1541. </div>
  1542. </div>
  1543. <div class="modal-footer">
  1544. <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
  1545. </div>
  1546. </div>
  1547. </div>
  1548. </div>
  1549. <!-- Bootstrap Bundle with Popper -->
  1550. <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
  1551. <!-- SPA Application JS Logic -->
  1552. <script th:inline="javascript">
  1553. const contextPath = /*[[@{/}]]*/ '';
  1554. let allRecords = [];
  1555. let allRecordsIncludingDeleted = [];
  1556. let filteredRecords = [];
  1557. let activeVendorFilter = 'All';
  1558. let currentTab = 'counts';
  1559. let currentSplitFilter = 'All';
  1560. let detailsModalObj = null;
  1561. let registrationModalObj = null;
  1562. let currentPage = 1;
  1563. let pageSize = 15;
  1564. let sortField = 'fin_year';
  1565. let sortDirection = 'desc';
  1566. // Dashboard specific view state
  1567. let dashboardViewMode = 'details';
  1568. let dashCurrentPage = 1;
  1569. let dashPageSize = 15;
  1570. let dashSortField = 'fin_year';
  1571. let dashSortDirection = 'desc';
  1572. let dashFilteredRecords = [];
  1573. // On document load
  1574. document.addEventListener("DOMContentLoaded", () => {
  1575. populateFinancialYears();
  1576. loadInitialDropdowns();
  1577. loadRecordsData();
  1578. detailsModalObj = new bootstrap.Modal(document.getElementById('viewDetailsModal'));
  1579. registrationModalObj = new bootstrap.Modal(document.getElementById('registrationModal'));
  1580. });
  1581. function showLoader(show) {
  1582. document.getElementById('loader-overlay').style.display = show ? 'flex' : 'none';
  1583. }
  1584. // Generate Financial Years dynamically from 2005 - 2006 to current
  1585. function populateFinancialYears() {
  1586. const selectFilter = document.getElementById('filter-fin-year');
  1587. const selectForm = document.getElementById('form-fin-year');
  1588. if (selectFilter) {
  1589. selectFilter.innerHTML = '<option value="All">All</option>';
  1590. }
  1591. if (selectForm) {
  1592. selectForm.innerHTML = '<option value="">- Select Year -</option>';
  1593. }
  1594. const startYear = 2005;
  1595. const currentYear = new Date().getFullYear() + 2;
  1596. for (let y = startYear; y <= currentYear; y++) {
  1597. const fy = `${y} - ${y + 1}`;
  1598. if (selectFilter) {
  1599. const opt = document.createElement('option');
  1600. opt.value = fy;
  1601. opt.textContent = fy;
  1602. selectFilter.appendChild(opt);
  1603. }
  1604. if (selectForm) {
  1605. const opt = document.createElement('option');
  1606. opt.value = fy;
  1607. opt.textContent = fy;
  1608. selectForm.appendChild(opt);
  1609. }
  1610. }
  1611. }
  1612. // Toggle SPA tabs
  1613. function switchView(tabName) {
  1614. currentTab = tabName;
  1615. document.querySelectorAll('.nav-pill-btn').forEach(btn => btn.classList.remove('active'));
  1616. document.querySelectorAll('.view-panel').forEach(panel => panel.classList.remove('active'));
  1617. if (tabName === 'counts') {
  1618. document.getElementById('tab-counts').classList.add('active');
  1619. document.getElementById('panel-counts').classList.add('active');
  1620. renderCountsDashboard();
  1621. setDashboardViewMode(dashboardViewMode);
  1622. } else if (tabName === 'list') {
  1623. document.getElementById('tab-list').classList.add('active');
  1624. document.getElementById('panel-list').classList.add('active');
  1625. filterRecordsList();
  1626. } else if (tabName === 'oc') {
  1627. document.getElementById('tab-oc').classList.add('active');
  1628. document.getElementById('panel-oc').classList.add('active');
  1629. filterOcList();
  1630. } else if (tabName === 'ejv') {
  1631. document.getElementById('tab-ejv').classList.add('active');
  1632. document.getElementById('panel-ejv').classList.add('active');
  1633. resetEJVForm();
  1634. }
  1635. }
  1636. // File indicator selection text
  1637. function indicateFileSelected(input) {
  1638. const indicator = input.parentElement.querySelector('.file-selected-indicator');
  1639. if (indicator) {
  1640. if (input.files && input.files[0]) {
  1641. indicator.querySelector('span').textContent = input.files[0].name;
  1642. indicator.style.display = 'inline-flex';
  1643. } else {
  1644. indicator.style.display = 'none';
  1645. }
  1646. }
  1647. }
  1648. // Populate Select File Number for Update EJV
  1649. function populateEJVFileDropdown() {
  1650. const datalist = document.getElementById('file-numbers-list');
  1651. if (!datalist) return;
  1652. datalist.innerHTML = '';
  1653. const sortedRecords = [...allRecords].sort((a, b) => {
  1654. const numA = String(a.file_data_num || '').toLowerCase();
  1655. const numB = String(b.file_data_num || '').toLowerCase();
  1656. return numA.localeCompare(numB);
  1657. });
  1658. sortedRecords.forEach(r => {
  1659. if (r.file_data_num) {
  1660. const opt = document.createElement('option');
  1661. opt.value = r.file_data_num;
  1662. datalist.appendChild(opt);
  1663. }
  1664. });
  1665. }
  1666. // Handle File Number change in Update EJV view
  1667. function handleEJVFileNumberInput() {
  1668. const input = document.getElementById('ejv-file-num-input');
  1669. const hiddenId = document.getElementById('ejv-file-id');
  1670. const statusInput = document.getElementById('ejv-adj-order-status');
  1671. const fileInput = document.getElementById('ejv-num-file');
  1672. const numInput = document.getElementById('ejv-number-input');
  1673. if (!input || !hiddenId || !statusInput || !fileInput || !numInput) return;
  1674. const typedVal = input.value.trim().toLowerCase();
  1675. if (!typedVal) {
  1676. hiddenId.value = '';
  1677. statusInput.value = '';
  1678. fileInput.value = '';
  1679. fileInput.setAttribute('disabled', 'true');
  1680. fileInput.required = false;
  1681. numInput.value = '';
  1682. numInput.setAttribute('disabled', 'true');
  1683. const container = fileInput.closest('.file-upload-wrapper');
  1684. if (container) {
  1685. const ind = container.querySelector('.file-selected-indicator');
  1686. if (ind) ind.style.display = 'none';
  1687. }
  1688. return;
  1689. }
  1690. const r = allRecords.find(item => String(item.file_data_num || '').trim().toLowerCase() === typedVal);
  1691. if (!r) {
  1692. hiddenId.value = '';
  1693. statusInput.value = '';
  1694. fileInput.value = '';
  1695. fileInput.setAttribute('disabled', 'true');
  1696. fileInput.required = false;
  1697. numInput.value = '';
  1698. numInput.setAttribute('disabled', 'true');
  1699. const container = fileInput.closest('.file-upload-wrapper');
  1700. if (container) {
  1701. const ind = container.querySelector('.file-selected-indicator');
  1702. if (ind) ind.style.display = 'none';
  1703. }
  1704. return;
  1705. }
  1706. hiddenId.value = r.id;
  1707. // Check condition: as_final_file_gen_date and as_final_file_path is not null/empty/'-'
  1708. const finalPath = r.as_final_file_path;
  1709. const finalDate = r.as_final_file_gen_date;
  1710. const isFinalUploaded = finalPath && finalPath !== '-' && finalPath !== 'null' && String(finalPath).trim() !== '' &&
  1711. finalDate && finalDate !== '-' && finalDate !== 'null' && String(finalDate).trim() !== '';
  1712. if (isFinalUploaded) {
  1713. statusInput.value = 'Yes';
  1714. fileInput.removeAttribute('disabled');
  1715. numInput.removeAttribute('disabled');
  1716. numInput.value = (r.ejv_num && r.ejv_num !== '-') ? r.ejv_num : '';
  1717. const hasExistingFile = r.ejv_num_file_path && r.ejv_num_file_path !== '-' && r.ejv_num_file_path !== 'null';
  1718. fileInput.required = !hasExistingFile;
  1719. const container = fileInput.closest('.file-upload-wrapper');
  1720. if (container) {
  1721. const ind = container.querySelector('.file-selected-indicator');
  1722. if (ind) {
  1723. if (hasExistingFile) {
  1724. ind.style.display = 'inline-block';
  1725. const fileName = r.ejv_num_file_path.substring(r.ejv_num_file_path.lastIndexOf('/') + 1);
  1726. ind.querySelector('span').textContent = 'Existing: ' + fileName;
  1727. } else {
  1728. ind.style.display = 'none';
  1729. }
  1730. }
  1731. }
  1732. } else {
  1733. statusInput.value = 'No';
  1734. fileInput.value = '';
  1735. fileInput.setAttribute('disabled', 'true');
  1736. fileInput.required = false;
  1737. numInput.value = '';
  1738. numInput.setAttribute('disabled', 'true');
  1739. const container = fileInput.closest('.file-upload-wrapper');
  1740. if (container) {
  1741. const ind = container.querySelector('.file-selected-indicator');
  1742. if (ind) ind.style.display = 'none';
  1743. }
  1744. Swal.fire({
  1745. title: 'Upload Locked',
  1746. text: 'Adjustment Order (Final AS Copy) is not uploaded for this file. EJV upload is locked.',
  1747. icon: 'warning',
  1748. confirmButtonColor: '#0a4b64'
  1749. });
  1750. }
  1751. }
  1752. // Submit EJV Update Form
  1753. function submitEJVForm(event) {
  1754. event.preventDefault();
  1755. const fileSelect = document.getElementById('ejv-file-id');
  1756. const fileInput = document.getElementById('ejv-num-file');
  1757. const numInput = document.getElementById('ejv-number-input');
  1758. const statusInput = document.getElementById('ejv-adj-order-status');
  1759. if (!fileSelect || !fileSelect.value) {
  1760. Swal.fire({ title: 'Error', text: 'Please select a valid registered file number.', icon: 'error' });
  1761. return;
  1762. }
  1763. if (statusInput.value !== 'Yes') {
  1764. Swal.fire({ title: 'Locked', text: 'EJV upload is locked because the Adjustment Order is missing.', icon: 'warning' });
  1765. return;
  1766. }
  1767. const ejvNum = numInput.value.trim();
  1768. if (!ejvNum) {
  1769. Swal.fire({ title: 'Error', text: 'Please enter EJV Number.', icon: 'error' });
  1770. return;
  1771. }
  1772. const fileId = parseInt(fileSelect.value);
  1773. const r = allRecords.find(item => item.id === fileId);
  1774. const hasExistingFile = r && r.ejv_num_file_path && r.ejv_num_file_path !== '-' && r.ejv_num_file_path !== 'null';
  1775. if (!hasExistingFile && (!fileInput.files || fileInput.files.length === 0)) {
  1776. Swal.fire({ title: 'Error', text: 'Please choose an EJV file.', icon: 'error' });
  1777. return;
  1778. }
  1779. showLoader(true);
  1780. const form = document.getElementById('ejv-update-form');
  1781. const formData = new FormData(form);
  1782. const csrfMeta = document.querySelector("meta[name='_csrf']");
  1783. const csrfHeaderMeta = document.querySelector("meta[name='_csrf_header']");
  1784. const headers = {};
  1785. if (csrfMeta && csrfHeaderMeta) {
  1786. headers[csrfHeaderMeta.getAttribute("content")] = csrfMeta.getAttribute("content");
  1787. }
  1788. fetch(contextPath + 'gccadjustmentmanagement/api/update-ejv', {
  1789. method: 'POST',
  1790. headers: headers,
  1791. body: formData
  1792. })
  1793. .then(res => res.json())
  1794. .then(data => {
  1795. showLoader(false);
  1796. if (data.success) {
  1797. Swal.fire({
  1798. title: 'Success!',
  1799. text: data.message,
  1800. icon: 'success',
  1801. confirmButtonColor: '#0a4b64'
  1802. }).then(() => {
  1803. resetEJVForm();
  1804. loadRecordsData();
  1805. switchView('list');
  1806. });
  1807. } else {
  1808. Swal.fire({
  1809. title: 'Failed!',
  1810. text: data.error || 'Failed to save EJV details.',
  1811. icon: 'error',
  1812. confirmButtonColor: '#ef4444'
  1813. });
  1814. }
  1815. })
  1816. .catch(err => {
  1817. showLoader(false);
  1818. Swal.fire({
  1819. title: 'Error!',
  1820. text: 'An error occurred during submission: ' + err,
  1821. icon: 'error',
  1822. confirmButtonColor: '#ef4444'
  1823. });
  1824. });
  1825. }
  1826. // Reset EJV Form
  1827. function resetEJVForm() {
  1828. const form = document.getElementById('ejv-update-form');
  1829. if (form) form.reset();
  1830. const input = document.getElementById('ejv-file-num-input');
  1831. if (input) input.value = '';
  1832. const hiddenId = document.getElementById('ejv-file-id');
  1833. if (hiddenId) hiddenId.value = '';
  1834. handleEJVFileNumberInput();
  1835. }
  1836. // Fetch Dropdown options (Advance Types & Fund Types)
  1837. function loadInitialDropdowns() {
  1838. fetch(contextPath + 'gccadjustmentmanagement/api/advance-types')
  1839. .then(res => res.json())
  1840. .then(data => {
  1841. const sel = document.getElementById('form-adv-type');
  1842. sel.innerHTML = '<option value="">- Select Type -</option>';
  1843. const filSel = document.getElementById('filter-advance-type');
  1844. if (filSel) filSel.innerHTML = '<option value="All">All Advance Types</option>';
  1845. const splitFilSel = document.getElementById('split-filter-advance-type');
  1846. if (splitFilSel) splitFilSel.innerHTML = '<option value="All">All Advance Types</option>';
  1847. data.forEach(t => {
  1848. sel.innerHTML += `<option value="${t.id}">${t.adv_type}</option>`;
  1849. if (filSel) filSel.innerHTML += `<option value="${t.id}">${t.adv_type}</option>`;
  1850. if (splitFilSel) splitFilSel.innerHTML += `<option value="${t.id}">${t.adv_type}</option>`;
  1851. });
  1852. });
  1853. fetch(contextPath + 'gccadjustmentmanagement/api/fund-types')
  1854. .then(res => res.json())
  1855. .then(data => {
  1856. const sel = document.getElementById('form-fund-type');
  1857. sel.innerHTML = '<option value="">- Select Type -</option>';
  1858. const filSel = document.getElementById('filter-fund-type');
  1859. if (filSel) filSel.innerHTML = '<option value="All">All Fund Types</option>';
  1860. data.forEach(t => {
  1861. sel.innerHTML += `<option value="${t.id}">${t.fund_type}</option>`;
  1862. if (filSel) filSel.innerHTML += `<option value="${t.id}">${t.fund_type}</option>`;
  1863. });
  1864. });
  1865. }
  1866. // Populate the vendor dropdown filter dynamically
  1867. function populateVendorFilterDropdown() {
  1868. const select = document.getElementById('filter-vendor');
  1869. const splitSelect = document.getElementById('split-filter-vendor');
  1870. const curVal = select ? select.value : 'All';
  1871. const curSplitVal = splitSelect ? splitSelect.value : 'All';
  1872. if (select) select.innerHTML = '<option value="All">All Vendors</option>';
  1873. if (splitSelect) splitSelect.innerHTML = '<option value="All">All Vendors</option>';
  1874. const vendors = new Set();
  1875. allRecords.forEach(r => {
  1876. let v = String(r.vend_name || '').trim();
  1877. if (v !== '') vendors.add(v);
  1878. });
  1879. const sortedVendors = Array.from(vendors).sort();
  1880. sortedVendors.forEach(v => {
  1881. if (select) {
  1882. const opt = document.createElement('option');
  1883. opt.value = v;
  1884. opt.textContent = v;
  1885. select.appendChild(opt);
  1886. }
  1887. if (splitSelect) {
  1888. const opt = document.createElement('option');
  1889. opt.value = v;
  1890. opt.textContent = v;
  1891. splitSelect.appendChild(opt);
  1892. }
  1893. });
  1894. if (select && Array.from(select.options).some(opt => opt.value === curVal)) {
  1895. select.value = curVal;
  1896. }
  1897. if (splitSelect && Array.from(splitSelect.options).some(opt => opt.value === curSplitVal)) {
  1898. splitSelect.value = curSplitVal;
  1899. }
  1900. }
  1901. // Load all database files
  1902. function loadRecordsData() {
  1903. showLoader(true);
  1904. Promise.all([
  1905. fetch(contextPath + 'gccadjustmentmanagement/api/list').then(res => res.json()),
  1906. fetch(contextPath + 'gccadjustmentmanagement/api/list-all').then(res => res.json())
  1907. ])
  1908. .then(([activeData, allData]) => {
  1909. allRecords = activeData;
  1910. allRecordsIncludingDeleted = allData;
  1911. populateVendorFilterDropdown();
  1912. populateEJVFileDropdown();
  1913. renderCountsDashboard();
  1914. updateLatestUpdateBadge();
  1915. renderSplitUpReport();
  1916. renderDashboardDetails();
  1917. filterRecordsList();
  1918. showLoader(false);
  1919. })
  1920. .catch(err => {
  1921. showLoader(false);
  1922. console.error("Error loading records:", err);
  1923. });
  1924. }
  1925. // Render dynamic counts badges in Dashboard Landing view
  1926. function renderCountsDashboard() {
  1927. const fyFilter = document.getElementById('filter-fin-year').value;
  1928. const cleanFy = str => String(str || '').replace(/\s+/g, '');
  1929. let records = allRecords;
  1930. if (fyFilter !== 'All') {
  1931. records = records.filter(r => cleanFy(r.fin_year) === cleanFy(fyFilter));
  1932. }
  1933. // Calculate vendor and adjustment status counts
  1934. const counts = {};
  1935. let total = 0;
  1936. let adjustedCount = 0;
  1937. let pendingCount = 0;
  1938. records.forEach(r => {
  1939. let v = String(r.vend_name || '').trim();
  1940. if (v === '') v = '-';
  1941. counts[v] = (counts[v] || 0) + 1;
  1942. total++;
  1943. // Check adjustment status
  1944. const ejvNum = String(r.ejv_num || '').trim();
  1945. const ejvPath = String(r.ejv_num_file_path || '').trim();
  1946. const isAdjusted = (ejvNum !== '' && ejvNum !== '-') || (ejvPath !== '' && ejvPath !== '-');
  1947. if (isAdjusted) {
  1948. adjustedCount++;
  1949. } else {
  1950. pendingCount++;
  1951. }
  1952. });
  1953. const sortedVendors = Object.keys(counts).sort();
  1954. const container = document.getElementById('dashboard-counts-list');
  1955. if (!container) return;
  1956. let html = '';
  1957. // Total Files count shows records.length (representing active database files only)
  1958. const displayTotal = records.length;
  1959. // 1. Total Files Button
  1960. html += `
  1961. <div class="col-lg-4 col-md-6 col-12">
  1962. <div class="count-badge-btn justify-content-between animate-fade-in ${currentSplitFilter === 'All' ? 'active-card' : ''}"
  1963. id="card-total-files" onclick="setSplitFilter('All')">
  1964. <span class="count-card-label"><i class="fa-solid fa-folder me-2 text-primary"></i> Total Files</span>
  1965. <span class="count-card-number text-white" style="background-color: var(--primary) !important;">${displayTotal}</span>
  1966. </div>
  1967. </div>
  1968. `;
  1969. // 1.1. Adjusted Files Button
  1970. html += `
  1971. <div class="col-lg-4 col-md-6 col-12">
  1972. <div class="count-badge-btn justify-content-between animate-fade-in ${currentSplitFilter === 'Adjusted' ? 'active-card' : ''}"
  1973. id="card-adjusted-files" onclick="setSplitFilter('Adjusted')">
  1974. <span class="count-card-label"><i class="fa-solid fa-circle-check me-2 text-success"></i> Adjusted Files</span>
  1975. <span class="count-card-number text-white" style="background-color: var(--success) !important;">${adjustedCount}</span>
  1976. </div>
  1977. </div>
  1978. `;
  1979. // 1.2. Pending / Not Adjusted Files Button
  1980. html += `
  1981. <div class="col-lg-4 col-md-6 col-12">
  1982. <div class="count-badge-btn justify-content-between animate-fade-in ${currentSplitFilter === 'Pending' ? 'active-card' : ''}"
  1983. id="card-pending-files" onclick="setSplitFilter('Pending')">
  1984. <span class="count-card-label"><i class="fa-solid fa-circle-xmark me-2 text-danger"></i> Pending / Not Adjusted</span>
  1985. <span class="count-card-number text-white" style="background-color: var(--danger) !important;">${pendingCount}</span>
  1986. </div>
  1987. </div>
  1988. `;
  1989. // Calculate Election and Not Available Files
  1990. let electionUploaded = records.filter(r => String(r.adv_type_id) === '9').length;
  1991. let notAvailableUploaded = records.filter(r => r.file_available_status === 'No').length;
  1992. // 1.3. Election Files
  1993. html += `
  1994. <div class="col-lg-4 col-md-6 col-12">
  1995. <div class="count-badge-btn justify-content-between animate-fade-in ${currentSplitFilter === 'Election' ? 'active-card' : ''}"
  1996. id="card-election-files" onclick="setSplitFilter('Election')">
  1997. <span class="count-card-label"><i class="fa-solid fa-square-poll-vertical me-2 text-warning"></i> Election Files</span>
  1998. <span class="count-card-number text-white" style="background-color: var(--warning) !important;">${electionUploaded}</span>
  1999. </div>
  2000. </div>
  2001. `;
  2002. // 1.4. Not Available Files
  2003. html += `
  2004. <div class="col-lg-4 col-md-6 col-12">
  2005. <div class="count-badge-btn justify-content-between animate-fade-in ${currentSplitFilter === 'NotAvailable' ? 'active-card' : ''}"
  2006. id="card-notavailable-files" onclick="setSplitFilter('NotAvailable')">
  2007. <span class="count-card-label"><i class="fa-solid fa-ban me-2 text-secondary"></i> Not Available Files</span>
  2008. <span class="count-card-number text-white" style="background-color: #64748b !important;">${notAvailableUploaded}</span>
  2009. </div>
  2010. </div>
  2011. `;
  2012. // 2. Vendor buttons (hidden as requested)
  2013. container.innerHTML = html;
  2014. }
  2015. function goToListViewWithFilter(vendor) {
  2016. const select = document.getElementById('filter-vendor');
  2017. if (select) {
  2018. if (vendor === 'All' || !Array.from(select.options).some(opt => opt.value === vendor)) {
  2019. select.value = 'All';
  2020. } else {
  2021. select.value = vendor;
  2022. }
  2023. }
  2024. const statusSelect = document.getElementById('filter-adjustment-status');
  2025. if (statusSelect) {
  2026. statusSelect.value = 'All';
  2027. }
  2028. const advSelect = document.getElementById('filter-advance-type');
  2029. if (advSelect) advSelect.value = 'All';
  2030. const fundSelect = document.getElementById('filter-fund-type');
  2031. if (fundSelect) fundSelect.value = 'All';
  2032. switchView('list');
  2033. }
  2034. function goToListViewWithStatusFilter(status) {
  2035. const statusSelect = document.getElementById('filter-adjustment-status');
  2036. if (statusSelect) {
  2037. statusSelect.value = status;
  2038. }
  2039. const vendorSelect = document.getElementById('filter-vendor');
  2040. if (vendorSelect) {
  2041. vendorSelect.value = 'All';
  2042. }
  2043. const advSelect = document.getElementById('filter-advance-type');
  2044. if (advSelect) advSelect.value = 'All';
  2045. const fundSelect = document.getElementById('filter-fund-type');
  2046. if (fundSelect) fundSelect.value = 'All';
  2047. switchView('list');
  2048. }
  2049. // Filter and display files list
  2050. function filterRecordsList(keepPage = false) {
  2051. if (!keepPage) {
  2052. currentPage = 1;
  2053. }
  2054. const fyFilter = document.getElementById('filter-fin-year').value;
  2055. const vendorFilter = document.getElementById('filter-vendor') ? document.getElementById('filter-vendor').value : 'All';
  2056. const searchVal = document.getElementById('table-search').value.toLowerCase().trim();
  2057. const cleanFy = str => String(str || '').replace(/\s+/g, '');
  2058. let records = allRecords;
  2059. // 1. FY Filter
  2060. if (fyFilter !== 'All') {
  2061. records = records.filter(r => cleanFy(r.fin_year) === cleanFy(fyFilter));
  2062. }
  2063. // 2. Vendor Dropdown Filter
  2064. if (vendorFilter !== 'All') {
  2065. records = records.filter(r => (r.vend_name || '').trim() === vendorFilter);
  2066. }
  2067. // 2.5. Adjustment Status Filter
  2068. const statusFilter = document.getElementById('filter-adjustment-status') ? document.getElementById('filter-adjustment-status').value : 'All';
  2069. if (statusFilter !== 'All') {
  2070. records = records.filter(r => {
  2071. const ejvNum = String(r.ejv_num || '').trim();
  2072. const ejvPath = String(r.ejv_num_file_path || '').trim();
  2073. const isAdjusted = (ejvNum !== '' && ejvNum !== '-') || (ejvPath !== '' && ejvPath !== '-');
  2074. return statusFilter === 'Adjusted' ? isAdjusted : !isAdjusted;
  2075. });
  2076. }
  2077. // 2.6. Advance Type Filter
  2078. const advTypeFilter = document.getElementById('filter-advance-type') ? document.getElementById('filter-advance-type').value : 'All';
  2079. if (advTypeFilter !== 'All') {
  2080. records = records.filter(r => String(r.adv_type_id || '').trim() === advTypeFilter);
  2081. }
  2082. // 2.7. Fund Type Filter
  2083. const fundTypeFilter = document.getElementById('filter-fund-type') ? document.getElementById('filter-fund-type').value : 'All';
  2084. if (fundTypeFilter !== 'All') {
  2085. records = records.filter(r => String(r.fund_type_id || '').trim() === fundTypeFilter);
  2086. }
  2087. // 2.8. File Available Filter
  2088. const availFilter = document.getElementById('filter-file-available') ? document.getElementById('filter-file-available').value : 'All';
  2089. if (availFilter !== 'All') {
  2090. records = records.filter(r => {
  2091. let isAvail = false;
  2092. if (r.file_available_status === 'Yes') {
  2093. isAvail = true;
  2094. } else if (r.file_available_status === 'No') {
  2095. isAvail = false;
  2096. } else {
  2097. // Check physical files
  2098. const paths = [
  2099. r.file_path, r.po_file_path, r.dc_file_path, r.ir_file_path,
  2100. r.sr_filepath, r.pi_file_path, r.fi_file_path, r.tc_file_path,
  2101. r.rtgs_file_path, r.ejv_num_file_path, r.as_file_path, r.as_final_file_path
  2102. ];
  2103. isAvail = paths.some(p => p && p !== '-' && p !== 'null' && String(p).trim() !== '');
  2104. }
  2105. return availFilter === 'Available' ? isAvail : !isAvail;
  2106. });
  2107. }
  2108. // 3. Search input filter
  2109. if (searchVal !== '') {
  2110. records = records.filter(r =>
  2111. String(r.vend_name || '').toLowerCase().includes(searchVal) ||
  2112. String(r.file_data_num || '').toLowerCase().includes(searchVal) ||
  2113. String(r.subject || '').toLowerCase().includes(searchVal) ||
  2114. String(r.ejv_num || '').toLowerCase().includes(searchVal)
  2115. );
  2116. }
  2117. // 4. Sort records dynamically
  2118. records.sort((a, b) => {
  2119. let valA, valB;
  2120. if (sortField === 'fin_year') {
  2121. valA = String(a.fin_year || '').trim();
  2122. valB = String(b.fin_year || '').trim();
  2123. } else if (sortField === 'bal_amt') {
  2124. valA = Number(a.bal_amt || 0);
  2125. valB = Number(b.bal_amt || 0);
  2126. } else if (sortField === 'pi_amt') {
  2127. valA = Number(a.pi_amt || 0);
  2128. valB = Number(b.pi_amt || 0);
  2129. } else if (sortField === 'fi_amt') {
  2130. valA = Number(a.fi_amt || 0);
  2131. valB = Number(b.fi_amt || 0);
  2132. } else if (sortField === 'updated_date') {
  2133. valA = new Date(a.updated_date || a.cdate).getTime();
  2134. valB = new Date(b.updated_date || b.cdate).getTime();
  2135. } else {
  2136. valA = a.id;
  2137. valB = b.id;
  2138. }
  2139. if (valA === valB) return b.id - a.id; // secondary sort by ID descending
  2140. if (sortDirection === 'asc') {
  2141. if (typeof valA === 'string') {
  2142. return valA.localeCompare(valB);
  2143. }
  2144. return valA > valB ? 1 : -1;
  2145. } else {
  2146. if (typeof valA === 'string') {
  2147. return valB.localeCompare(valA);
  2148. }
  2149. return valA < valB ? 1 : -1;
  2150. }
  2151. });
  2152. // Store filtered records globally for Excel export
  2153. filteredRecords = records;
  2154. const totalRecords = records.length;
  2155. const startIndex = (currentPage - 1) * pageSize;
  2156. const endIndex = Math.min(startIndex + pageSize, totalRecords);
  2157. const paginatedRecords = records.slice(startIndex, endIndex);
  2158. if (currentTab === 'list') {
  2159. renderTableData(paginatedRecords, startIndex + 1);
  2160. renderPagination(totalRecords);
  2161. updateSortIcons();
  2162. } else if (currentTab === 'oc') {
  2163. // For OC Copy list, show records with ELCOT vendor primarily or filter ELCOT
  2164. const elcotRecords = records.filter(r => String(r.vend_name || '').toUpperCase().includes('ELCOT'));
  2165. renderOcTableData(elcotRecords);
  2166. }
  2167. }
  2168. function changePageSize() {
  2169. const val = document.getElementById('pagination-page-size').value;
  2170. if (val === 'All') {
  2171. pageSize = 999999;
  2172. } else {
  2173. pageSize = parseInt(val, 10);
  2174. }
  2175. currentPage = 1;
  2176. filterRecordsList(true);
  2177. }
  2178. function renderPagination(totalRecords) {
  2179. const info = document.getElementById('pagination-info');
  2180. const buttons = document.getElementById('pagination-buttons');
  2181. if (!info || !buttons) return;
  2182. const totalPages = Math.ceil(totalRecords / pageSize);
  2183. const startRange = totalRecords === 0 ? 0 : (currentPage - 1) * pageSize + 1;
  2184. const endRange = Math.min(currentPage * pageSize, totalRecords);
  2185. info.textContent = `Showing ${startRange} to ${endRange} of ${totalRecords} entries`;
  2186. buttons.innerHTML = '';
  2187. if (totalPages <= 1) {
  2188. return;
  2189. }
  2190. // Previous Button
  2191. const prevLi = document.createElement('li');
  2192. prevLi.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`;
  2193. prevLi.innerHTML = `<a class="page-link" href="javascript:void(0)" onclick="goToPage(${currentPage - 1})">&laquo;</a>`;
  2194. buttons.appendChild(prevLi);
  2195. // Page Numbers
  2196. const maxPageButtons = 5;
  2197. let startPage = Math.max(1, currentPage - 2);
  2198. let endPage = Math.min(totalPages, startPage + maxPageButtons - 1);
  2199. if (endPage - startPage < maxPageButtons - 1) {
  2200. startPage = Math.max(1, endPage - maxPageButtons + 1);
  2201. }
  2202. for (let p = startPage; p <= endPage; p++) {
  2203. const li = document.createElement('li');
  2204. li.className = `page-item ${currentPage === p ? 'active' : ''}`;
  2205. li.innerHTML = `<a class="page-link" href="javascript:void(0)" onclick="goToPage(${p})">${p}</a>`;
  2206. buttons.appendChild(li);
  2207. }
  2208. // Next Button
  2209. const nextLi = document.createElement('li');
  2210. nextLi.className = `page-item ${currentPage === totalPages ? 'disabled' : ''}`;
  2211. nextLi.innerHTML = `<a class="page-link" href="javascript:void(0)" onclick="goToPage(${currentPage + 1})">&raquo;</a>`;
  2212. buttons.appendChild(nextLi);
  2213. }
  2214. function goToPage(page) {
  2215. currentPage = page;
  2216. filterRecordsList(true);
  2217. }
  2218. function toggleSort(field) {
  2219. if (sortField === field) {
  2220. sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
  2221. } else {
  2222. sortField = field;
  2223. sortDirection = 'desc';
  2224. }
  2225. filterRecordsList(true);
  2226. }
  2227. function updateSortIcons() {
  2228. const fields = ['fin_year', 'bal_amt', 'pi_amt', 'fi_amt', 'cdate', 'updated_date'];
  2229. fields.forEach(f => {
  2230. const el = document.getElementById(`sort-icon-${f}`);
  2231. if (!el) return;
  2232. if (sortField === f) {
  2233. 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>';
  2234. } else {
  2235. el.innerHTML = '<i class="fa-solid fa-sort text-muted opacity-50 ms-1"></i>';
  2236. }
  2237. });
  2238. }
  2239. // Search in OC Copies Tab Specifically
  2240. function filterOcList() {
  2241. const searchVal = document.getElementById('oc-table-search').value.toLowerCase().trim();
  2242. let records = allRecords.filter(r => String(r.vend_name || '').toUpperCase().includes('ELCOT'));
  2243. const fyFilter = document.getElementById('filter-fin-year').value;
  2244. const cleanFy = str => String(str || '').replace(/\s+/g, '');
  2245. if (fyFilter !== 'All') {
  2246. records = records.filter(r => cleanFy(r.fin_year) === cleanFy(fyFilter));
  2247. }
  2248. if (searchVal !== '') {
  2249. records = records.filter(r =>
  2250. String(r.file_data_num || '').toLowerCase().includes(searchVal) ||
  2251. String(r.ejv_num || '').toLowerCase().includes(searchVal)
  2252. );
  2253. }
  2254. renderOcTableData(records);
  2255. }
  2256. // Format Date
  2257. function formatDate(dateStr) {
  2258. if (!dateStr) return '-';
  2259. try {
  2260. const d = new Date(dateStr);
  2261. if (isNaN(d.getTime())) return dateStr;
  2262. const dd = String(d.getDate()).padStart(2, '0');
  2263. const MM = String(d.getMonth() + 1).padStart(2, '0');
  2264. const yyyy = d.getFullYear();
  2265. return `${dd}/${MM}/${yyyy}`;
  2266. } catch (e) {
  2267. return dateStr;
  2268. }
  2269. }
  2270. // Format DateTime
  2271. function formatDateTime(dateTimeStr) {
  2272. if (!dateTimeStr) return '-';
  2273. try {
  2274. const d = new Date(dateTimeStr);
  2275. if (isNaN(d.getTime())) return dateTimeStr;
  2276. const dd = String(d.getDate()).padStart(2, '0');
  2277. const MM = String(d.getMonth() + 1).padStart(2, '0');
  2278. const yyyy = d.getFullYear();
  2279. const hh = String(d.getHours()).padStart(2, '0');
  2280. const mm = String(d.getMinutes()).padStart(2, '0');
  2281. return `${dd}/${MM}/${yyyy} ${hh}:${mm}`;
  2282. } catch (e) {
  2283. return dateTimeStr;
  2284. }
  2285. }
  2286. // Render main table rows
  2287. function renderTableData(records, startSNo = 1) {
  2288. const tbody = document.getElementById('records-table-body');
  2289. tbody.innerHTML = '';
  2290. if (records.length === 0) {
  2291. tbody.innerHTML = '<tr><td colspan="14" class="text-center text-muted">No files found matching criteria.</td></tr>';
  2292. return;
  2293. }
  2294. let currentSNo = startSNo;
  2295. records.forEach(r => {
  2296. const balAmt = r.bal_amt ? Number(r.bal_amt).toLocaleString('en-IN') : '0';
  2297. const piAmt = r.pi_amt ? Number(r.pi_amt).toLocaleString('en-IN') : '-';
  2298. const fiAmt = r.fi_amt ? Number(r.fi_amt).toLocaleString('en-IN') : '-';
  2299. tbody.innerHTML += `
  2300. <tr>
  2301. <td class="text-center">${currentSNo++}</td>
  2302. <td class="text-center">${escapeHtml(r.fin_year)}</td>
  2303. <td>${escapeHtml(r.file_data_num || '-')}</td>
  2304. <td class="text-center"><span class="badge bg-secondary">${escapeHtml(r.resolved_adv_type || '-')}</span></td>
  2305. <td class="text-center"><span class="badge bg-info text-dark">${escapeHtml(r.resolved_fund_type || '-')}</span></td>
  2306. <td class="text-center">${escapeHtml(r.ac_code || '-')}</td>
  2307. <td class="text-center">
  2308. ${r.file_available_status === 'Yes'
  2309. ? `<span class="badge bg-success-light text-success border border-success px-2 py-0.5">Yes</span>`
  2310. : r.file_available_status === 'No'
  2311. ? `<span class="badge bg-danger-light text-danger border border-danger px-2 py-0.5">No</span>`
  2312. : '-'}
  2313. </td>
  2314. <td class="fw-semibold text-primary text-end">₹ ${balAmt}</td>
  2315. <td class="text-end">₹ ${piAmt}</td>
  2316. <td class="text-end">₹ ${fiAmt}</td>
  2317. <td>${escapeHtml(r.ejv_num || '-')}</td>
  2318. <td class="text-muted" style="font-size: 0.8rem;">${formatDateTime(r.cdate)}</td>
  2319. <td class="text-muted" style="font-size: 0.8rem;">${formatDateTime(r.updated_date)}</td>
  2320. <td>${escapeHtml(r.updated_by_name || r.updated_by || '-')}</td>
  2321. <td class="text-center">
  2322. <div class="action-btn-group justify-content-center">
  2323. <button class="action-btn btn-view" title="View Lifecycle" onclick="viewRecordDetails(${r.id})">
  2324. <i class="fa-solid fa-eye"></i>
  2325. </button>
  2326. <button class="action-btn btn-edit" title="Edit File" onclick="editRecord(${r.id})">
  2327. <i class="fa-solid fa-pen-to-square"></i>
  2328. </button>
  2329. <button class="action-btn btn-delete" title="Delete File" onclick="deleteRecord(${r.id})">
  2330. <i class="fa-solid fa-trash"></i>
  2331. </button>
  2332. </div>
  2333. </td>
  2334. </tr>
  2335. `;
  2336. });
  2337. }
  2338. // Render ELCOT OC Copies Table
  2339. function renderOcTableData(records) {
  2340. const tbody = document.getElementById('oc-table-body');
  2341. tbody.innerHTML = '';
  2342. if (records.length === 0) {
  2343. tbody.innerHTML = '<tr><td colspan="13" class="text-center text-muted">No ELCOT files found.</td></tr>';
  2344. return;
  2345. }
  2346. records.forEach(r => {
  2347. tbody.innerHTML += `
  2348. <tr>
  2349. <td>${r.id}</td>
  2350. <td class="fw-bold">${escapeHtml(r.vend_name)}</td>
  2351. <td>${escapeHtml(r.fin_year)}</td>
  2352. <td>${escapeHtml(r.file_data_num || '-')}</td>
  2353. <td>${getFileLink(r.file_path, 'Original')}</td>
  2354. <td>${getFileLink(r.po_file_path, 'PO')}</td>
  2355. <td>${getFileLink(r.dc_file_path, 'DC')}</td>
  2356. <td>${getFileLink(r.ir_file_path, 'Inward')}</td>
  2357. <td>${getFileLink(r.sr_filepath, 'Stock')}</td>
  2358. <td>${getFileLink(r.tc_file_path, 'Temp Bill')}</td>
  2359. <td>${getFileLink(r.rtgs_file_path, 'RTGS')}</td>
  2360. <td>${getFileLink(r.ejv_num_file_path, 'EJV')}</td>
  2361. <td>${getFileLink(r.as_final_file_path, 'Final AS')}</td>
  2362. </tr>
  2363. `;
  2364. });
  2365. }
  2366. function isValidFilePath(path) {
  2367. if (!path) return false;
  2368. const p = String(path).trim();
  2369. return p !== '' && p !== '-' && p !== 'null';
  2370. }
  2371. function getFileLink(path, label) {
  2372. if (!isValidFilePath(path)) return '<span class="doc-missing">-</span>';
  2373. let cleanPath = String(path).trim();
  2374. if (cleanPath.startsWith('/')) {
  2375. cleanPath = cleanPath.substring(1);
  2376. }
  2377. return `<a href="${contextPath}${cleanPath}" target="_blank" class="doc-link"><i class="fa-solid fa-file-pdf"></i> ${label}</a>`;
  2378. }
  2379. // Details Modal View / Lifecycle
  2380. function viewRecordDetails(id) {
  2381. const r = allRecords.find(item => item.id === id);
  2382. if (!r) return;
  2383. const balAmt = r.bal_amt ? Number(r.bal_amt).toLocaleString('en-IN') : '0';
  2384. const piAmt = r.pi_amt ? Number(r.pi_amt).toLocaleString('en-IN') : '-';
  2385. const fiAmt = r.fi_amt ? Number(r.fi_amt).toLocaleString('en-IN') : '-';
  2386. // Define checklist document array
  2387. const docs = [
  2388. { name: "Original / Main Registration File", path: r.file_path, extra: `Ref Num: ${r.file_data_num || '-'}` },
  2389. { name: "Purchase Order (PO) Document", path: r.po_file_path, extra: null },
  2390. { name: "Delivery Challan (DC) Document", path: r.dc_file_path, extra: r.dc_completed_date ? `Completed Date: ${formatDate(r.dc_completed_date)}` : null },
  2391. { name: "Inward Register Document", path: r.ir_file_path, extra: null },
  2392. { name: "Stock Register (SR) Document", path: r.sr_filepath, extra: null },
  2393. { name: "Proforma Invoice (PI) Document", path: r.pi_file_path, extra: `PI Amount: ₹ ${piAmt} | Date: ${formatDate(r.pi_file_gen_date)}` },
  2394. { name: "Final Invoice (FI) Document", path: r.fi_file_path, extra: `FI Amount: ₹ ${fiAmt} | Date: ${formatDate(r.fi_file_gen_date)}` },
  2395. { name: "Temporary Bill (TC) Document", path: r.tc_file_path, extra: `Date: ${formatDate(r.tc_file_gen_date)}` },
  2396. { name: "RTGS Bank Payment Proof", path: r.rtgs_file_path, extra: r.rtgs_date ? `Upload Date: ${formatDate(r.rtgs_date)}` : null },
  2397. { 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)}` },
  2398. { name: "AS Sanction Copy", path: r.as_file_path, extra: `Date: ${formatDate(r.as_file_gen_date)}` },
  2399. { name: "Final AS Copy", path: r.as_final_file_path, extra: `Date: ${r.as_final_file_gen_date || '-'}` }
  2400. ];
  2401. let html = `
  2402. <div class="row mb-3">
  2403. <div class="col-md-6">
  2404. <strong>Vendor Name:</strong> ${escapeHtml(r.vend_name)}<br>
  2405. <strong>Financial Year:</strong> ${escapeHtml(r.fin_year)}<br>
  2406. <strong>Advance Type:</strong> ${escapeHtml(r.resolved_adv_type || '-')}<br>
  2407. <strong>Fund Type:</strong> ${escapeHtml(r.resolved_fund_type || '-')}<br>
  2408. <strong>A/C Code:</strong> ${escapeHtml(r.ac_code || '-')}<br>
  2409. <strong>File Available Status:</strong> ${escapeHtml(r.file_available_status || '-')}
  2410. </div>
  2411. <div class="col-md-6 text-md-end">
  2412. <strong>Balance Amount:</strong> <span class="text-primary fw-bold">₹ ${balAmt}</span><br>
  2413. <strong>Last Updated By:</strong> ${escapeHtml(r.updated_by_name || r.updated_by || '-')}<br>
  2414. <strong>Last Updated Date:</strong> ${formatDateTime(r.updated_date || r.cdate)}
  2415. </div>
  2416. <div class="col-12 mt-2">
  2417. <strong>Subject:</strong> <span class="text-muted">${escapeHtml(r.subject || 'No subject provided.')}</span>
  2418. </div>
  2419. <div class="col-12 mt-2">
  2420. <strong>File Remarks:</strong> <span class="text-muted">${escapeHtml(r.file_remark || 'No remarks provided.')}</span>
  2421. </div>
  2422. </div>
  2423. <hr>
  2424. <h6 class="fw-bold mb-3"><i class="fa-solid fa-list-check text-success"></i> Document Checklist Lifecycle</h6>
  2425. <div class="list-group">
  2426. `;
  2427. docs.forEach((d, index) => {
  2428. const serial = index + 1;
  2429. const statusIcon = isValidFilePath(d.path)
  2430. ? `<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>`
  2431. : `<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>`;
  2432. let cleanPath = '';
  2433. if (d.path) {
  2434. cleanPath = String(d.path).trim();
  2435. if (cleanPath.startsWith('/')) {
  2436. cleanPath = cleanPath.substring(1);
  2437. }
  2438. }
  2439. const linkBtn = isValidFilePath(d.path)
  2440. ? `<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>`
  2441. : '';
  2442. html += `
  2443. <div class="list-group-item d-flex justify-content-between align-items-center flex-wrap gap-2 py-3">
  2444. <div>
  2445. <span class="fw-bold text-dark me-2">${serial}.</span>
  2446. <span class="fw-semibold text-secondary" style="font-size: 0.95rem;">${d.name}</span>
  2447. ${d.extra ? `<br><small class="text-muted ms-4">${escapeHtml(d.extra)}</small>` : ''}
  2448. </div>
  2449. <div class="d-flex align-items-center">
  2450. ${statusIcon}
  2451. ${linkBtn}
  2452. </div>
  2453. </div>
  2454. `;
  2455. });
  2456. html += `</div>`;
  2457. document.getElementById('modal-details-content').innerHTML = html;
  2458. detailsModalObj.show();
  2459. }
  2460. // Edit Record
  2461. function editRecord(id) {
  2462. const r = allRecords.find(item => item.id === id);
  2463. if (!r) return;
  2464. // Check if file is adjusted
  2465. const ejvNum = String(r.ejv_num || '').trim();
  2466. const ejvPath = String(r.ejv_num_file_path || '').trim();
  2467. const isAdjusted = (ejvNum !== '' && ejvNum !== '-') || (ejvPath !== '' && ejvPath !== '-');
  2468. if (isAdjusted) {
  2469. Swal.fire({
  2470. title: 'Access Denied',
  2471. text: 'This file has already been adjusted and cannot be updated.',
  2472. icon: 'warning',
  2473. confirmButtonColor: '#0a4b64'
  2474. });
  2475. return;
  2476. }
  2477. // Pre-populate form fields
  2478. document.getElementById('form-id').value = r.id;
  2479. document.getElementById('form-vend-name').value = r.vend_name;
  2480. document.getElementById('form-fin-year').value = r.fin_year;
  2481. document.getElementById('form-subject').value = r.subject || '';
  2482. document.getElementById('form-adv-type').value = r.adv_type_id || '';
  2483. document.getElementById('form-fund-type').value = r.fund_type_id || '';
  2484. document.getElementById('form-ac-code').value = r.ac_code || '';
  2485. document.getElementById('form-file-available-status').value = r.file_available_status || '';
  2486. document.getElementById('form-bal-amt').value = r.bal_amt || '';
  2487. document.getElementById('form-file-remark').value = r.file_remark || '';
  2488. document.getElementById('form-file-data-num').value = r.file_data_num || '';
  2489. document.getElementById('form-pi-amt').value = r.pi_amt || '';
  2490. document.getElementById('form-pi-gen-date').value = r.pi_file_gen_date || '';
  2491. document.getElementById('form-as-date').value = r.as_file_gen_date || '';
  2492. // Pre-populate new update-only fields
  2493. document.getElementById('form-fi-amt').value = r.fi_amt || '';
  2494. document.getElementById('form-fi-gen-date').value = r.fi_file_gen_date || '';
  2495. document.getElementById('form-as-final-date').value = r.as_final_file_gen_date || '';
  2496. document.getElementById('form-tc-date').value = r.tc_file_gen_date || '';
  2497. document.getElementById('form-rtgs-date').value = r.rtgs_date || '';
  2498. document.getElementById('form-dc-completed-date').value = r.dc_completed_date || '';
  2499. // Hide financial year input in edit mode (keep enabled but hidden so its value gets submitted)
  2500. document.getElementById('form-fin-year-container').style.display = 'none';
  2501. document.getElementById('form-fin-year').removeAttribute('disabled');
  2502. document.getElementById('form-fin-year').required = false;
  2503. // Run visibility check after populating fields
  2504. toggleVendorNameVisibility();
  2505. // Reset indicators
  2506. document.querySelectorAll('.file-selected-indicator').forEach(ind => {
  2507. ind.style.display = 'none';
  2508. ind.querySelector('span').textContent = '';
  2509. });
  2510. // Existing file paths links
  2511. setupExistingFileLink('form-as-file', r.as_file_path);
  2512. setupExistingFileLink('form-pi-file', r.pi_file_path);
  2513. setupExistingFileLink('form-rtgs-file', r.rtgs_file_path);
  2514. setupExistingFileLink('form-po-file', r.po_file_path);
  2515. setupExistingFileLink('form-dc-file', r.dc_file_path);
  2516. setupExistingFileLink('form-sr-file', r.sr_filepath);
  2517. setupExistingFileLink('form-ir-file', r.ir_file_path);
  2518. setupExistingFileLink('form-fi-file', r.fi_file_path);
  2519. setupExistingFileLink('form-as-final-file', r.as_final_file_path);
  2520. setupExistingFileLink('form-tc-file', r.tc_file_path);
  2521. // Show all edit-only fields
  2522. document.querySelectorAll('.edit-only-field').forEach(el => el.style.display = 'block');
  2523. // Set Form Title
  2524. document.getElementById('registration-title').innerHTML = `Edit File Registration (ID: ${r.id})`;
  2525. // Show registration modal for editing
  2526. registrationModalObj.show();
  2527. }
  2528. function openRegistrationModal() {
  2529. // Count active database records
  2530. // if (allRecords.length >= 257) {
  2531. // Swal.fire({
  2532. // title: 'Upload Locked',
  2533. // text: 'The database files count has reached the maximum cap of 257. No more file registrations are allowed.',
  2534. // icon: 'warning',
  2535. // confirmButtonColor: '#0a4b64'
  2536. // });
  2537. // return;
  2538. // }
  2539. resetForm();
  2540. // Show financial year input in new registration mode
  2541. document.getElementById('form-fin-year-container').style.display = 'block';
  2542. document.getElementById('form-fin-year').removeAttribute('disabled');
  2543. document.getElementById('form-fin-year').required = true;
  2544. // Hide edit-only fields in new registration mode
  2545. document.querySelectorAll('.edit-only-field').forEach(el => el.style.display = 'none');
  2546. // Hide any existing file links
  2547. document.querySelectorAll('.existing-file-link').forEach(el => el.style.display = 'none');
  2548. registrationModalObj.show();
  2549. }
  2550. // Reset form
  2551. function resetForm() {
  2552. document.getElementById('adjustment-registration-form').reset();
  2553. document.getElementById('form-id').value = '';
  2554. document.getElementById('registration-title').innerHTML = `New File Registration`;
  2555. // Show financial year input on form reset
  2556. document.getElementById('form-fin-year-container').style.display = 'block';
  2557. document.getElementById('form-fin-year').removeAttribute('disabled');
  2558. document.getElementById('form-fin-year').required = true;
  2559. document.querySelectorAll('.file-selected-indicator').forEach(ind => {
  2560. ind.style.display = 'none';
  2561. });
  2562. document.querySelectorAll('.existing-file-link').forEach(link => {
  2563. link.style.display = 'none';
  2564. });
  2565. toggleVendorNameVisibility();
  2566. }
  2567. function calculateBalance() {
  2568. const piVal = parseFloat(document.getElementById('form-pi-amt').value) || 0;
  2569. const fiVal = parseFloat(document.getElementById('form-fi-amt').value) || 0;
  2570. const balVal = piVal - fiVal;
  2571. document.getElementById('form-bal-amt').value = balVal.toFixed(2);
  2572. }
  2573. function setupExistingFileLink(inputId, filePath) {
  2574. const input = document.getElementById(inputId);
  2575. if (!input) return;
  2576. const container = input.closest('.file-upload-wrapper');
  2577. if (!container) return;
  2578. let linkDiv = container.querySelector('.existing-file-link');
  2579. if (!linkDiv) {
  2580. linkDiv = document.createElement('div');
  2581. linkDiv.className = 'existing-file-link mt-1';
  2582. linkDiv.style.fontSize = '0.85rem';
  2583. container.appendChild(linkDiv);
  2584. }
  2585. if (filePath && filePath !== '-' && filePath !== 'null') {
  2586. let cleanPath = String(filePath).trim();
  2587. if (cleanPath.startsWith('/')) {
  2588. cleanPath = cleanPath.substring(1);
  2589. }
  2590. const fileName = cleanPath.substring(cleanPath.lastIndexOf('/') + 1);
  2591. linkDiv.innerHTML = `<a href="${contextPath}${cleanPath}" target="_blank" class="text-primary"><i class="fa-solid fa-file-pdf"></i> View Existing: ${fileName}</a>`;
  2592. linkDiv.style.display = 'block';
  2593. } else {
  2594. linkDiv.style.display = 'none';
  2595. }
  2596. }
  2597. // Toggle vendor name visibility based on Senior System Manager (ID 6)
  2598. function toggleVendorNameVisibility() {
  2599. const advTypeSelect = document.getElementById('form-adv-type');
  2600. const vendorContainer = document.getElementById('vendor-name-container');
  2601. const vendorInput = document.getElementById('form-vend-name');
  2602. if (!advTypeSelect || !vendorContainer || !vendorInput) return;
  2603. if (advTypeSelect.value === '6') {
  2604. vendorContainer.style.display = 'block';
  2605. vendorInput.required = true;
  2606. } else {
  2607. vendorContainer.style.display = 'none';
  2608. vendorInput.required = false;
  2609. vendorInput.value = ''; // Reset when hidden
  2610. }
  2611. }
  2612. // Submit form (Save / Update)
  2613. function submitRegistrationForm(event) {
  2614. event.preventDefault();
  2615. showLoader(true);
  2616. const form = document.getElementById('adjustment-registration-form');
  2617. // Temporarily enable disabled fields (like fin_year in edit mode) so they are submitted in FormData
  2618. const disabledInputs = form.querySelectorAll(':disabled');
  2619. disabledInputs.forEach(input => input.removeAttribute('disabled'));
  2620. const formData = new FormData(form);
  2621. // Re-disable them
  2622. disabledInputs.forEach(input => input.setAttribute('disabled', 'true'));
  2623. // Append CSRF tokens if available
  2624. const csrfMeta = document.querySelector("meta[name='_csrf']");
  2625. const csrfHeaderMeta = document.querySelector("meta[name='_csrf_header']");
  2626. const headers = {};
  2627. if (csrfMeta && csrfHeaderMeta) {
  2628. headers[csrfHeaderMeta.getAttribute("content")] = csrfMeta.getAttribute("content");
  2629. }
  2630. fetch(contextPath + 'gccadjustmentmanagement/api/save', {
  2631. method: 'POST',
  2632. headers: headers,
  2633. body: formData
  2634. })
  2635. .then(res => res.json())
  2636. .then(data => {
  2637. showLoader(false);
  2638. if (data.success) {
  2639. Swal.fire({
  2640. title: 'Success!',
  2641. text: data.message,
  2642. icon: 'success',
  2643. confirmButtonColor: '#0a4b64'
  2644. }).then(() => {
  2645. if (registrationModalObj) {
  2646. registrationModalObj.hide();
  2647. }
  2648. resetForm();
  2649. loadRecordsData();
  2650. switchView('list');
  2651. });
  2652. } else {
  2653. Swal.fire({
  2654. title: 'Failed!',
  2655. text: data.error || 'Failed to save record details.',
  2656. icon: 'error',
  2657. confirmButtonColor: '#ef4444'
  2658. });
  2659. }
  2660. })
  2661. .catch(err => {
  2662. showLoader(false);
  2663. Swal.fire({
  2664. title: 'Error!',
  2665. text: 'An error occurred during submission: ' + err,
  2666. icon: 'error',
  2667. confirmButtonColor: '#ef4444'
  2668. });
  2669. });
  2670. }
  2671. // Delete Record with remarks
  2672. function deleteRecord(id) {
  2673. Swal.fire({
  2674. title: 'Delete Record?',
  2675. text: 'Please enter remarks/reason for deletion:',
  2676. input: 'text',
  2677. inputAttributes: {
  2678. autocapitalize: 'off',
  2679. required: true
  2680. },
  2681. showCancelButton: true,
  2682. confirmButtonText: 'Delete',
  2683. confirmButtonColor: '#ef4444',
  2684. cancelButtonColor: '#475569',
  2685. preConfirm: (remarks) => {
  2686. if (!remarks || remarks.trim() === '') {
  2687. Swal.showValidationMessage('Deletion remarks are required!');
  2688. }
  2689. return remarks;
  2690. }
  2691. }).then((result) => {
  2692. if (result.isConfirmed) {
  2693. showLoader(true);
  2694. const csrfMeta = document.querySelector("meta[name='_csrf']");
  2695. const csrfHeaderMeta = document.querySelector("meta[name='_csrf_header']");
  2696. const headers = {};
  2697. if (csrfMeta && csrfHeaderMeta) {
  2698. headers[csrfHeaderMeta.getAttribute("content")] = csrfMeta.getAttribute("content");
  2699. }
  2700. const formData = new FormData();
  2701. formData.append('id', id);
  2702. formData.append('remarks', result.value);
  2703. fetch(contextPath + 'gccadjustmentmanagement/api/delete', {
  2704. method: 'POST',
  2705. headers: headers,
  2706. body: formData
  2707. })
  2708. .then(res => res.json())
  2709. .then(data => {
  2710. showLoader(false);
  2711. if (data.success) {
  2712. Swal.fire({
  2713. title: 'Deleted!',
  2714. text: data.message,
  2715. icon: 'success',
  2716. confirmButtonColor: '#0a4b64'
  2717. }).then(() => {
  2718. loadRecordsData();
  2719. });
  2720. } else {
  2721. Swal.fire({
  2722. title: 'Failed!',
  2723. text: data.error || 'Failed to delete record.',
  2724. icon: 'error',
  2725. confirmButtonColor: '#ef4444'
  2726. });
  2727. }
  2728. })
  2729. .catch(err => {
  2730. showLoader(false);
  2731. Swal.fire('Error', 'Error deleting record: ' + err, 'error');
  2732. });
  2733. }
  2734. });
  2735. }
  2736. // Export data to Excel
  2737. function exportToExcel() {
  2738. const tempTable = document.createElement('table');
  2739. tempTable.innerHTML = `
  2740. <thead>
  2741. <tr>
  2742. <th style="text-align: center;">S.No</th>
  2743. <th>Vendor Name</th>
  2744. <th style="text-align: center;">Fin Year</th>
  2745. <th>File Num</th>
  2746. <th>Subject</th>
  2747. <th style="text-align: center;">Advance Type</th>
  2748. <th style="text-align: center;">Fund Type</th>
  2749. <th style="text-align: center;">A/C Code</th>
  2750. <th style="text-align: center;">File Available Status</th>
  2751. <th style="text-align: right;">Bal Amount</th>
  2752. <th style="text-align: right;">PI Amount</th>
  2753. <th style="text-align: right;">FI Amount</th>
  2754. <th>EJV Number</th>
  2755. <th>Created Date</th>
  2756. <th>Last Updated</th>
  2757. <th>Updated By</th>
  2758. </tr>
  2759. </thead>
  2760. <tbody>
  2761. </tbody>
  2762. `;
  2763. const tbody = tempTable.querySelector('tbody');
  2764. let currentSNo = 1;
  2765. filteredRecords.forEach(r => {
  2766. const balAmt = r.bal_amt ? Number(r.bal_amt).toLocaleString('en-IN') : '0';
  2767. const piAmt = r.pi_amt ? Number(r.pi_amt).toLocaleString('en-IN') : '-';
  2768. const fiAmt = r.fi_amt ? Number(r.fi_amt).toLocaleString('en-IN') : '-';
  2769. tbody.innerHTML += `
  2770. <tr>
  2771. <td style="text-align: center;">${currentSNo++}</td>
  2772. <td>${escapeHtml(r.vend_name || '')}</td>
  2773. <td style="text-align: center;">${escapeHtml(r.fin_year || '')}</td>
  2774. <td>${escapeHtml(r.file_data_num || '-')}</td>
  2775. <td>${escapeHtml(r.subject || '-')}</td>
  2776. <td style="text-align: center;">${escapeHtml(r.resolved_adv_type || '-')}</td>
  2777. <td style="text-align: center;">${escapeHtml(r.resolved_fund_type || '-')}</td>
  2778. <td style="text-align: center;">${escapeHtml(r.ac_code || '-')}</td>
  2779. <td style="text-align: center;">${escapeHtml(r.file_available_status || '-')}</td>
  2780. <td style="text-align: right;">₹ ${balAmt}</td>
  2781. <td style="text-align: right;">₹ ${piAmt}</td>
  2782. <td style="text-align: right;">₹ ${fiAmt}</td>
  2783. <td>${escapeHtml(r.ejv_num || '-')}</td>
  2784. <td>${formatDateTime(r.cdate)}</td>
  2785. <td>${formatDateTime(r.updated_date)}</td>
  2786. <td>${escapeHtml(r.updated_by_name || r.updated_by || '-')}</td>
  2787. </tr>
  2788. `;
  2789. });
  2790. const wb = XLSX.utils.table_to_book(tempTable, { sheet: "Adjustments" });
  2791. XLSX.writeFile(wb, "Adjustment_Files_Report.xlsx");
  2792. }
  2793. // Dashboard view toggling and detail list functions
  2794. function setDashboardViewMode(mode) {
  2795. dashboardViewMode = mode;
  2796. // Sync radio button checks
  2797. const detailsRadio = document.getElementById('mode-details');
  2798. const splitRadio = document.getElementById('mode-split');
  2799. if (detailsRadio) detailsRadio.checked = (mode === 'details');
  2800. if (splitRadio) splitRadio.checked = (mode === 'split');
  2801. // Update title text and icon
  2802. const titleText = document.getElementById('dash-report-title-text');
  2803. const titleIcon = document.getElementById('dash-report-title-icon');
  2804. const exportBtn = document.getElementById('btn-dash-export');
  2805. const searchContainer = document.getElementById('dash-search-container');
  2806. if (mode === 'details') {
  2807. if (titleText) titleText.textContent = 'Existing Adjustment Files';
  2808. if (titleIcon) {
  2809. titleIcon.className = 'fa-solid fa-folder-open text-primary me-2';
  2810. }
  2811. if (exportBtn) {
  2812. exportBtn.innerHTML = '<i class="fa-solid fa-file-excel me-1"></i> Export Excel';
  2813. }
  2814. if (searchContainer) searchContainer.style.display = 'block';
  2815. document.getElementById('panel-details-content').style.display = 'block';
  2816. document.getElementById('panel-split-content').style.display = 'none';
  2817. renderDashboardDetails();
  2818. } else {
  2819. if (titleText) titleText.textContent = 'Advance Split-up Report';
  2820. if (titleIcon) {
  2821. titleIcon.className = 'fa-solid fa-table-list text-primary me-2';
  2822. }
  2823. if (exportBtn) {
  2824. exportBtn.innerHTML = '<i class="fa-solid fa-file-excel me-1"></i> Export Split-up';
  2825. }
  2826. if (searchContainer) searchContainer.style.display = 'none';
  2827. document.getElementById('panel-details-content').style.display = 'none';
  2828. document.getElementById('panel-split-content').style.display = 'block';
  2829. renderSplitUpReport();
  2830. }
  2831. }
  2832. function onDashboardFilterChange() {
  2833. dashCurrentPage = 1;
  2834. renderSplitUpReport();
  2835. renderDashboardDetails();
  2836. }
  2837. function renderDashboardDetails() {
  2838. const fyFilter = document.getElementById('filter-fin-year').value;
  2839. const splitVendorSelect = document.getElementById('split-filter-vendor');
  2840. const splitVendorFilter = splitVendorSelect ? splitVendorSelect.value : 'All';
  2841. const splitAdvSelect = document.getElementById('split-filter-advance-type');
  2842. const splitAdvFilter = splitAdvSelect ? splitAdvSelect.value : 'All';
  2843. const searchVal = document.getElementById('dash-table-search') ? document.getElementById('dash-table-search').value.toLowerCase().trim() : '';
  2844. const cleanFy = str => String(str || '').replace(/\s+/g, '');
  2845. let records = allRecords;
  2846. // 1. FY Filter
  2847. if (fyFilter !== 'All') {
  2848. records = records.filter(r => cleanFy(r.fin_year) === cleanFy(fyFilter));
  2849. }
  2850. // 2. Click Filter (Total, Adjusted, Pending, Election, NotAvailable, LatestUpdates)
  2851. if (currentSplitFilter === 'Adjusted') {
  2852. records = records.filter(r => {
  2853. const ejvNum = String(r.ejv_num || '').trim();
  2854. const ejvPath = String(r.ejv_num_file_path || '').trim();
  2855. return (ejvNum !== '' && ejvNum !== '-') || (ejvPath !== '' && ejvPath !== '-');
  2856. });
  2857. } else if (currentSplitFilter === 'Pending') {
  2858. records = records.filter(r => {
  2859. const ejvNum = String(r.ejv_num || '').trim();
  2860. const ejvPath = String(r.ejv_num_file_path || '').trim();
  2861. return !((ejvNum !== '' && ejvNum !== '-') || (ejvPath !== '' && ejvPath !== '-'));
  2862. });
  2863. } else if (currentSplitFilter === 'Election') {
  2864. records = records.filter(r => String(r.adv_type_id) === '9');
  2865. } else if (currentSplitFilter === 'NotAvailable') {
  2866. records = records.filter(r => r.file_available_status === 'No');
  2867. } else if (currentSplitFilter === 'LatestUpdates') {
  2868. records = records.filter(r => {
  2869. const dateStr = r.updated_date || r.cdate;
  2870. return dateStr && getLocalDateString(dateStr) === latestUpdateDateString;
  2871. });
  2872. }
  2873. // 3. Vendor Dropdown
  2874. if (splitVendorFilter !== 'All') {
  2875. records = records.filter(r => String(r.vend_name || '').trim() === splitVendorFilter);
  2876. }
  2877. // 4. Advance Type Dropdown
  2878. if (splitAdvFilter !== 'All') {
  2879. records = records.filter(r => String(r.adv_type_id || '').trim() === splitAdvFilter);
  2880. }
  2881. // 5. Search text filter
  2882. if (searchVal !== '') {
  2883. records = records.filter(r =>
  2884. String(r.vend_name || '').toLowerCase().includes(searchVal) ||
  2885. String(r.file_data_num || '').toLowerCase().includes(searchVal) ||
  2886. String(r.subject || '').toLowerCase().includes(searchVal) ||
  2887. String(r.ejv_num || '').toLowerCase().includes(searchVal)
  2888. );
  2889. }
  2890. // 6. Sorting
  2891. records.sort((a, b) => {
  2892. let valA, valB;
  2893. if (dashSortField === 'fin_year') {
  2894. valA = String(a.fin_year || '').trim();
  2895. valB = String(b.fin_year || '').trim();
  2896. } else if (dashSortField === 'bal_amt') {
  2897. valA = Number(a.bal_amt || 0);
  2898. valB = Number(b.bal_amt || 0);
  2899. } else if (dashSortField === 'pi_amt') {
  2900. valA = Number(a.pi_amt || 0);
  2901. valB = Number(b.pi_amt || 0);
  2902. } else if (dashSortField === 'fi_amt') {
  2903. valA = Number(a.fi_amt || 0);
  2904. valB = Number(b.fi_amt || 0);
  2905. } else if (dashSortField === 'updated_date') {
  2906. valA = new Date(a.updated_date || a.cdate).getTime();
  2907. valB = new Date(b.updated_date || b.cdate).getTime();
  2908. } else {
  2909. valA = a.id;
  2910. valB = b.id;
  2911. }
  2912. if (valA === valB) return b.id - a.id; // secondary sort by ID descending
  2913. if (dashSortDirection === 'asc') {
  2914. if (typeof valA === 'string') {
  2915. return valA.localeCompare(valB);
  2916. }
  2917. return valA > valB ? 1 : -1;
  2918. } else {
  2919. if (typeof valA === 'string') {
  2920. return valB.localeCompare(valA);
  2921. }
  2922. return valA < valB ? 1 : -1;
  2923. }
  2924. });
  2925. dashFilteredRecords = records;
  2926. const totalRecords = records.length;
  2927. const startIndex = (dashCurrentPage - 1) * dashPageSize;
  2928. const endIndex = Math.min(startIndex + dashPageSize, totalRecords);
  2929. const paginatedRecords = records.slice(startIndex, endIndex);
  2930. const tbody = document.getElementById('dash-records-table-body');
  2931. tbody.innerHTML = '';
  2932. if (paginatedRecords.length === 0) {
  2933. tbody.innerHTML = '<tr><td colspan="14" class="text-center text-muted">No files found matching criteria.</td></tr>';
  2934. renderDashPagination(0);
  2935. return;
  2936. }
  2937. let currentSNo = startIndex + 1;
  2938. paginatedRecords.forEach(r => {
  2939. const balAmt = r.bal_amt ? Number(r.bal_amt).toLocaleString('en-IN') : '0';
  2940. const piAmt = r.pi_amt ? Number(r.pi_amt).toLocaleString('en-IN') : '-';
  2941. const fiAmt = r.fi_amt ? Number(r.fi_amt).toLocaleString('en-IN') : '-';
  2942. // <td class="text-center"><span class="badge bg-info text-dark">${escapeHtml(r.resolved_fund_type || '-')}</span></td>
  2943. // <td class="text-center">${escapeHtml(r.ac_code || '-')}</td>
  2944. tbody.innerHTML += `
  2945. <tr>
  2946. <td class="text-center">${currentSNo++}</td>
  2947. <td class="text-center">${escapeHtml(r.fin_year)}</td>
  2948. <td>${escapeHtml(r.file_data_num || '-')}</td>
  2949. <td class="text-center"><span class="badge bg-secondary">${escapeHtml(r.resolved_adv_type || '-')}</span></td>
  2950. <td class="text-center">
  2951. ${r.file_available_status === 'Yes'
  2952. ? `<span class="badge bg-success-light text-success border border-success px-2 py-0.5">Yes</span>`
  2953. : r.file_available_status === 'No'
  2954. ? `<span class="badge bg-danger-light text-danger border border-danger px-2 py-0.5">No</span>`
  2955. : '-'}
  2956. </td>
  2957. <td class="fw-semibold text-primary text-end">₹ ${balAmt}</td>
  2958. <td class="text-end">₹ ${piAmt}</td>
  2959. <td class="text-end">₹ ${fiAmt}</td>
  2960. <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>
  2961. <td class="text-muted" style="font-size: 0.8rem;">${formatDateTime(r.cdate)}</td>
  2962. <td class="text-muted" style="font-size: 0.8rem;">${formatDateTime(r.updated_date)}</td>
  2963. <td>${escapeHtml(r.updated_by_name || r.updated_by || '-')}</td>
  2964. <td class="text-center">
  2965. <div class="action-btn-group justify-content-center">
  2966. <button class="action-btn btn-view" title="View Lifecycle" onclick="viewRecordDetails(${r.id})">
  2967. <i class="fa-solid fa-eye"></i>
  2968. </button>
  2969. <button class="action-btn btn-edit" title="Edit File" onclick="editRecord(${r.id})">
  2970. <i class="fa-solid fa-pen-to-square"></i>
  2971. </button>
  2972. <button class="action-btn btn-delete" title="Delete File" onclick="deleteRecord(${r.id})">
  2973. <i class="fa-solid fa-trash"></i>
  2974. </button>
  2975. </div>
  2976. </td>
  2977. </tr>
  2978. `;
  2979. });
  2980. renderDashPagination(totalRecords);
  2981. updateDashSortIcons();
  2982. }
  2983. function renderDashPagination(totalRecords) {
  2984. const info = document.getElementById('dash-pagination-info');
  2985. const buttons = document.getElementById('dash-pagination-buttons');
  2986. if (!info || !buttons) return;
  2987. const totalPages = Math.ceil(totalRecords / dashPageSize);
  2988. const startRange = totalRecords === 0 ? 0 : (dashCurrentPage - 1) * dashPageSize + 1;
  2989. const endRange = Math.min(dashCurrentPage * dashPageSize, totalRecords);
  2990. info.textContent = `Showing ${startRange} to ${endRange} of ${totalRecords} entries`;
  2991. buttons.innerHTML = '';
  2992. if (totalPages <= 1) {
  2993. return;
  2994. }
  2995. // Previous Button
  2996. const prevLi = document.createElement('li');
  2997. prevLi.className = `page-item ${dashCurrentPage === 1 ? 'disabled' : ''}`;
  2998. prevLi.innerHTML = `<a class="page-link" href="javascript:void(0)" onclick="goToDashPage(${dashCurrentPage - 1})">&laquo;</a>`;
  2999. buttons.appendChild(prevLi);
  3000. // Page Numbers
  3001. const maxPageButtons = 5;
  3002. let startPage = Math.max(1, dashCurrentPage - 2);
  3003. let endPage = Math.min(totalPages, startPage + maxPageButtons - 1);
  3004. if (endPage - startPage < maxPageButtons - 1) {
  3005. startPage = Math.max(1, endPage - maxPageButtons + 1);
  3006. }
  3007. for (let p = startPage; p <= endPage; p++) {
  3008. const li = document.createElement('li');
  3009. li.className = `page-item ${dashCurrentPage === p ? 'active' : ''}`;
  3010. li.innerHTML = `<a class="page-link" href="javascript:void(0)" onclick="goToDashPage(${p})">${p}</a>`;
  3011. buttons.appendChild(li);
  3012. }
  3013. // Next Button
  3014. const nextLi = document.createElement('li');
  3015. nextLi.className = `page-item ${dashCurrentPage === totalPages ? 'disabled' : ''}`;
  3016. nextLi.innerHTML = `<a class="page-link" href="javascript:void(0)" onclick="goToDashPage(${dashCurrentPage + 1})">&raquo;</a>`;
  3017. buttons.appendChild(nextLi);
  3018. }
  3019. function changeDashPageSize() {
  3020. const val = document.getElementById('dash-pagination-page-size').value;
  3021. if (val === 'All') {
  3022. dashPageSize = 999999;
  3023. } else {
  3024. dashPageSize = parseInt(val, 10);
  3025. }
  3026. dashCurrentPage = 1;
  3027. renderDashboardDetails();
  3028. }
  3029. function goToDashPage(page) {
  3030. dashCurrentPage = page;
  3031. renderDashboardDetails();
  3032. }
  3033. function toggleDashSort(field) {
  3034. if (dashSortField === field) {
  3035. dashSortDirection = dashSortDirection === 'asc' ? 'desc' : 'asc';
  3036. } else {
  3037. dashSortField = field;
  3038. dashSortDirection = 'desc';
  3039. }
  3040. renderDashboardDetails();
  3041. }
  3042. function updateDashSortIcons() {
  3043. const fields = ['fin_year', 'bal_amt', 'pi_amt', 'fi_amt', 'cdate', 'updated_date'];
  3044. fields.forEach(f => {
  3045. const el = document.getElementById(`dash-sort-icon-${f}`);
  3046. if (!el) return;
  3047. if (dashSortField === f) {
  3048. 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>';
  3049. } else {
  3050. el.innerHTML = '<i class="fa-solid fa-sort text-muted opacity-50 ms-1"></i>';
  3051. }
  3052. });
  3053. }
  3054. function exportDashboardDetailsToExcel() {
  3055. const tempTable = document.createElement('table');
  3056. tempTable.innerHTML = `
  3057. <thead>
  3058. <tr>
  3059. <th style="text-align: center;">S.No</th>
  3060. <th>Vendor Name</th>
  3061. <th style="text-align: center;">Fin Year</th>
  3062. <th>File Num</th>
  3063. <th>Subject</th>
  3064. <th style="text-align: center;">Advance Type</th>
  3065. <th style="text-align: center;">Fund Type</th>
  3066. <th style="text-align: center;">A/C Code</th>
  3067. <th style="text-align: center;">File Available Status</th>
  3068. <th style="text-align: right;">Bal Amount</th>
  3069. <th style="text-align: right;">PI Amount</th>
  3070. <th style="text-align: right;">FI Amount</th>
  3071. <th>EJV Number</th>
  3072. <th>Created Date</th>
  3073. <th>Last Updated</th>
  3074. <th>Updated By</th>
  3075. </tr>
  3076. </thead>
  3077. <tbody>
  3078. </tbody>
  3079. `;
  3080. const tbody = tempTable.querySelector('tbody');
  3081. let currentSNo = 1;
  3082. dashFilteredRecords.forEach(r => {
  3083. const balAmt = r.bal_amt ? Number(r.bal_amt).toLocaleString('en-IN') : '0';
  3084. const piAmt = r.pi_amt ? Number(r.pi_amt).toLocaleString('en-IN') : '-';
  3085. const fiAmt = r.fi_amt ? Number(r.fi_amt).toLocaleString('en-IN') : '-';
  3086. tbody.innerHTML += `
  3087. <tr>
  3088. <td style="text-align: center;">${currentSNo++}</td>
  3089. <td>${escapeHtml(r.vend_name || '')}</td>
  3090. <td style="text-align: center;">${escapeHtml(r.fin_year || '')}</td>
  3091. <td>${escapeHtml(r.file_data_num || '-')}</td>
  3092. <td>${escapeHtml(r.subject || '-')}</td>
  3093. <td style="text-align: center;">${escapeHtml(r.resolved_adv_type || '-')}</td>
  3094. <td style="text-align: center;">${escapeHtml(r.resolved_fund_type || '-')}</td>
  3095. <td style="text-align: center;">${escapeHtml(r.ac_code || '-')}</td>
  3096. <td style="text-align: center;">${escapeHtml(r.file_available_status || '-')}</td>
  3097. <td style="text-align: right;">₹ ${balAmt}</td>
  3098. <td style="text-align: right;">₹ ${piAmt}</td>
  3099. <td style="text-align: right;">₹ ${fiAmt}</td>
  3100. <td>${escapeHtml(r.ejv_num || '-')}</td>
  3101. <td>${formatDateTime(r.cdate)}</td>
  3102. <td>${formatDateTime(r.updated_date)}</td>
  3103. <td>${escapeHtml(r.updated_by_name || r.updated_by || '-')}</td>
  3104. </tr>
  3105. `;
  3106. });
  3107. const wb = XLSX.utils.table_to_book(tempTable, { sheet: "Dashboard Details" });
  3108. XLSX.writeFile(wb, "Dashboard_Files_Report.xlsx");
  3109. }
  3110. function exportDashboardData() {
  3111. if (dashboardViewMode === 'details') {
  3112. exportDashboardDetailsToExcel();
  3113. } else {
  3114. exportSplitUpToExcel();
  3115. }
  3116. }
  3117. // Generate Year-wise and Advance Type-wise Split-up Report
  3118. function renderSplitUpReport() {
  3119. const container = document.getElementById('panel-split-content');
  3120. if (!container) return;
  3121. if (!allRecords || allRecords.length === 0) {
  3122. container.innerHTML = `<div class="text-center text-muted p-5">No records loaded to generate split-up report.</div>`;
  3123. return;
  3124. }
  3125. // Filter allRecords based on currentSplitFilter
  3126. let recordsToProcess = allRecords;
  3127. if (currentSplitFilter === 'Adjusted') {
  3128. recordsToProcess = recordsToProcess.filter(r => {
  3129. const ejvNum = String(r.ejv_num || '').trim();
  3130. const ejvPath = String(r.ejv_num_file_path || '').trim();
  3131. return (ejvNum !== '' && ejvNum !== '-') || (ejvPath !== '' && ejvPath !== '-');
  3132. });
  3133. } else if (currentSplitFilter === 'Pending') {
  3134. recordsToProcess = recordsToProcess.filter(r => {
  3135. const ejvNum = String(r.ejv_num || '').trim();
  3136. const ejvPath = String(r.ejv_num_file_path || '').trim();
  3137. return !((ejvNum !== '' && ejvNum !== '-') || (ejvPath !== '' && ejvPath !== '-'));
  3138. });
  3139. } else if (currentSplitFilter === 'Election') {
  3140. recordsToProcess = recordsToProcess.filter(r => String(r.adv_type_id) === '9');
  3141. } else if (currentSplitFilter === 'NotAvailable') {
  3142. recordsToProcess = recordsToProcess.filter(r => r.file_available_status === 'No');
  3143. } else if (currentSplitFilter === 'LatestUpdates') {
  3144. recordsToProcess = recordsToProcess.filter(r => {
  3145. const dateStr = r.updated_date || r.cdate;
  3146. return dateStr && getLocalDateString(dateStr) === latestUpdateDateString;
  3147. });
  3148. }
  3149. // 1.1. Filter by split-page vendor dropdown filter
  3150. const splitVendorSelect = document.getElementById('split-filter-vendor');
  3151. const splitVendorFilter = splitVendorSelect ? splitVendorSelect.value : 'All';
  3152. if (splitVendorFilter !== 'All') {
  3153. recordsToProcess = recordsToProcess.filter(r => String(r.vend_name || '').trim() === splitVendorFilter);
  3154. }
  3155. // 1.2. Filter by split-page advance type dropdown filter
  3156. const splitAdvSelect = document.getElementById('split-filter-advance-type');
  3157. const splitAdvFilter = splitAdvSelect ? splitAdvSelect.value : 'All';
  3158. if (splitAdvFilter !== 'All') {
  3159. recordsToProcess = recordsToProcess.filter(r => String(r.adv_type_id || '').trim() === splitAdvFilter);
  3160. }
  3161. if (recordsToProcess.length === 0) {
  3162. 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>`;
  3163. return;
  3164. }
  3165. // 1. Group records by advance type (resolved_adv_type)
  3166. const groups = {};
  3167. recordsToProcess.forEach(r => {
  3168. const advType = String(r.resolved_adv_type || 'Others').trim();
  3169. if (!groups[advType]) {
  3170. groups[advType] = [];
  3171. }
  3172. groups[advType].push(r);
  3173. });
  3174. const sortedAdvTypes = Object.keys(groups).sort();
  3175. let html = `
  3176. <div class="table-responsive">
  3177. <table class="table table-bordered table-striped align-middle table-sm" style="border-color: #cbd5e1;">
  3178. <thead class="bg-primary text-white text-center fw-bold" style="background-color: #0a4b64 !important; color: white !important;">
  3179. <tr>
  3180. <th style="width: 60px;">Sl. No.</th>
  3181. <th>Advance Type</th>
  3182. <th>A/C Code</th>
  3183. <th>Year</th>
  3184. <th>To Whom paid</th>
  3185. <th>No. of Files</th>
  3186. <th>No. Of file not available</th>
  3187. <th>No.of file available</th>
  3188. <th>Amt.of Adv.paid</th>
  3189. <th>No.of file adjusted</th>
  3190. <th>adjusted amount</th>
  3191. </tr>
  3192. </thead>
  3193. <tbody>
  3194. `;
  3195. let slNo = 1;
  3196. let grandTotalFiles = 0;
  3197. let grandNotAvailable = 0;
  3198. let grandAvailable = 0;
  3199. let grandAdvPaid = 0;
  3200. let grandAdjustedCount = 0;
  3201. let grandAdjustedAmt = 0;
  3202. sortedAdvTypes.forEach(advType => {
  3203. const recordsInGroup = groups[advType];
  3204. // 2. Sub-group by financial year (fin_year)
  3205. const yearGroups = {};
  3206. recordsInGroup.forEach(r => {
  3207. const fy = String(r.fin_year || '-').trim();
  3208. if (!yearGroups[fy]) {
  3209. yearGroups[fy] = [];
  3210. }
  3211. yearGroups[fy].push(r);
  3212. });
  3213. // Sort years chronologically
  3214. const sortedYears = Object.keys(yearGroups).sort((a, b) => a.localeCompare(b));
  3215. const totalRowsForAdvType = sortedYears.length;
  3216. // Sums for this advance type
  3217. let subTotalFiles = 0;
  3218. let subNotAvailable = 0;
  3219. let subAvailable = 0;
  3220. let subAdvPaid = 0;
  3221. let subAdjustedCount = 0;
  3222. let subAdjustedAmt = 0;
  3223. // Unique A/C Codes for this advance type group
  3224. const acCodes = new Set();
  3225. recordsInGroup.forEach(r => {
  3226. if (r.ac_code && r.ac_code !== '-' && r.ac_code !== 'null') acCodes.add(r.ac_code);
  3227. });
  3228. const acCodeStr = acCodes.size > 0 ? Array.from(acCodes).join(', ') : '-';
  3229. sortedYears.forEach((fy, index) => {
  3230. const subRecords = yearGroups[fy];
  3231. const noOfFiles = subRecords.length;
  3232. let notAvailableCount = 0;
  3233. let availableCount = 0;
  3234. let sumAdvPaid = 0;
  3235. let adjustedCount = 0;
  3236. let sumAdjustedAmt = 0;
  3237. subRecords.forEach(r => {
  3238. // Availability calculation
  3239. let isAvail = false;
  3240. if (r.file_available_status === 'Yes') {
  3241. isAvail = true;
  3242. } else if (r.file_available_status === 'No') {
  3243. isAvail = false;
  3244. } else {
  3245. // Check physical files presence
  3246. const paths = [
  3247. r.file_path, r.po_file_path, r.dc_file_path, r.ir_file_path,
  3248. r.sr_filepath, r.pi_file_path, r.fi_file_path, r.tc_file_path,
  3249. r.rtgs_file_path, r.ejv_num_file_path, r.as_file_path, r.as_final_file_path
  3250. ];
  3251. isAvail = paths.some(p => p && p !== '-' && p !== 'null' && String(p).trim() !== '');
  3252. }
  3253. if (isAvail) {
  3254. availableCount++;
  3255. } else {
  3256. notAvailableCount++;
  3257. }
  3258. // Advance amount sum
  3259. const piAmtVal = r.pi_amt ? Number(r.pi_amt) : 0;
  3260. sumAdvPaid += piAmtVal;
  3261. // Adjusted count & sum
  3262. const ejvNum = String(r.ejv_num || '').trim();
  3263. const ejvPath = String(r.ejv_num_file_path || '').trim();
  3264. const isAdjusted = (ejvNum !== '' && ejvNum !== '-') || (ejvPath !== '' && ejvPath !== '-');
  3265. if (isAdjusted) {
  3266. adjustedCount++;
  3267. sumAdjustedAmt += piAmtVal;
  3268. }
  3269. });
  3270. // Add to sub-totals
  3271. subTotalFiles += noOfFiles;
  3272. subNotAvailable += notAvailableCount;
  3273. subAvailable += availableCount;
  3274. subAdvPaid += sumAdvPaid;
  3275. subAdjustedCount += adjustedCount;
  3276. subAdjustedAmt += sumAdjustedAmt;
  3277. html += `<tr>`;
  3278. if (index === 0) {
  3279. html += `
  3280. <td class="text-center" rowspan="${totalRowsForAdvType}">${slNo}</td>
  3281. <td class="text-center" rowspan="${totalRowsForAdvType}"><strong>${escapeHtml(advType)}</strong></td>
  3282. <td class="text-center font-monospace" rowspan="${totalRowsForAdvType}">${escapeHtml(acCodeStr)}</td>
  3283. `;
  3284. }
  3285. html += `
  3286. <td class="text-center">${escapeHtml(fy)}</td>
  3287. <td class="text-center">${escapeHtml(advType)}</td>
  3288. <td class="text-center">
  3289. <span class="text-primary text-decoration-underline fw-semibold" style="cursor: pointer;" onclick="goToListViewWithSplitFilters('${advType.replace(/'/g, "\\'")}', '${fy}', 'All', 'All')">${noOfFiles}</span>
  3290. </td>
  3291. <td class="text-center">
  3292. <span class="text-danger text-decoration-underline fw-semibold" style="cursor: pointer;" onclick="goToListViewWithSplitFilters('${advType.replace(/'/g, "\\'")}', '${fy}', 'NotAvailable', 'All')">${notAvailableCount}</span>
  3293. </td>
  3294. <td class="text-center">
  3295. <span class="text-success text-decoration-underline fw-semibold" style="cursor: pointer;" onclick="goToListViewWithSplitFilters('${advType.replace(/'/g, "\\'")}', '${fy}', 'Available', 'All')">${availableCount}</span>
  3296. </td>
  3297. <td class="text-end fw-semibold">₹ ${sumAdvPaid.toLocaleString('en-IN')}</td>
  3298. <td class="text-center">
  3299. <span class="text-info text-decoration-underline fw-semibold" style="cursor: pointer;" onclick="goToListViewWithSplitFilters('${advType.replace(/'/g, "\\'")}', '${fy}', 'All', 'Adjusted')">${adjustedCount > 0 ? adjustedCount : ''}</span>
  3300. </td>
  3301. <td class="text-end">${sumAdjustedAmt > 0 ? '₹ ' + sumAdjustedAmt.toLocaleString('en-IN') : ''}</td>
  3302. </tr>
  3303. `;
  3304. });
  3305. // Render Sub-totals Row
  3306. html += `
  3307. <tr class="table-warning fw-bold text-dark" style="background-color: #fef08a !important;">
  3308. <td colspan="5" class="text-end">Total ${escapeHtml(advType)} Advance</td>
  3309. <td class="text-center">${subTotalFiles}</td>
  3310. <td class="text-center">${subNotAvailable}</td>
  3311. <td class="text-center">${subAvailable}</td>
  3312. <td class="text-end">₹ ${subAdvPaid.toLocaleString('en-IN')}</td>
  3313. <td class="text-center">${subAdjustedCount}</td>
  3314. <td class="text-end">₹ ${subAdjustedAmt.toLocaleString('en-IN')}</td>
  3315. </tr>
  3316. `;
  3317. // Add to grand totals
  3318. grandTotalFiles += subTotalFiles;
  3319. grandNotAvailable += subNotAvailable;
  3320. grandAvailable += subAvailable;
  3321. grandAdvPaid += subAdvPaid;
  3322. grandAdjustedCount += subAdjustedCount;
  3323. grandAdjustedAmt += subAdjustedAmt;
  3324. slNo++;
  3325. });
  3326. // Render Grand Total Row
  3327. html += `
  3328. <tr class="bg-dark text-white fw-bold">
  3329. <td colspan="5" class="text-end fw-bold" style="background-color: #1e293b !important; color: #ffffff !important;">Grand Total</td>
  3330. <td class="text-center fw-bold" style="background-color: #1e293b !important; color: #ffffff !important;">${grandTotalFiles}</td>
  3331. <td class="text-center fw-bold" style="background-color: #1e293b !important; color: #ffffff !important;">${grandNotAvailable}</td>
  3332. <td class="text-center fw-bold" style="background-color: #1e293b !important; color: #ffffff !important;">${grandAvailable}</td>
  3333. <td class="text-end fw-bold" style="background-color: #1e293b !important; color: #ffffff !important;">₹ ${grandAdvPaid.toLocaleString('en-IN')}</td>
  3334. <td class="text-center fw-bold" style="background-color: #1e293b !important; color: #ffffff !important;">${grandAdjustedCount}</td>
  3335. <td class="text-end fw-bold" style="background-color: #10b981 !important; color: #ffffff !important;">₹ ${grandAdjustedAmt.toLocaleString('en-IN')}</td>
  3336. </tr>
  3337. </tbody>
  3338. </table>
  3339. </div>
  3340. `;
  3341. container.innerHTML = html;
  3342. }
  3343. // Export split up report to Excel
  3344. function exportSplitUpToExcel() {
  3345. const table = document.querySelector('#panel-split-content table');
  3346. if (!table) {
  3347. Swal.fire({ title: 'Error', text: 'No table data available to export.', icon: 'error' });
  3348. return;
  3349. }
  3350. const wb = XLSX.utils.table_to_book(table, { sheet: "Split-up Report" });
  3351. XLSX.writeFile(wb, "Advance_Split_up_Report.xlsx");
  3352. }
  3353. // Navigate to Existing Files Details view with custom pre-applied filters
  3354. function goToListViewWithSplitFilters(advType, fy, availability, isAdjusted) {
  3355. const cleanFy = str => String(str || '').replace(/\s+/g, '');
  3356. // 1. Set Financial Year dropdown filter (whitespace-insensitive matching)
  3357. const fySelect = document.getElementById('filter-fin-year');
  3358. if (fySelect) {
  3359. const cleanTarget = cleanFy(fy);
  3360. const matchedOpt = Array.from(fySelect.options).find(opt => cleanFy(opt.value) === cleanTarget);
  3361. if (matchedOpt) {
  3362. fySelect.value = matchedOpt.value;
  3363. } else {
  3364. fySelect.value = 'All';
  3365. }
  3366. }
  3367. // 2. Set Advance Type dropdown filter (case-insensitive & trimmed matching)
  3368. const advSelect = document.getElementById('filter-advance-type');
  3369. if (advSelect) {
  3370. const matchedOption = Array.from(advSelect.options).find(opt => opt.textContent.trim().toLowerCase() === advType.trim().toLowerCase());
  3371. if (matchedOption) {
  3372. advSelect.value = matchedOption.value;
  3373. } else {
  3374. advSelect.value = 'All';
  3375. }
  3376. }
  3377. // 3. Set File Available dropdown filter
  3378. const availSelect = document.getElementById('filter-file-available');
  3379. if (availSelect) {
  3380. availSelect.value = availability;
  3381. }
  3382. // 4. Set Adjustment Status dropdown filter
  3383. const statusSelect = document.getElementById('filter-adjustment-status');
  3384. if (statusSelect) {
  3385. statusSelect.value = isAdjusted;
  3386. }
  3387. // 5. Reset other filters to 'All' or empty
  3388. const vendorSelect = document.getElementById('filter-vendor');
  3389. if (vendorSelect) vendorSelect.value = 'All';
  3390. const fundSelect = document.getElementById('filter-fund-type');
  3391. if (fundSelect) fundSelect.value = 'All';
  3392. const searchInput = document.getElementById('table-search');
  3393. if (searchInput) searchInput.value = '';
  3394. // 6. Switch SPA view to list and refresh data grid
  3395. switchView('list');
  3396. }
  3397. // Filter split-up report from dashboard card clicks
  3398. function setSplitFilter(filterType) {
  3399. currentSplitFilter = filterType;
  3400. // 1. Update visual card active indicators
  3401. const totalCard = document.getElementById('card-total-files');
  3402. const adjCard = document.getElementById('card-adjusted-files');
  3403. const pendCard = document.getElementById('card-pending-files');
  3404. const electCard = document.getElementById('card-election-files');
  3405. const notAvailCard = document.getElementById('card-notavailable-files');
  3406. if (totalCard) totalCard.classList.remove('active-card');
  3407. if (adjCard) adjCard.classList.remove('active-card');
  3408. if (pendCard) pendCard.classList.remove('active-card');
  3409. if (electCard) electCard.classList.remove('active-card');
  3410. if (notAvailCard) notAvailCard.classList.remove('active-card');
  3411. if (filterType === 'All') {
  3412. if (totalCard) totalCard.classList.add('active-card');
  3413. const ind = document.getElementById('split-filter-indicator');
  3414. if (ind) ind.textContent = 'All Files';
  3415. } else if (filterType === 'Adjusted') {
  3416. if (adjCard) adjCard.classList.add('active-card');
  3417. const ind = document.getElementById('split-filter-indicator');
  3418. if (ind) ind.textContent = 'Adjusted Files Only';
  3419. } else if (filterType === 'Pending') {
  3420. if (pendCard) pendCard.classList.add('active-card');
  3421. const ind = document.getElementById('split-filter-indicator');
  3422. if (ind) ind.textContent = 'Pending / Not Adjusted Only';
  3423. } else if (filterType === 'Election') {
  3424. if (electCard) electCard.classList.add('active-card');
  3425. const ind = document.getElementById('split-filter-indicator');
  3426. if (ind) ind.textContent = 'Election Files Only';
  3427. } else if (filterType === 'NotAvailable') {
  3428. if (notAvailCard) notAvailCard.classList.add('active-card');
  3429. const ind = document.getElementById('split-filter-indicator');
  3430. if (ind) ind.textContent = 'Not Available Files Only';
  3431. } else if (filterType === 'LatestUpdates') {
  3432. const ind = document.getElementById('split-filter-indicator');
  3433. if (ind) ind.textContent = 'Latest Updates Only';
  3434. }
  3435. // 2. Refresh Split-up table & Details table below
  3436. dashCurrentPage = 1;
  3437. renderSplitUpReport();
  3438. renderDashboardDetails();
  3439. }
  3440. // Reset all dashboard summary counts and filter selections
  3441. function resetDashboardFilters() {
  3442. // 1. Reset Split filter to All
  3443. currentSplitFilter = 'All';
  3444. const ind = document.getElementById('split-filter-indicator');
  3445. if (ind) ind.textContent = 'All Files';
  3446. const totalCard = document.getElementById('card-total-files');
  3447. const adjCard = document.getElementById('card-adjusted-files');
  3448. const pendCard = document.getElementById('card-pending-files');
  3449. const electCard = document.getElementById('card-election-files');
  3450. const notAvailCard = document.getElementById('card-notavailable-files');
  3451. if (totalCard) totalCard.classList.add('active-card');
  3452. if (adjCard) adjCard.classList.remove('active-card');
  3453. if (pendCard) pendCard.classList.remove('active-card');
  3454. if (electCard) electCard.classList.remove('active-card');
  3455. if (notAvailCard) notAvailCard.classList.remove('active-card');
  3456. // 2. Reset Financial Year selector to All
  3457. const fySelect = document.getElementById('filter-fin-year');
  3458. if (fySelect) fySelect.value = 'All';
  3459. // 3. Reset all list view filters to All / Empty
  3460. const vendorSelect = document.getElementById('filter-vendor');
  3461. if (vendorSelect) vendorSelect.value = 'All';
  3462. const statusSelect = document.getElementById('filter-adjustment-status');
  3463. if (statusSelect) statusSelect.value = 'All';
  3464. const advSelect = document.getElementById('filter-advance-type');
  3465. if (advSelect) advSelect.value = 'All';
  3466. const fundSelect = document.getElementById('filter-fund-type');
  3467. if (fundSelect) fundSelect.value = 'All';
  3468. const availSelect = document.getElementById('filter-file-available');
  3469. if (availSelect) availSelect.value = 'All';
  3470. const searchInput = document.getElementById('table-search');
  3471. if (searchInput) searchInput.value = '';
  3472. // 3.1. Reset split-page/dashboard selectors & search as well
  3473. const splitVendorSelect = document.getElementById('split-filter-vendor');
  3474. if (splitVendorSelect) splitVendorSelect.value = 'All';
  3475. const splitAdvSelect = document.getElementById('split-filter-advance-type');
  3476. if (splitAdvSelect) splitAdvSelect.value = 'All';
  3477. const dashSearchInput = document.getElementById('dash-table-search');
  3478. if (dashSearchInput) dashSearchInput.value = '';
  3479. // 3.2. Reset dashboard details paging/sorting state
  3480. dashCurrentPage = 1;
  3481. dashPageSize = 15;
  3482. dashSortField = 'fin_year';
  3483. dashSortDirection = 'desc';
  3484. // 4. Reload database data to ensure counts are fully fresh
  3485. loadRecordsData();
  3486. }
  3487. // --- LATEST UPDATES CORNER BADGE LOGIC ---
  3488. let latestUpdateDateString = null;
  3489. function updateLatestUpdateBadge() {
  3490. const badge = document.getElementById('latest-update-badge');
  3491. if (!badge) return;
  3492. if (!allRecordsIncludingDeleted || allRecordsIncludingDeleted.length === 0) {
  3493. badge.style.setProperty('display', 'none', 'important');
  3494. return;
  3495. }
  3496. // Find the maximum updated_date
  3497. let maxTime = 0;
  3498. let maxDateStr = null;
  3499. allRecordsIncludingDeleted.forEach(r => {
  3500. const dateStr = r.updated_date || r.cdate;
  3501. if (dateStr) {
  3502. const time = new Date(dateStr).getTime();
  3503. if (!isNaN(time) && time > maxTime) {
  3504. maxTime = time;
  3505. maxDateStr = dateStr;
  3506. }
  3507. }
  3508. });
  3509. if (maxTime === 0 || !maxDateStr) {
  3510. badge.style.setProperty('display', 'none', 'important');
  3511. return;
  3512. }
  3513. const latestDateOnly = getLocalDateString(maxDateStr);
  3514. latestUpdateDateString = latestDateOnly;
  3515. // Count how many files share this exact latest update date stamp
  3516. let count = 0;
  3517. allRecordsIncludingDeleted.forEach(r => {
  3518. const dateStr = r.updated_date || r.cdate;
  3519. if (dateStr && getLocalDateString(dateStr) === latestDateOnly) {
  3520. count++;
  3521. }
  3522. });
  3523. // Formatted date string helper
  3524. const formatDateTimeHelper = dateTimeStr => {
  3525. if (!dateTimeStr) return '-';
  3526. try {
  3527. const d = new Date(dateTimeStr);
  3528. if (isNaN(d.getTime())) return dateTimeStr;
  3529. const pad = n => String(n).padStart(2, '0');
  3530. return `${pad(d.getDate())}/${pad(d.getMonth() + 1)}/${d.getFullYear()} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
  3531. } catch (e) {
  3532. return dateTimeStr;
  3533. }
  3534. };
  3535. const countEl = document.getElementById('latest-update-count');
  3536. const timeEl = document.getElementById('latest-update-time');
  3537. if (countEl) countEl.textContent = count;
  3538. if (timeEl) timeEl.textContent = formatDateTimeHelper(maxDateStr);
  3539. badge.style.setProperty('display', 'flex', 'important');
  3540. }
  3541. // Get local date string YYYY-MM-DD
  3542. function getLocalDateString(dateStr) {
  3543. if (!dateStr) return null;
  3544. try {
  3545. const d = new Date(dateStr);
  3546. if (isNaN(d.getTime())) return null;
  3547. const yyyy = d.getFullYear();
  3548. const mm = String(d.getMonth() + 1).padStart(2, '0');
  3549. const dd = String(d.getDate()).padStart(2, '0');
  3550. return `${yyyy}-${mm}-${dd}`;
  3551. } catch (e) {
  3552. return null;
  3553. }
  3554. }
  3555. let latestUpdatesModalObj = null;
  3556. function openLatestUpdatesModal() {
  3557. if (!latestUpdateDateString) return;
  3558. // Filter allRecordsIncludingDeleted to find files updated on the latest date
  3559. const files = allRecordsIncludingDeleted.filter(r => {
  3560. const dateStr = r.updated_date || r.cdate;
  3561. return dateStr && getLocalDateString(dateStr) === latestUpdateDateString;
  3562. });
  3563. if (files.length === 0) return;
  3564. // Set modal title
  3565. const timeEl = document.getElementById('latest-update-time');
  3566. const timeStr = timeEl ? timeEl.textContent : '';
  3567. document.getElementById('latest-updates-modal-title').innerHTML = `<i class="fa-solid fa-bell text-warning me-2"></i> Latest Updates (${timeStr})`;
  3568. // Populate the modal table body
  3569. const tbody = document.getElementById('latest-updates-modal-body');
  3570. tbody.innerHTML = '';
  3571. let currentSNo = 1;
  3572. files.forEach(r => {
  3573. const balAmt = r.bal_amt ? Number(r.bal_amt).toLocaleString('en-IN') : '0';
  3574. // Determine update status tag
  3575. let statusBadge = '';
  3576. const cTime = r.cdate ? new Date(r.cdate).getTime() : 0;
  3577. const uTime = r.updated_date ? new Date(r.updated_date).getTime() : 0;
  3578. const isSoftDeleted = r.is_delete === 1 || r.is_delete === '1' || r.is_active === 0 || r.is_active === '0';
  3579. if (isSoftDeleted) {
  3580. statusBadge = `<span class="badge bg-danger">Deleted</span>`;
  3581. } else if (cTime > 0 && uTime > 0 && Math.abs(cTime - uTime) < 2000) {
  3582. statusBadge = `<span class="badge bg-success">Added</span>`;
  3583. } else {
  3584. statusBadge = `<span class="badge bg-primary">Updated</span>`;
  3585. }
  3586. tbody.innerHTML += `
  3587. <tr>
  3588. <td class="text-center">${currentSNo++}</td>
  3589. <td class="text-center">${escapeHtml(r.fin_year)}</td>
  3590. <td>${escapeHtml(r.file_data_num || '-')}</td>
  3591. <td>${escapeHtml(r.vend_name || '-')}</td>
  3592. <td class="text-center"><span class="badge bg-secondary">${escapeHtml(r.resolved_adv_type || '-')}</span></td>
  3593. <td class="text-center">${statusBadge}</td>
  3594. <td class="text-center">
  3595. ${r.file_available_status === 'Yes'
  3596. ? `<span class="badge bg-success-light text-success border border-success px-2 py-0.5">Yes</span>`
  3597. : r.file_available_status === 'No'
  3598. ? `<span class="badge bg-danger-light text-danger border border-danger px-2 py-0.5">No</span>`
  3599. : '-'}
  3600. </td>
  3601. <td class="fw-semibold text-primary text-end">₹ ${balAmt}</td>
  3602. <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>
  3603. <td class="text-center">${formatDateTime(r.cdate)}</td>
  3604. <td class="text-center">${escapeHtml(r.updated_by_name || r.updated_by || '-')}</td>
  3605. <td class="text-center">${formatDateTime(r.updated_date)}</td>
  3606. <td class="text-center">
  3607. <div class="action-btn-group justify-content-center">
  3608. <button class="action-btn btn-view" title="View Lifecycle"
  3609. onclick="closeLatestUpdatesModalAndShowDetails(${r.id})">
  3610. <i class="fa-solid fa-eye"></i>
  3611. </button>
  3612. ${isSoftDeleted
  3613. ? ''
  3614. : `<button class="action-btn btn-edit" title="Edit File" onclick="closeLatestUpdatesModalAndEdit(${r.id})">
  3615. <i class="fa-solid fa-pen-to-square"></i>
  3616. </button>`
  3617. }
  3618. </div>
  3619. </td>
  3620. </tr>
  3621. `;
  3622. });
  3623. // Initialize bootstrap modal if needed
  3624. if (!latestUpdatesModalObj) {
  3625. latestUpdatesModalObj = new bootstrap.Modal(document.getElementById('latestUpdatesModal'));
  3626. }
  3627. latestUpdatesModalObj.show();
  3628. }
  3629. function closeLatestUpdatesModalAndShowDetails(id) {
  3630. if (latestUpdatesModalObj) {
  3631. latestUpdatesModalObj.hide();
  3632. }
  3633. setTimeout(() => {
  3634. viewRecordDetails(id);
  3635. }, 300);
  3636. }
  3637. function closeLatestUpdatesModalAndEdit(id) {
  3638. if (latestUpdatesModalObj) {
  3639. latestUpdatesModalObj.hide();
  3640. }
  3641. setTimeout(() => {
  3642. editRecord(id);
  3643. }, 300);
  3644. }
  3645. // HTML escaping helper
  3646. function escapeHtml(str) {
  3647. if (!str) return '';
  3648. return String(str)
  3649. .replace(/&/g, "&amp;")
  3650. .replace(/</g, "&lt;")
  3651. .replace(/>/g, "&gt;")
  3652. .replace(/"/g, "&quot;")
  3653. .replace(/'/g, "&#039;");
  3654. }
  3655. </script>
  3656. </body>
  3657. </html>