Browse Source

added gcc services documents upload module

master
yoga-agoy 4 months ago
parent
commit
c372d416a1
  1. BIN
      gcc_manpower_uploads/workorderfiles/1011 WO.pdf
  2. BIN
      gcc_manpower_uploads/workorderfiles/485 - 2025 WO 1.pdf
  3. BIN
      gcc_manpower_uploads/workorderfiles/618 Extension WO.pdf
  4. BIN
      gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C111802025.pdf
  5. BIN
      gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C114642025.pdf
  6. BIN
      gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C11702026.pdf
  7. BIN
      gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C12182025.pdf
  8. BIN
      gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C124102025.pdf
  9. BIN
      gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C12472026.pdf
  10. BIN
      gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C126592025.pdf
  11. BIN
      gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C12852026.pdf
  12. BIN
      gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C132302025.pdf
  13. BIN
      gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C136782025.pdf
  14. BIN
      gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C137392025.pdf
  15. BIN
      gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C139982025.pdf
  16. BIN
      gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C145902023.pdf
  17. BIN
      gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C14852025.pdf
  18. BIN
      gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C15962026.pdf
  19. BIN
      gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C16182025.pdf
  20. BIN
      gcc_manpower_uploads/workorderfiles/WO (4).pdf
  21. 7
      pom.xml
  22. 2
      src/main/java/com/gcc_manpower/GccManpowerApplication.java
  23. 10
      src/main/java/com/gcc_manpower/config/DataSourceConfig.java
  24. 8
      src/main/java/com/gcc_manpower/config/WebConfig.java
  25. 192
      src/main/java/com/gcc_manpower/controller/GccServiceDocumentController.java
  26. 180
      src/main/java/com/gcc_manpower/service/GccServiceDocumentService.java
  27. 98
      src/main/java/com/gcc_manpower/service/VideoCompressionService.java
  28. 4
      src/main/resources/application-local.properties
  29. 4
      src/main/resources/application-prod.properties
  30. 22
      src/main/resources/templates/fragments/documentsheader.html
  31. 637
      src/main/resources/templates/gccservicedocuments/upload.html
  32. 1115
      src/main/resources/templates/gccservicedocuments/view.html

BIN
gcc_manpower_uploads/workorderfiles/1011 WO.pdf

BIN
gcc_manpower_uploads/workorderfiles/485 - 2025 WO 1.pdf

BIN
gcc_manpower_uploads/workorderfiles/618 Extension WO.pdf

BIN
gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C111802025.pdf

BIN
gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C114642025.pdf

BIN
gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C11702026.pdf

BIN
gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C12182025.pdf

BIN
gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C124102025.pdf

BIN
gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C12472026.pdf

BIN
gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C126592025.pdf

BIN
gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C12852026.pdf

BIN
gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C132302025.pdf

BIN
gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C136782025.pdf

BIN
gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C137392025.pdf

BIN
gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C139982025.pdf

BIN
gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C145902023.pdf

BIN
gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C14852025.pdf

BIN
gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C15962026.pdf

BIN
gcc_manpower_uploads/workorderfiles/I.T.C.C.No.C16182025.pdf

BIN
gcc_manpower_uploads/workorderfiles/WO (4).pdf

7
pom.xml

@ -9,7 +9,7 @@
<relativePath/> <!-- lookup parent from repository --> <relativePath/> <!-- lookup parent from repository -->
</parent> </parent>
<groupId>com.gcc_manpower</groupId> <groupId>com.gcc_manpower</groupId>
<artifactId>gcc-manpower</artifactId>
<artifactId>gccmanpower</artifactId>
<version>0.0.1-SNAPSHOT</version> <version>0.0.1-SNAPSHOT</version>
<name/> <name/>
<description/> <description/>
@ -58,6 +58,11 @@
<artifactId>mysql-connector-j</artifactId> <artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-all-deps</artifactId>
<version>3.5.0</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>

2
src/main/java/com/gcc_manpower/GccManpowerApplication.java

@ -2,8 +2,10 @@ package com.gcc_manpower;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication @SpringBootApplication
@EnableAsync
public class GccManpowerApplication { public class GccManpowerApplication {
public static void main(String[] args) { public static void main(String[] args) {

10
src/main/java/com/gcc_manpower/config/DataSourceConfig.java

@ -26,4 +26,14 @@ public class DataSourceConfig {
dataSource.setPassword(dbpassword); dataSource.setPassword(dbpassword);
return dataSource; return dataSource;
} }
@Bean(name = "mysqlServiceDocumentsDataSource")
public DataSource mysqlServiceDocumentsDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://" + host + "/gccservices_documents");
dataSource.setUsername("root");
dataSource.setPassword(dbpassword);
return dataSource;
}
} }

8
src/main/java/com/gcc_manpower/config/WebConfig.java

