Browse Source

newly added files type wise count service, controller and pages

master
yoganandhan 6 months ago
parent
commit
d865fb2fb7
  1. 11
      .env
  2. 2
      src/main/java/com/adjustment_file_management_system/springboot/AdjustmentFileManagementSystemApplication.java
  3. 8
      src/main/java/com/adjustment_file_management_system/springboot/controller/FileDataController.java
  4. 5
      src/main/java/com/adjustment_file_management_system/springboot/controller/HomeController.java
  5. 15
      src/main/java/com/adjustment_file_management_system/springboot/service/FileDataService.java
  6. 9
      src/main/resources/static/js/view-file-detail-script.js
  7. 46
      src/main/resources/static/js/view-file-script.js
  8. 181
      src/main/resources/templates/file/reports.html
  9. 6
      src/main/resources/templates/file/view-file-detail.html
  10. 121
      src/main/resources/templates/home/home.html

11
.env

@ -1,4 +1,9 @@
DB_URL=jdbc:oracle:thin:@10.1.0.14:1521:chncorp
DB_USERNAME=fileadjust
DB_PASSWORD=fileadjust
# DB_URL=jdbc:oracle:thin:@10.1.0.14:1521:chncorp
# DB_USERNAME=fileadjust
# DB_PASSWORD=fileadjust
# FORCE_HTTPS=false
DB_URL=jdbc:oracle:thin:@//localhost:1522/XEPDB1
DB_USERNAME=system
DB_PASSWORD=root
FORCE_HTTPS=false

2
src/main/java/com/adjustment_file_management_system/springboot/AdjustmentFileManagementSystemApplication.java