@ -12,6 +12,9 @@ public class WebConfig implements WebMvcConfigurer {
@Value("${upload.path:gcc_manpower_uploads}") @Value("${upload.path:gcc_manpower_uploads}")
private String uploadPathConfig; private String uploadPathConfig;
@Value("${document.upload.path:gccservicedocuments}")
private String documentUploadPathConfig;
@Override @Override
public void addResourceHandlers(ResourceHandlerRegistry registry) { public void addResourceHandlers(ResourceHandlerRegistry registry) {
// Resolve absolute path (supports both relative and absolute paths) // Resolve absolute path (supports both relative and absolute paths)
@ -20,5 +23,10 @@ public class WebConfig implements WebMvcConfigurer {
// Map URL path /gcc_manpower_uploads/** to the configured folder path // Map URL path /gcc_manpower_uploads/** to the configured folder path
registry.addResourceHandler("/gcc_manpower_uploads/**") registry.addResourceHandler("/gcc_manpower_uploads/**")
.addResourceLocations(uploadPath); .addResourceLocations(uploadPath);
// Map URL path /gccservicedocuments/files/** to the configured document folder path
String docUploadPath = Paths.get(documentUploadPathConfig).toAbsolutePath().toUri().toString();
registry.addResourceHandler("/gccservicedocuments/files/**")
.addResourceLocations(docUploadPath);
} }
} }

192
src/main/java/com/gcc_manpower/controller/GccServiceDocumentController.java

@ -0,0 +1,192 @@
package com.gcc_manpower.controller;
import com.gcc_manpower.service.GccServiceDocumentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import jakarta.servlet.http.HttpSession;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Controller
@RequestMapping("/gccservicedocuments")
public class GccServiceDocumentController {
@Autowired
private GccServiceDocumentService documentService;
@Autowired
private com.gcc_manpower.service.VideoCompressionService videoCompressionService;
@Value("${document.upload.path:gccservicedocuments}")
private String documentUploadPathConfig;
@GetMapping({ "", "/" })
public String viewPortal(Model model, HttpSession session) {
// String usrName = (String) session.getAttribute("usrName");
// String usrRole = (String) session.getAttribute("usrRole");
// boolean isLoggedIn = (usrName != null);
// boolean isAdmin = "ADMIN".equalsIgnoreCase(usrRole);
model.addAttribute("isLoggedIn", true);
model.addAttribute("isAdmin", true);
model.addAttribute("usrName", "Guest");
model.addAttribute("curPage", "gccservicedocuments");
return "gccservicedocuments/upload";
}
@GetMapping("/view")
public String viewReportPage(Model model, HttpSession session) {
// String usrName = (String) session.getAttribute("usrName");
// if (usrName == null) {
// return "redirect:/login";
// }
// String usrRole = (String) session.getAttribute("usrRole");
// boolean isAdmin = "ADMIN".equalsIgnoreCase(usrRole);
model.addAttribute("isLoggedIn", true);
model.addAttribute("isAdmin", true);
model.addAttribute("usrName", "Guest");
model.addAttribute("curPage", "gccservicedocuments_upload");
return "gccservicedocuments/view";
}
@GetMapping("/api/list")
@ResponseBody
public List<Map<String, Object>> listDocuments(
@RequestParam(value = "category", required = false) String category,
@RequestParam(value = "fromDate", required = false) String fromDate,
@RequestParam(value = "toDate", required = false) String toDate) {
return documentService.searchDocuments(category, fromDate, toDate);
}
@GetMapping("/api/categories")
@ResponseBody
public List<String> getCategories() {
return documentService.getAllCategories();
}
@GetMapping("/api/file-types")
@ResponseBody
public List<Map<String, Object>> getFileTypes() {
return documentService.getAllFileTypes();
}
@PostMapping("/api/upload")
@ResponseBody
public Map<String, Object> uploadFile(
@RequestParam("title") String title,
@RequestParam("category") String category,
@RequestParam("fileType") String fileType,
@RequestParam("file") MultipartFile file,
HttpSession session) {
Map<String, Object> response = new HashMap<>();
// String usrName = (String) session.getAttribute("usrName");
// if (usrName == null) {
// response.put("success", false);
// response.put("error", "Unauthorized access! Please log in.");
// return response;
// }
if (file.isEmpty()) {
response.put("success", false);
response.put("error", "Please select a file to upload.");
return response;
}
try {
// Clean category name for folder names (letters, numbers, underscores, dashes,
// spaces)
String cleanCategory = category.trim().replaceAll("[^a-zA-Z0-9-_ ]", "").trim();
if (cleanCategory.isEmpty()) {
cleanCategory = "General";
}
// Build the folder directory path: [documentUploadPathConfig]/[cleanCategory]/
File uploadDir = new File(documentUploadPathConfig, cleanCategory).getAbsoluteFile();
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
// Generate unique filename to avoid overwrites
String originalFilename = file.getOriginalFilename();
String nameWithoutExt = "";
String ext = "";
if (originalFilename != null && originalFilename.contains(".")) {
int dotIdx = originalFilename.lastIndexOf(".");
nameWithoutExt = originalFilename.substring(0, dotIdx);
ext = originalFilename.substring(dotIdx);
} else {
nameWithoutExt = originalFilename != null ? originalFilename : "file";
}
nameWithoutExt = nameWithoutExt.replaceAll("[^a-zA-Z0-9-_]", "_");
String savedFilename = nameWithoutExt + "_" + System.currentTimeMillis() + ext;
File destFile = new File(uploadDir, savedFilename).getAbsoluteFile();
file.transferTo(destFile);
// Save Web URL in database:
// "/gccservicedocuments/files/[category]/[savedFilename]"
String fileWebUrl = "/gccservicedocuments/files/" + cleanCategory + "/" + savedFilename;
boolean saved = documentService.saveDocument(
title.trim(),
cleanCategory,
fileType,
fileWebUrl,
originalFilename);
if (saved) {
if ("video".equalsIgnoreCase(fileType)) {
videoCompressionService.compressVideoAsync(destFile);
}
response.put("success", true);
response.put("message", "File uploaded and registered successfully.");
} else {
// Remove saved file on DB insert failure
destFile.delete();
response.put("success", false);
response.put("error", "Failed to record document details in the database.");
}
} catch (Exception e) {
response.put("success", false);
response.put("error", "File upload failed: " + e.getMessage());
}
return response;
}
@PostMapping("/api/delete")
@ResponseBody
public Map<String, Object> deleteFile(@RequestParam("id") Long id, HttpSession session) {
Map<String, Object> response = new HashMap<>();
// String usrName = (String) session.getAttribute("usrName");
// if (usrName == null) {
// response.put("success", false);
// response.put("error", "Unauthorized access! Please log in.");
// return response;
// }
boolean deleted = documentService.deleteDocument(id);
if (deleted) {
response.put("success", true);
response.put("message", "File record removed successfully.");
} else {
response.put("success", false);
response.put("error", "Failed to delete the file record.");
}
return response;
}
}