@ -16,7 +16,7 @@ public class AdjustmentFileManagementSystemApplication {
static {
Dotenv dotEnv = Dotenv.configure().ignoreIfMissing().load();
for(DotenvEntry entry : dotEnv.entries()) {
for (DotenvEntry entry : dotEnv.entries()) {
System.setProperty(entry.getKey(), entry.getValue());
}
}

8
src/main/java/com/adjustment_file_management_system/springboot/controller/FileDataController.java

@ -79,4 +79,12 @@ public class FileDataController {
return "file/view-file";
}
@GetMapping("/reports")
public String viewReports(Model model) {
List<FileDataResponseDTO> fileDataResDTOs = fileDataSer.getAllFileData();
model.addAttribute("fileDatum", fileDataResDTOs);
model.addAttribute("curPage", "reports");
return "file/reports";
}
}

5
src/main/java/com/adjustment_file_management_system/springboot/controller/HomeController.java

@ -10,6 +10,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.adjustment_file_management_system.springboot.service.HomeService;
import com.adjustment_file_management_system.springboot.service.FileDataService;
@Controller
@RequestMapping("/")
@ -18,11 +19,15 @@ public class HomeController {
@Autowired
private HomeService homeSer;
@Autowired
private FileDataService fileDataSer;
@GetMapping("/home")
public String homePage(Model model) {
Map<String, Long> stats = homeSer.getFileDataStat();
model.addAttribute("curPage", "home");
model.addAttribute("stats", stats);
model.addAttribute("fileDatum", fileDataSer.getAllFileData());
return "home/home";
}

15
src/main/java/com/adjustment_file_management_system/springboot/service/FileDataService.java

@ -267,9 +267,23 @@ public class FileDataService {
}
}
private void normalizeFinYear(FileDataResponseDTO dto) {
if (dto.getFinYear() != null && dto.getFinYear().contains("-")) {
String[] parts = dto.getFinYear().split("-");
if (parts.length == 2 && parts[1].trim().length() == 2) {
String startYear = parts[0].trim();
if (startYear.length() == 4) {
String endYear = startYear.substring(0, 2) + parts[1].trim();
dto.setFinYear(startYear + "-" + endYear);
}
}
}
}
@Transactional(readOnly = true)
public FileDataResponseDTO getFileDataByFileNum(String fileNum) {
FileDataResponseDTO dto = fileDataMap.toFileDataResDTO(fileDataRepo.findFileDataByFileDataNum(fileNum));
normalizeFinYear(dto);
calculateDaysPass(dto);
return dto;
}
@ -288,6 +302,7 @@ public class FileDataService {
return fileDataRepo.findAll().stream().map(fileData -> {
FileDataResponseDTO dto = fileDataMap.toFileDataResDTO(fileData);
normalizeFinYear(dto);
calculateDaysPass(dto);
return dto;
}).sorted(comparator).toList();

9
src/main/resources/static/js/view-file-detail-script.js

@ -13,6 +13,7 @@ function viewFileInModal(fileNum, fileType, filePath) {
var modalTitle = document.getElementById('pdf-modal-label');
var pdfContainer = document.getElementById('pdf-container');
pdfContainer.innerHTML = ''; // Clear previous pages
pdfContainer.scrollTop = 0; // Scroll to the first page (top)
var decodedFilePath = decodeURIComponent(filePath);
var fileName = decodedFilePath.split(/[\\/]/).pop();
@ -31,11 +32,13 @@ function viewFileInModal(fileNum, fileType, filePath) {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params.toString()
}).then(function (response) {
if (!response.ok) {
throw new Error('Failed to fetch PDF: ' + response.statusText);
}
return response.arrayBuffer();
}).then(function (pdfData) {
console.log(pdfData);
var blob = new Blob([pdfData], { type: 'application/pdf' });
var blobUrl = URL.createObjectURL(blob);
@ -69,6 +72,12 @@ function viewFileInModal(fileNum, fileType, filePath) {
return sequence;
}).then(function () {
var modalElement = document.getElementById('pdf-modal');
modalElement.addEventListener('shown.bs.modal', function onShown() {
pdfContainer.scrollTop = 0; // Reset scroll position
modalElement.scrollTop = 0; // Reset modal scroll
modalElement.removeEventListener('shown.bs.modal', onShown); // Clean up listener
});
pdfModal.show();
}).catch(function (error) {
alert('Error loading PDF: ' + error.message);

46
src/main/resources/static/js/view-file-script.js

@ -1,4 +1,4 @@
$(document).ready(function() {
$(document).ready(function () {
// Initialize DataTable
var table = $('#file-det-tbl').DataTable({
paging: true,
@ -27,7 +27,7 @@ $(document).ready(function() {
exportOptions: {
columns: ':visible:not(:eq(12))', // exclude 13th column
format: {
body: function(data, row, column, node) {
body: function (data, row, column, node) {
// If it's the first column (S.No), replace with sequential number
if (column === 0) {
return row + 1; // row is 0-based index of **visible filtered rows**
@ -42,9 +42,9 @@ $(document).ready(function() {
});
// --- SEQUENTIAL S.NO FIX ---
table.on('order.dt search.dt draw.dt', function() {
table.on('order.dt search.dt draw.dt', function () {
var pageInfo = table.page.info(); // get current page info
table.column(0, { search: 'applied', order: 'applied' }).nodes().each(function(cell, i) {
table.column(0, { search: 'applied', order: 'applied' }).nodes().each(function (cell, i) {
cell.innerHTML = i + 1 + pageInfo.start; // keep numbering continuous across pages
});
}).draw();
@ -56,24 +56,24 @@ $(document).ready(function() {
$('.dataTables_filter').hide(); // hide default search
// Custom search box
$('#filter-records').on('keyup', function() {
$('#filter-records').on('keyup', function () {
table.search($(this).val()).draw();
});
// Custom export buttons
$('#export-excel').on('click', function() {
$('#export-excel').on('click', function () {
table.button('.buttons-excel').trigger();
});
$('#export-pdf').on('click', function() {
$('#export-pdf').on('click', function () {
var originalLength = table.page.len();
table.one('draw', function() {
table.one('draw', function () {
var tableClone = $('#file-det-tbl').clone();
tableClone.removeClass('dataTable');
tableClone.find('thead, tbody, tfoot').removeAttr('style');
// Remove "Supporting Documents" column (13th col = index 12)
tableClone.find('tr').each(function() {
tableClone.find('tr').each(function () {
$(this).find('th:eq(12), td:eq(12)').remove();
});
@ -98,7 +98,7 @@ $(document).ready(function() {
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 2, useCORS: true },
jsPDF: { unit: 'in', format: 'a3', orientation: 'landscape' }
}).from(pdfContainer[0]).save().then(function() {
}).from(pdfContainer[0]).save().then(function () {
table.page.len(originalLength).draw();
pdfContainer.hide();
});
@ -109,7 +109,7 @@ $(document).ready(function() {
function populateFilters() {
var advSet = {}, fundSet = {}, yearSet = {}, statSet = {}, numSet = {}, vendSet = {};
table.rows().every(function() {
table.rows().every(function () {
var data = this.data();
var advType = data[3] ? data[3].trim() : '';
@ -170,4 +170,28 @@ $(document).ready(function() {
$('#filter-file-stat').on('input', applyFilters);
$('#filter-file-num').on('input', applyFilters);
$('#filter-vend-name').on('input', applyFilters); // input, not datalist
// --- Check for URL parameters to pre-apply filters ---
const urlParams = new URLSearchParams(window.location.search);
const advTypeParam = urlParams.get('advType');
const finYearParam = urlParams.get('finYear');
const fileStatParam = urlParams.get('fileStat');
let shouldApplyFilter = false;
if (advTypeParam) {
$('#filter-adv-type').val(advTypeParam);
shouldApplyFilter = true;
}
if (finYearParam) {
$('#filter-fin-year').val(finYearParam);
shouldApplyFilter = true;
}
if (fileStatParam) {
$('#filter-file-stat').val(fileStatParam);
shouldApplyFilter = true;
}
if (shouldApplyFilter) {
applyFilters();
}
});

181
src/main/resources/templates/file/reports.html

@ -0,0 +1,181 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Adjustment File Management System</title>
<!-- Include CSS -->
<link rel="stylesheet" type="text/css" th:href="@{/css/body-form-styles.css}">
<link rel="stylesheet" type="text/css" th:href="@{/css/datatable-styles.css}">
<link rel="stylesheet" type="text/css" th:href="@{/css/home-styles.css}">
<link rel="stylesheet" type="text/css" th:href="@{/css/view-file-styles.css}">
<link rel="stylesheet" type="text/css" th:href="@{/css/btn-secondary-styles.css}">
<!-- CSRF Tokens -->
<meta name="_csrf" th:content="${_csrf.token}" />
<meta name="_csrf_header" th:content="${_csrf.headerName}" />
<!-- Header Fragment -->
<th:block th:replace="~{fragments/header :: header}"></th:block>
</head>
<body>
<!-- Navigation -->
<th:block th:replace="~{fragments/navbar :: navbar(${curPage})}"></th:block>
<div class="container">
<div class="form-container">
<div class="header-row">
<button class="back-button" onclick="goBack()">&#8592; Back</button>
</div>
<h2 class="form-title">Reports</h2>
<!-- Button and Filter Bar -->
<div class="row mb-3">
<!-- Adds spacing and a responsive layout -->
<div class="col-12 col-md-8 mb-2 mb-md-0">
<!-- Buttons on the left -->
<!-- <div class="btn-group" role="group" aria-label="Options">
<button type="button" th:onclick="|window.location.href='@{/file/upd-file}'|"
class="btn btn-secondary">
<i class="fa-solid fa-file-pen"></i> Update File
</button>
<button type="button" th:onclick="|window.location.href='@{/file/upd-ejv-num}'|"
class="btn btn-secondary">
<i class="fas fa-light fa-pen"></i> Update EJV Number
</button>
</div> -->
</div>
<div class="col-12 col-md-8 mb-2 mb-md-0" style="padding-top:5px">
<div class="btn-group" role="group" aria-label="Options">
<button class="btn btn-secondary" id="export-excel" name="export-excel">
<i class="fas fa-file-excel"></i> Export Excel
</button>
<button class="btn btn-secondary" id="export-pdf" name="export-pdf">
<i class="fas fa-file-pdf"></i> Export PDF
</button>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4 form-group">
<label for="filter-adv-type" id="filter-adv-type-lbl">Filter By Advance Type : </label>
<input list="adv-type-list" class="form-control" id="filter-adv-type" name="filter-adv-type"
autocomplete="off" placeholder="Select Advance Type">
<datalist id="adv-type-list">
<!-- Options will be filled by JavaScript -->
</datalist>
</div>
<div class="col-md-4 form-group">
<label for="filter-fund-type" id="filter-fund-type-lbl">Filter By Fund Type : </label>
<input list="fund-list" class="form-control" id="filter-fund-type" name="filter-fund-type"
autocomplete="off" placeholder="Select Fund Type">
<datalist id="fund-list">
<!-- Options will be filled by JavaScript -->
</datalist>
</div>
<div class="col-md-4 form-group">
<label for="filter-fin-year" id="filter-fin-year-lbl">Filter By Financial Year : </label>
<input list="fin-year-list" class="form-control" id="filter-fin-year" name="filter-fin-year"
autocomplete="off" placeholder="Select Financial Year">
<datalist id="fin-year-list">
<!-- Options will be filled by JavaScript -->
</datalist>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4 form-group">
<label for="filter-file-stat" id="filter-file-stat-lbl">Filter By File Status : </label>
<input list="file-stat-list" class="form-control" id="filter-file-stat" name="filter-file-stat"
autocomplete="off" placeholder="Select File Status">
<datalist id="file-stat-list">
<!-- Options will be filled by JavaScript -->
</datalist>
</div>
<div class="col-md-4 form-group">
<label for="filter-file-num" id="filter-file-num-lbl">Filter By File Number : </label>
<input list="file-num-list" class="form-control" id="filter-file-num" name="filter-file-num"
autocomplete="off" placeholder="Select File Number">
<datalist id="file-num-list">
<!-- Options will be filled by JavaScript -->
</datalist>
</div>
<div class="col-md-4 form-group">
<label for="filter-vend-name" id="filter-vend-name-lbl">Filter By Vendor Name : </label>
<input list="vend-name-list" class="form-control" id="filter-vend-name" name="filter-vend-name"
autocomplete="off" placeholder="Select Vendor Name">
<datalist id="vend-name-list">
<!-- Options will be filled by JavaScript -->
</datalist>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4" id="custom-length"></div>
</div>
<div class="table-responsive">
<table class="det-table" id="file-det-tbl">
<thead>
<tr class="table-header">
<th>S.No</th>
<th class="text-center">File Number</th>
<th>Financial Year</th>
<th>Advance Type</th>
<th>Fund Type</th>
<th>Vendor Name</th>
<th>File Status</th>
<th>Days Past</th>
<th>File Remarks</th>
</tr>
</thead>
<tbody>
<tr th:each="fileData : ${fileDatum}"
th:attr="onclick=|window.location.href='/file/view-file-det?file-num=${fileData.fileDataNum}'|"
style="cursor: pointer;">
<td></td>
<!-- File Number -->
<td class="text-center" th:text="${fileData.fileDataNum}"></td>
<!-- Financial Year -->
<td th:text="${fileData.finYear != null ? fileData.finYear : '-'}"></td>
<!-- Advance Type -->
<td th:text="${fileData.advTypeResDTO.advType}"></td>
<!-- Fund Type -->
<td th:text="${fileData.fundTypeResDTO.fundType}"></td>
<!-- Vendor Name -->
<td
th:text="${fileData.vendName != null and fileData.vendName != '-' ? fileData.vendName : 'None'}">
</td>
<!-- File Status -->
<td class="status-cell">
<span th:with="ejvNum=${fileData.ejvNum}, ejvNumFile=${fileData.ejvNumFilePath}"
th:text="${ejvNum != '-' and ejvNumFile != '-' ? 'Adjusted' : 'Not Adjusted'}"
th:classappend="${ejvNum != '-' and ejvNumFile != '-' ? 'status-active' : 'status-expired'}">
</span>
</td>
<td th:text="${fileData.daysPass}"></td>
<!-- File Remarks -->
<td th:text="${fileData.fileRmrk != null ? fileData.fileRmrk : '-'}"></td>
</tr>
</tbody>
</table>
</div>
<br />
<div class="row mb-3">
<div class="col-md-4 text-center" id="custom-info"></div>
<div class="col-md-8" id="custom-pagination"></div>
</div>
<th:block th:replace="~{fragments/export-pdf :: export-pdf}"></th:block>
</div>
</div>
<th:block th:replace="~{modal/pdf-modal :: pdf-modal}"></th:block>
<!-- Footer Fragment -->
<th:block th:replace="~{fragments/footer :: footer}"></th:block>
<!-- Custom JS -->
<script th:src="@{/js/view-file-script.js}" type="text/javascript"></script>
</body>
</html>

6
src/main/resources/templates/file/view-file-detail.html

@ -130,6 +130,12 @@
type="button">
<i class="fas fa-light fa-eye" style="cursor: pointer"></i>
</button>
<!-- <button data-path="uploads/file-data/I.T.C.C.No.C2/551/2026/PI/eoffice_ref-pi-2026.pdf"
th:attr="data-file-num=${fileDatum.fileDataNum}"
onclick="viewFileInModal(this.dataset.fileNum, 'PI', this.dataset.path)" class="btn"
type="button">
<i class="fas fa-light fa-eye" style="cursor: pointer"></i>
</button> -->
</p>
</div>
<div class="col-12 col-md-3"

121
src/main/resources/templates/home/home.html

@ -35,12 +35,29 @@
</button>
</div>
<br />
<div class="row mb-3 justify-content-center">
<div class="col-md-8 form-group text-center">
<label for="home-fin-year" class="fw-bold">Financial Year : </label>
<select class="form-select d-inline-block w-auto ms-2" id="home-fin-year"
style="min-width: 150px; padding: 5px; border-radius: 5px; vertical-align: middle;">
<option value="">All</option>
</select>
<button type="button" id="btn-apply-filter" class="custom-button ms-2"
style="padding: 5px 15px; font-size: 14px; vertical-align: middle;">
Apply
</button>
</div>
</div>
<div class="stats">
Total Files : <span th:text="${stats.totFile}"></span><br />
<!--Total I.T.C Files : <span th:text="${stats.totItcFile}"></span><br />
Total E.D.P Files : <span th:text="${stats.totEdpFile}"></span><br />-->
Files Adjusted : <span th:text="${stats.totAdjustFile}"></span><br />
Files Not Adjusted : <span th:text="${stats.totNotAdjustFile}"></span><br />
<!-- Dynamic Total File Counts -->
<div id="dynamic-total-counts" class="mb-2"></div>
<!-- Dynamic Advance Type Counts -->
<div id="dynamic-adv-counts" class="mb-2"></div>
<!-- Dynamic Status Counts -->
<div id="dynamic-status-counts" class="mb-2"></div>
</div>
</div>
</div>
@ -48,6 +65,100 @@
<!-- Footer Fragment -->
<th:block th:replace="~{fragments/footer :: footer}"></th:block>
<script th:inline="javascript">
$(document).ready(function () {
var fileDataList = /*[[${fileDatum}]]*/[];
if (!fileDataList) fileDataList = [];
var yearSet = new Set();
// Extract unique financial years
fileDataList.forEach(function (file) {
if (file.finYear && file.finYear.trim() !== "") {
yearSet.add(file.finYear.trim());
}
});
var years = Array.from(yearSet).sort().reverse(); // Sort descending
var yearSelect = $('#home-fin-year');
years.forEach(function (year) {
yearSelect.append('<option value="' + year + '">' + year + '</option>');
});
function renderAdvCounts() {
var selectedYear = yearSelect.val();
var advCounts = {};
var adjCount = 0;
var notAdjCount = 0;
var totalCount = 0;
fileDataList.forEach(function (file) {
if (selectedYear && file.finYear !== selectedYear) {
return; // skip if doesn't match selected year
}
totalCount++;
var advType = file.advTypeResDTO && file.advTypeResDTO.advType ? file.advTypeResDTO.advType : "Unknown";
advCounts[advType] = (advCounts[advType] || 0) + 1;
// File Status Check
if (file.ejvNum != null && file.ejvNum !== '-' && file.ejvNumFilePath != null && file.ejvNumFilePath !== '-') {
adjCount++;
} else {
notAdjCount++;
}
});
var totalContainer = $('#dynamic-total-counts');
totalContainer.empty();
var totalParams = "";
if (selectedYear) {
totalParams = "?finYear=" + encodeURIComponent(selectedYear);
}
totalContainer.append(
'<button type="button" onclick="window.location.href=\'/file/reports' + totalParams + '\'" class="custom-button mb-2">Total Files : ' + totalCount + '</button><br/>'
);
var container = $('#dynamic-adv-counts');
container.empty();
Object.keys(advCounts).sort().forEach(function (advType) {
var count = advCounts[advType];
var queryParams = "?advType=" + encodeURIComponent(advType);
if (selectedYear) {
queryParams += "&finYear=" + encodeURIComponent(selectedYear);
}
var btnHtml = '<button type="button" onclick="window.location.href=\'/file/reports' + queryParams + '\'" class="custom-button mb-2" style="font-size: 16px; padding: 5px 15px;">' +
advType + ' Files : ' + count +
'</button><br/>';
container.append(btnHtml);
});
var statusContainer = $('#dynamic-status-counts');
statusContainer.empty();
var adjParams = "?fileStat=" + encodeURIComponent("Adjusted");
var notAdjParams = "?fileStat=" + encodeURIComponent("Not Adjusted");
if (selectedYear) {
adjParams += "&finYear=" + encodeURIComponent(selectedYear);
notAdjParams += "&finYear=" + encodeURIComponent(selectedYear);
}
statusContainer.append(
'<button type="button" onclick="window.location.href=\'/file/reports' + adjParams + '\'" class="custom-button mb-2" style="font-size: 16px; padding: 5px 15px;">Files Adjusted : ' + adjCount + '</button><br/>'
);
statusContainer.append(
'<button type="button" onclick="window.location.href=\'/file/reports' + notAdjParams + '\'" class="custom-button mb-2" style="font-size: 16px; padding: 5px 15px;">Files Not Adjusted : ' + notAdjCount + '</button><br/>'
);
}
$('#btn-apply-filter').on('click', function () {
renderAdvCounts();
});
// Optionally render initially or leave it blank until apply is clicked.
// renderAdvCounts();
});
</script>
</body>
</html>
Loading…
Cancel
Save