180
src/main/java/com/gcc_manpower/service/GccServiceDocumentService.java

@ -0,0 +1,180 @@
package com.gcc_manpower.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import javax.sql.DataSource;
import jakarta.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Service
public class GccServiceDocumentService {
private JdbcTemplate jdbcTemplate;
@Autowired
public void setDataSource(@Qualifier("mysqlServiceDocumentsDataSource") DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
private int getOrCreateCategory(String categoryName) {
// Try parsing as ID first
try {
int id = Integer.parseInt(categoryName.trim());
String checkIdSql = "SELECT id FROM category_list WHERE id = ? AND is_delete = 0";
List<Integer> ids = jdbcTemplate.queryForList(checkIdSql, Integer.class, id);
if (!ids.isEmpty()) {
return ids.get(0);
}
} catch (NumberFormatException e) {
// Not a number, fallback to name matching
}
String selectSql = "SELECT id FROM category_list WHERE category_name = ? AND is_delete = 0";
try {
List<Integer> ids = jdbcTemplate.queryForList(selectSql, Integer.class, categoryName.trim());
if (!ids.isEmpty()) {
return ids.get(0);
}
} catch (Exception e) {
System.err.println("Error checking category: " + e.getMessage());
}
// Insert new category
String insertSql = "INSERT INTO category_list (category_name, is_active, is_delete) VALUES (?, 1, 0)";
try {
jdbcTemplate.update(insertSql, categoryName.trim());
// Fetch the generated ID
List<Integer> ids = jdbcTemplate.queryForList(selectSql, Integer.class, categoryName.trim());
if (!ids.isEmpty()) {
return ids.get(0);
}
} catch (Exception e) {
System.err.println("Error creating category: " + e.getMessage());
}
return 0;
}
private int getOrCreateFileType(String fileTypeName) {
// Try parsing as ID first
try {
int id = Integer.parseInt(fileTypeName.trim());
String checkIdSql = "SELECT id FROM upload_file_types WHERE id = ? AND is_delete = 0";
List<Integer> ids = jdbcTemplate.queryForList(checkIdSql, Integer.class, id);
if (!ids.isEmpty()) {
return ids.get(0);
}
} catch (NumberFormatException e) {
// Not a number, fallback to name matching
}
String selectSql = "SELECT id FROM upload_file_types WHERE file_type_name = ? AND is_delete = 0";
try {
List<Integer> ids = jdbcTemplate.queryForList(selectSql, Integer.class, fileTypeName.trim());
if (!ids.isEmpty()) {
return ids.get(0);
}
} catch (Exception e) {
System.err.println("Error checking file type: " + e.getMessage());
}
// Insert new file type
String insertSql = "INSERT INTO upload_file_types (file_type_name, is_active, is_delete) VALUES (?, 1, 0)";
try {
jdbcTemplate.update(insertSql, fileTypeName.trim());
// Fetch the generated ID
List<Integer> ids = jdbcTemplate.queryForList(selectSql, Integer.class, fileTypeName.trim());
if (!ids.isEmpty()) {
return ids.get(0);
}
} catch (Exception e) {
System.err.println("Error creating file type: " + e.getMessage());
}
return 0;
}
public boolean saveDocument(String title, String category, String fileType, String filePath,
String originalFilename) {
int categoryId = getOrCreateCategory(category);
int fileTypeId = getOrCreateFileType(fileType);
String sql = "INSERT INTO documents_uploads (title, category, file_type, file_path, original_filename, is_active, is_delete) "
+
"VALUES (?, ?, ?, ?, ?, 1, 0)";
try {
int rows = jdbcTemplate.update(sql, title, String.valueOf(categoryId), String.valueOf(fileTypeId), filePath,
originalFilename);
return rows > 0;
} catch (Exception e) {
System.err.println("Error saving service document: " + e.getMessage());
return false;
}
}
public List<Map<String, Object>> searchDocuments(String category, String fromDate, String toDate) {
StringBuilder sql = new StringBuilder(
"SELECT d.id, d.title, c.category_name AS category, f.file_type_name AS file_type, d.file_path, d.original_filename, d.upload_date "
+
"FROM documents_uploads d " +
"LEFT JOIN category_list c ON d.category = c.id " +
"LEFT JOIN upload_file_types f ON d.file_type = f.id " +
"WHERE d.is_active = 1 AND d.is_delete = 0");
List<Object> params = new ArrayList<>();
if (category != null && !category.trim().isEmpty()) {
sql.append(" AND c.category_name = ?");
params.add(category.trim());
}
if (fromDate != null && !fromDate.trim().isEmpty()) {
sql.append(" AND DATE(d.upload_date) >= ?");
params.add(fromDate.trim());
}
if (toDate != null && !toDate.trim().isEmpty()) {
sql.append(" AND DATE(d.upload_date) <= ?");
params.add(toDate.trim());
}
sql.append(" ORDER BY d.category ");
try {
return jdbcTemplate.queryForList(sql.toString(), params.toArray());
} catch (Exception e) {
System.err.println("Error searching service documents: " + e.getMessage());
return new ArrayList<>();
}
}
public boolean deleteDocument(Long id) {
String sql = "UPDATE documents_uploads SET is_active = 0, is_delete = 1 WHERE id = ?";
try {
int rows = jdbcTemplate.update(sql, id);
return rows > 0;
} catch (Exception e) {
System.err.println("Error deleting service document ID " + id + ": " + e.getMessage());
return false;
}
}
public List<String> getAllCategories() {
String sql = "SELECT category_name FROM category_list WHERE is_active = 1 AND is_delete = 0 ORDER BY category_name";
try {
return jdbcTemplate.queryForList(sql, String.class);
} catch (Exception e) {
System.err.println("Error fetching document categories: " + e.getMessage());
return new ArrayList<>();
}
}
public List<Map<String, Object>> getAllFileTypes() {
String sql = "SELECT id, file_type_name FROM upload_file_types WHERE is_active = 1 AND is_delete = 0 ORDER BY file_type_name";
try {
return jdbcTemplate.queryForList(sql);
} catch (Exception e) {
System.err.println("Error fetching file types: " + e.getMessage());
return new ArrayList<>();
}
}
}

98
src/main/java/com/gcc_manpower/service/VideoCompressionService.java

@ -0,0 +1,98 @@
package com.gcc_manpower.service;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import ws.schild.jave.Encoder;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.encode.AudioAttributes;
import ws.schild.jave.encode.EncodingAttributes;
import ws.schild.jave.encode.VideoAttributes;
import ws.schild.jave.info.AudioInfo;
import java.io.File;
import java.util.logging.Level;
import java.util.logging.Logger;
@Service
public class VideoCompressionService {
private static final Logger LOGGER = Logger.getLogger(VideoCompressionService.class.getName());
@Async
public void compressVideoAsync(File originalFile) {
if (originalFile == null || !originalFile.exists() || !originalFile.isFile()) {
LOGGER.warning("Invalid file provided for compression.");
return;
}
String originalPath = originalFile.getAbsolutePath();
long originalSize = originalFile.length();
LOGGER.info("Starting background video compression for file: " + originalPath + " (Size: " + (originalSize / (1024 * 1024)) + " MB)");
// Create a temporary file in the same directory for the compressed output
File tempFile = new File(originalFile.getParentFile(), "temp_" + System.currentTimeMillis() + "_" + originalFile.getName());
try {
// Read input file details
MultimediaObject mediaObject = new MultimediaObject(originalFile);
AudioInfo audioInfo = mediaObject.getInfo().getAudio();
// Set up video encoding attributes
VideoAttributes video = new VideoAttributes();
video.setCodec("h264"); // Standard widely supported web codec
video.setBitRate(1500000); // Target 1.5 Mbps for compression
video.setFrameRate(24);
EncodingAttributes attrs = new EncodingAttributes();
attrs.setOutputFormat("mp4");
attrs.setVideoAttributes(video);
// Set up audio encoding attributes if audio stream exists
if (audioInfo != null) {
AudioAttributes audio = new AudioAttributes();
audio.setCodec("aac");
audio.setBitRate(128000); // Target 128 kbps audio
audio.setChannels(2);
audio.setSamplingRate(44100);
attrs.setAudioAttributes(audio);
}
// Encode/Compress
Encoder encoder = new Encoder();
encoder.encode(mediaObject, tempFile, attrs);
// Replace original file with compressed file if successful
if (tempFile.exists() && tempFile.length() > 0) {
long compressedSize = tempFile.length();
// Delete original file
boolean deletedOriginal = originalFile.delete();
if (!deletedOriginal) {
LOGGER.warning("Failed to delete the original uncompressed file. Trying to force replace.");
}
// Rename temp file to original file name
boolean renamed = tempFile.renameTo(originalFile);
if (renamed) {
long sizeReduction = originalSize - compressedSize;
LOGGER.info("Compression complete! Saved to: " + originalPath);
LOGGER.info(String.format("File size reduced by %.2f%% (from %d MB to %d MB)",
((double) sizeReduction / originalSize) * 100,
originalSize / (1024 * 1024),
compressedSize / (1024 * 1024)));
} else {
LOGGER.severe("Failed to rename temp compressed file to original filename: " + originalPath);
}
} else {
LOGGER.severe("Compression failed: Temp file was not created or is empty.");
}
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Exception occurred during video compression for file: " + originalPath, e);
// Cleanup temp file in case of failure
if (tempFile.exists()) {
tempFile.delete();
}
}
}
}

4
src/main/resources/application-local.properties

@ -35,3 +35,7 @@ server.servlet.context-path=/
# Disable Thymeleaf caching for hot-swapping templates # Disable Thymeleaf caching for hot-swapping templates
spring.thymeleaf.cache=false spring.thymeleaf.cache=false
# Multipart File Upload Limits (supports large video files)
spring.servlet.multipart.max-file-size=300MB
spring.servlet.multipart.max-request-size=300MB

4
src/main/resources/application-prod.properties

@ -42,3 +42,7 @@ server.servlet.context-path=/gcc_manpower
# Disable Thymeleaf caching for hot-swapping templates # Disable Thymeleaf caching for hot-swapping templates
spring.thymeleaf.cache=false spring.thymeleaf.cache=false
# Multipart File Upload Limits (supports large video files)
spring.servlet.multipart.max-file-size=300MB
spring.servlet.multipart.max-request-size=300MB

22
src/main/resources/templates/fragments/documentsheader.html

@ -0,0 +1,22 @@
<th:block th:fragment="documentsheader">
<!-- Awesome Font -->
<link rel="stylesheet" th:href="@{/assets/font-awesome-6.5.2/css/all.min.css}" type="text/css">
<!-- Bootstrap CSS -->
<link rel="stylesheet" th:href="@{/assets/bootstrap-5.3.2/css/bootstrap.min.css}" type="text/css">
<!-- CSS for Header -->
<link rel="stylesheet" th:href="@{/assets/css/header-styles.css}" type="text/css">
<div class="header">
<div class="logo-left">
<img th:src="@{/assets/header-images/gcc1.png}" alt="Greater Chennai Corporation Logo">
<div class="logo-text">
<h2>GREATER</h2>
<h2>CHENNAI</h2>
<h2>CORPORATION</h2>
<p>பெருநகர சென்னை மாநகராட்சி</p>
</div>
</div>
<div class="title">
<h2>GCC Training Material Services Details</h2>
</div>
</div>
</th:block>

637
src/main/resources/templates/gccservicedocuments/upload.html

@ -0,0 +1,637 @@
<!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>GCC Documents Upload Portal</title>
<!-- CSRF Tokens -->
<meta th:if="${_csrf != null}" name="_csrf" th:content="${_csrf.token}" />
<meta th:if="${_csrf != null}" name="_csrf_header" th:content="${_csrf.headerName}" />
<!-- Header Fragment -->
<th:block th:replace="~{fragments/documentsheader :: documentsheader}"></th:block>
<!-- Google Fonts -->
<link
href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=Poppins:wght@300;400;500;600;700&display=swap"
rel="stylesheet">
<!-- Styles -->
<style>
body {
background-color: #f4f7f6;
font-family: 'Poppins', sans-serif;
color: #333;
}
.main-container {
max-width: 800px;
margin: 40px auto;
padding: 0 20px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
}
.page-title {
font-family: 'Outfit', sans-serif;
font-size: 2rem;
font-weight: 700;
color: #0f5c78;
margin: 0;
display: flex;
align-items: center;
gap: 12px;
}
.btn-back {
background-color: #ffffff;
color: #475569;
font-weight: 600;
border: 1.5px solid #cbd5e1;
padding: 10px 20px;
border-radius: 30px;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-back:hover {
background-color: #f1f5f9;
color: #0f5c78;
border-color: #0f5c78;
}
.upload-card {
background: #ffffff;
border-radius: 20px;
border: 1px solid rgba(15, 92, 120, 0.08);
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.04);
padding: 40px;
}
.form-label {
font-size: 0.9rem;
font-weight: 600;
color: #475569;
margin-bottom: 8px;
}
.form-control-custom {
height: 48px;
border-radius: 10px;
border: 1.5px solid #e2e8f0;
padding: 10px 16px;
font-size: 0.95rem;
font-weight: 500;
color: #334155;
background-color: #f8fafc;
transition: all 0.3s ease;
width: 100%;
}
.form-control-custom:focus {
outline: none;
border-color: #0f5c78;
background-color: #ffffff;
box-shadow: 0 0 0 3px rgba(15, 92, 120, 0.15);
}
.form-group {
margin-bottom: 24px;
}
.category-select-group {
display: flex;
flex-direction: column;
gap: 12px;
}
/* Drag & Drop Zone */
.dropzone-container {
border: 2px dashed #cbd5e1;
border-radius: 16px;
padding: 40px 20px;
text-align: center;
background-color: #f8fafc;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
margin-bottom: 24px;
}
.dropzone-container:hover,
.dropzone-container.dragover {
border-color: #0f5c78;
background-color: rgba(15, 92, 120, 0.03);
}
.dropzone-icon {
font-size: 3rem;
color: #94a3b8;
margin-bottom: 16px;
transition: color 0.3s ease;
}
.dropzone-container:hover .dropzone-icon {
color: #0f5c78;
}
.dropzone-text {
font-size: 1rem;
color: #475569;
font-weight: 500;
margin-bottom: 8px;
}
.dropzone-hint {
font-size: 0.8rem;
color: #94a3b8;
}
.file-input-hidden {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
.selected-file-banner {
background-color: #f0fdf4;
border: 1px solid #bbf7d0;
border-radius: 10px;
padding: 12px 18px;
margin-bottom: 24px;
display: none;
align-items: center;
justify-content: space-between;
}
.selected-file-details {
display: flex;
align-items: center;
gap: 12px;
color: #166534;
font-size: 0.9rem;
font-weight: 500;
}
.btn-remove-file {
background: none;
border: none;
color: #b91c1c;
font-size: 1.1rem;
cursor: pointer;
}
/* Progress Bar */
.progress-container {
display: none;
margin-bottom: 24px;
}
.progress-bar-wrapper {
background-color: #e2e8f0;
height: 8px;
border-radius: 10px;
overflow: hidden;
}
.progress-bar-fill {
background: linear-gradient(90deg, #0f5c78, #1781a6);
height: 100%;
width: 0%;
transition: width 0.1s ease;
}
.progress-text {
display: flex;
justify-content: space-between;
font-size: 0.85rem;
font-weight: 600;
color: #64748b;
margin-bottom: 6px;
}
.btn-submit-upload {
width: 100%;
height: 50px;
border-radius: 12px;
background: linear-gradient(135deg, #0f5c78, #1781a6);
color: #ffffff;
font-weight: 600;
font-size: 1rem;
border: none;
box-shadow: 0 4px 15px rgba(15, 92, 120, 0.2);
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.btn-submit-upload:hover {
background: linear-gradient(135deg, #1781a6, #0f5c78);
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(15, 92, 120, 0.3);
}
.btn-submit-upload:disabled {
background: #cbd5e1;
color: #94a3b8;
box-shadow: none;
cursor: not-allowed;
transform: none;
}
</style>
</head>
<body>
<!-- Navigation Fragment -->
<!-- <th:block th:replace="~{fragments/navbar :: navbar(curPage=${curPage}, isAdmin=${isAdmin})}"></th:block> -->
<div class="main-container">
<!-- Header -->
<div class="page-header">
<h1 class="page-title">
<i class="fa-solid fa-cloud-arrow-up text-primary"></i>
Upload Documents
</h1>
<a th:href="@{/gccservicedocuments/view}" class="btn-back">
<i class="fa-solid fa-arrow-left"></i>
Back to View Report
</a>
</div>
<!-- Upload Card -->
<div class="upload-card">
<form id="upload-form" onsubmit="handleUpload(event)">
<!-- Title -->
<div class="form-group">
<label class="form-label" for="title-input">Document / Video Title *</label>
<input type="text" id="title-input" required class="form-control-custom"
placeholder="Enter a descriptive title">
</div>
<!-- File Type & Category Row -->
<div class="row">
<div class="col-md-6 form-group">
<label class="form-label" for="type-select">File Type *</label>
<select id="type-select" required class="form-control-custom" onchange="adjustFileAcceptance()">
<option value="">- Select File Type -</option>
</select>
</div>
<div class="col-md-6 form-group category-select-group">
<label class="form-label" for="category-select">Category *</label>
<select id="category-select" required class="form-control-custom"
onchange="toggleCustomCategoryInput()">
<option value="">- Select Category -</option>
<option value="__NEW__">+ Add Custom Category...</option>
</select>
<!-- Dynamic Custom Category Write-In -->
<input type="text" id="custom-category-input" class="form-control-custom" style="display: none;"
placeholder="Enter custom category name">
</div>
</div>
<!-- Dropzone Area -->
<div class="dropzone-container" id="dropzone">
<i class="fa-solid fa-cloud-arrow-up dropzone-icon"></i>
<p class="dropzone-text" id="dropzone-msg">Drag and drop file here or click to browse</p>
<p class="dropzone-hint" id="dropzone-hint">Allowed documents: PDF, Word, Excel, TXT</p>
<input type="file" id="file-chooser" class="file-input-hidden" onchange="onFileSelected()">
</div>
<!-- Selected File Banner -->
<div class="selected-file-banner" id="file-banner">
<div class="selected-file-details">
<i class="fa-solid fa-circle-check"></i>
<span id="file-name-display">filename.pdf</span>
<span id="file-size-display" style="opacity: 0.7; font-size: 0.8rem;">(0 MB)</span>
</div>
<button type="button" class="btn-remove-file" onclick="clearSelectedFile()" title="Remove file">
<i class="fa-solid fa-circle-xmark"></i>
</button>
</div>
<!-- Progress Bar -->
<div class="progress-container" id="progress-container">
<div class="progress-text">
<span id="upload-status">Uploading file...</span>
<span id="progress-percent">0%</span>
</div>
<div class="progress-bar-wrapper">
<div class="progress-bar-fill" id="progress-fill"></div>
</div>
</div>
<!-- Submit Button -->
<button type="submit" class="btn-submit-upload" id="submit-btn" disabled>
<i class="fa-solid fa-upload"></i> Start Upload
</button>
</form>
</div>
</div>
<!-- Footer Fragment -->
<th:block th:replace="~{fragments/footer :: footer}"></th:block>
<script th:inline="javascript">
const contextPath = /*[[@{/}]]*/ '';
let selectedFile = null;
document.addEventListener('DOMContentLoaded', () => {
loadExistingCategories();
loadFileTypes();
setupDragAndDrop();
});
function loadExistingCategories() {
fetch(contextPath + 'gccservicedocuments/api/categories')
.then(res => res.json())
.then(categories => {
const select = document.getElementById('category-select');
// Add items before the last child option (+ Add Custom Category...)
categories.forEach(cat => {
const opt = document.createElement('option');
opt.value = cat;
opt.textContent = cat;
select.insertBefore(opt, select.lastElementChild);
});
})
.catch(err => console.error("Error loading categories:", err));
}
function loadFileTypes() {
fetch(contextPath + 'gccservicedocuments/api/file-types')
.then(res => res.json())
.then(fileTypes => {
const select = document.getElementById('type-select');
select.innerHTML = '<option value="">- Select File Type -</option>';
fileTypes.forEach(ft => {
const opt = document.createElement('option');
opt.value = ft.id;
opt.textContent = ft.file_type_name;
opt.setAttribute('data-name', ft.file_type_name.toLowerCase());
select.appendChild(opt);
});
adjustFileAcceptance();
})
.catch(err => console.error("Error loading file types:", err));
}
function adjustFileAcceptance() {
const typeSelect = document.getElementById('type-select');
const fileChooser = document.getElementById('file-chooser');
const hint = document.getElementById('dropzone-hint');
const selectedOption = typeSelect.options[typeSelect.selectedIndex];
const fileTypeName = selectedOption ? selectedOption.getAttribute('data-name') : '';
if (fileTypeName === 'video') {
fileChooser.accept = 'video/mp4,video/webm,video/ogg,video/quicktime';
hint.textContent = 'Allowed videos: MP4, WebM, OGG, MOV';
} else {
fileChooser.accept = '.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt';
hint.textContent = 'Allowed documents: PDF, Word, Excel, PowerPoint, TXT';
}
// If a file was already selected, clear it to prevent invalid file types from submitting
if (selectedFile) {
clearSelectedFile();
}
}
function toggleCustomCategoryInput() {
const select = document.getElementById('category-select');
const customInput = document.getElementById('custom-category-input');
if (select.value === '__NEW__') {
customInput.style.display = 'block';
customInput.required = true;
customInput.focus();
} else {
customInput.style.display = 'none';
customInput.required = false;
customInput.value = '';
}
}
function setupDragAndDrop() {
const dropzone = document.getElementById('dropzone');
['dragenter', 'dragover'].forEach(eventName => {
dropzone.addEventListener(eventName, (e) => {
e.preventDefault();
dropzone.classList.add('dragover');
}, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropzone.addEventListener(eventName, (e) => {
e.preventDefault();
dropzone.classList.remove('dragover');
}, false);
});
dropzone.addEventListener('drop', (e) => {
const dt = e.dataTransfer;
const files = dt.files;
if (files.length > 0) {
document.getElementById('file-chooser').files = files;
onFileSelected();
}
});
}
function validateFileExtension(file, fileTypeName) {
if (!fileTypeName) return false;
const fileName = file.name.toLowerCase();
const dotIdx = fileName.lastIndexOf('.');
if (dotIdx === -1) return false;
const extension = fileName.substring(dotIdx);
if (fileTypeName === 'video') {
const allowed = ['.mp4', '.webm', '.ogg', '.mov', '.avi', '.mkv'];
return allowed.includes(extension);
} else if (fileTypeName === 'pdf') {
return extension === '.pdf';
} else if (fileTypeName === 'word') {
return extension === '.doc' || extension === '.docx';
} else if (fileTypeName === 'excel') {
return extension === '.xls' || extension === '.xlsx';
} else if (fileTypeName === 'presentation') {
return extension === '.ppt' || extension === '.pptx';
}
return true;
}
function onFileSelected() {
const fileInput = document.getElementById('file-chooser');
if (fileInput.files.length > 0) {
const file = fileInput.files[0];
const typeSelect = document.getElementById('type-select');
const selectedOption = typeSelect.options[typeSelect.selectedIndex];
const fileTypeName = selectedOption ? selectedOption.getAttribute('data-name') : '';
if (!typeSelect.value) {
if (typeof Swal !== 'undefined') {
Swal.fire({
title: 'Select File Type',
text: 'Please select a File Type first before choosing a file.',
icon: 'warning',
confirmButtonColor: '#0f5c78'
});
} else {
alert("Please select a File Type first before choosing a file.");
}
fileInput.value = '';
return;
}
if (!validateFileExtension(file, fileTypeName)) {
const typeLabel = selectedOption.textContent;
if (typeof Swal !== 'undefined') {
Swal.fire({
title: 'Invalid File Format',
text: `The selected file does not match the chosen File Type (${typeLabel}). Please choose a valid file.`,
icon: 'error',
confirmButtonColor: '#0f5c78'
});
} else {
alert(`The selected file does not match the chosen File Type (${typeLabel}). Please choose a valid file.`);
}
fileInput.value = '';
return;
}
selectedFile = file;
// Show banner
document.getElementById('file-name-display').textContent = selectedFile.name;
document.getElementById('file-size-display').textContent = '(' + (selectedFile.size / (1024 * 1024)).toFixed(2) + ' MB)';
document.getElementById('dropzone').style.display = 'none';
document.getElementById('file-banner').style.display = 'flex';
document.getElementById('submit-btn').disabled = false;
}
}
function clearSelectedFile() {
document.getElementById('file-chooser').value = '';
selectedFile = null;
document.getElementById('dropzone').style.display = 'block';
document.getElementById('file-banner').style.display = 'none';
document.getElementById('submit-btn').disabled = true;
}
function handleUpload(event) {
event.preventDefault();
if (!selectedFile) {
alert("Please select a file.");
return;
}
const title = document.getElementById('title-input').value.trim();
const type = document.getElementById('type-select').value;
const categorySelect = document.getElementById('category-select').value;
const customCategory = document.getElementById('custom-category-input').value.trim();
let category = categorySelect;
if (categorySelect === '__NEW__') {
if (!customCategory) {
alert("Please specify a custom category name.");
return;
}
category = customCategory;
}
if (!category) {
alert("Please select or type a category.");
return;
}
// CSRF security token
const csrfMeta = document.querySelector('meta[name="_csrf"]');
const csrfHeaderMeta = document.querySelector('meta[name="_csrf_header"]');
const formData = new FormData();
formData.append('title', title);
formData.append('fileType', type);
formData.append('category', category);
formData.append('file', selectedFile);
const xhr = new XMLHttpRequest();
xhr.open('POST', contextPath + 'gccservicedocuments/api/upload', true);
if (csrfMeta && csrfHeaderMeta) {
xhr.setRequestHeader(csrfHeaderMeta.content, csrfMeta.content);
}
// Show progress elements
const progressContainer = document.getElementById('progress-container');
const progressFill = document.getElementById('progress-fill');
const progressPercent = document.getElementById('progress-percent');
const submitBtn = document.getElementById('submit-btn');
progressContainer.style.display = 'block';
submitBtn.disabled = true;
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const percent = Math.round((e.loaded / e.total) * 100);
progressFill.style.width = percent + '%';
progressPercent.textContent = percent + '%';
}
});
xhr.onload = function () {
if (xhr.status === 200) {
const response = JSON.parse(xhr.responseText);
if (response.success) {
alert(response.message || "Upload completed successfully!");
// Redirect back to main page
window.location.href = contextPath + 'gccservicedocuments/view';
} else {
alert("Upload failed: " + response.error);
progressContainer.style.display = 'none';
submitBtn.disabled = false;
}
} else {
alert("An error occurred during file upload. Code: " + xhr.status);
progressContainer.style.display = 'none';
submitBtn.disabled = false;
}
};
xhr.onerror = function () {
alert("Connection error occurred while uploading file.");
progressContainer.style.display = 'none';
submitBtn.disabled = false;
};
xhr.send(formData);
}
</script>
</body>
</html>

1115
src/main/resources/templates/gccservicedocuments/view.html
File diff suppressed because it is too large
View File

Loading…
Cancel
Save