diff --git a/src/main/java/com/gcc_it_cell_services/springboot/controller/UrlDataController.java b/src/main/java/com/gcc_it_cell_services/springboot/controller/UrlDataController.java index fee7ef0..a7a6b10 100644 --- a/src/main/java/com/gcc_it_cell_services/springboot/controller/UrlDataController.java +++ b/src/main/java/com/gcc_it_cell_services/springboot/controller/UrlDataController.java @@ -36,11 +36,22 @@ public class UrlDataController { // Add role information to the model for conditional navbar rendering urlCatName = urlCatName.replace("-", " "); List urls = urlDataSer.getUrlsByUrlCatName(urlCatName); - urlCatName = urls.get(0).getUrlCatResDTO().getUrlCatName(); + + String resolvedCatName = urls.isEmpty() ? urlCatName : urls.get(0).getUrlCatResDTO().getUrlCatName(); + + java.util.Map> groupedUrls = urls.stream() + .collect(java.util.stream.Collectors.groupingBy( + dto -> (dto.getUrlSubCatResDTO() != null && dto.getUrlSubCatResDTO().getUrlSubCatName() != null && !dto.getUrlSubCatResDTO().getUrlSubCatName().trim().isEmpty()) + ? dto.getUrlSubCatResDTO().getUrlSubCatName().trim() : "-", + java.util.LinkedHashMap::new, + java.util.stream.Collectors.toList() + )); + boolean isAdmin = auth != null && auth.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN")); model.addAttribute("isAdmin", isAdmin); - model.addAttribute("urlCatName", urlCatName); + model.addAttribute("urlCatName", resolvedCatName); + model.addAttribute("groupedUrls", groupedUrls); model.addAttribute("urls", urls); return "urls/get-urls-by-url-category"; // Thymeleaf view name } diff --git a/src/main/java/com/gcc_it_cell_services/springboot/mapper/UrlDataMapper.java b/src/main/java/com/gcc_it_cell_services/springboot/mapper/UrlDataMapper.java index a7df1c8..bd93b8c 100644 --- a/src/main/java/com/gcc_it_cell_services/springboot/mapper/UrlDataMapper.java +++ b/src/main/java/com/gcc_it_cell_services/springboot/mapper/UrlDataMapper.java @@ -13,6 +13,9 @@ public class UrlDataMapper { @Autowired private UrlCategoryMapper urlCatMap; + @Autowired + private UrlSubCategoryMapper urlSubCatMap; + @Autowired private StatusMapper statMap; @@ -22,6 +25,7 @@ public class UrlDataMapper { urlData.setUrlDataName(urlDataReqDTO.getUrlDataName()); urlData.setUrlDataUsrName(urlDataReqDTO.getUrlDataUsrName()); urlData.setUrlDataUsrPass(urlDataReqDTO.getUrlDataUsrPass()); + urlData.setUrlSubCat(urlSubCatMap.toUrlSubCatEnt(urlDataReqDTO.getUrlSubCatResDTO())); urlData.setUrlCat(urlCatMap.toUrlCatEnt(urlDataReqDTO.getUrlCatResDTO())); urlData.setStatId(statMap.toStatEnt(urlDataReqDTO.getStatResDTO())); return urlData; @@ -29,8 +33,8 @@ public class UrlDataMapper { public UrlDataResponseDTO toUrlDataResDTO(UrlData urlData) { return new UrlDataResponseDTO(urlData.getUrlDataId(), urlData.getUrlDataName(), urlData.getUrlDataLink(), - urlData.getUrlDataUsrPass(), urlData.getUrlDataUsrName(), urlCatMap.toUrlCatResDTO(urlData.getUrlCat()), - statMap.toStatResDTO(urlData.getStatId())); + urlData.getUrlDataUsrPass(), urlData.getUrlDataUsrName(), urlSubCatMap.toUrlSubCatResDTO(urlData.getUrlSubCat()), + urlCatMap.toUrlCatResDTO(urlData.getUrlCat()), statMap.toStatResDTO(urlData.getStatId())); } public UrlData toUrlDatEnt(UrlDataResponseDTO urlDataResDTO) { @@ -41,6 +45,7 @@ public class UrlDataMapper { urlData.setUrlDataName(urlDataResDTO.getUrlDataName()); urlData.setUrlDataUsrName(urlDataResDTO.getUrlDataUsrName()); urlData.setUrlDataUsrPass(urlDataResDTO.getUrlDataUsrPass()); + urlData.setUrlSubCat(urlSubCatMap.toUrlSubCatEnt(urlDataResDTO.getUrlSubCatResDTO())); urlData.setUrlCat(urlCatMap.toUrlCatEnt(urlDataResDTO.getUrlCatResDTO())); urlData.setStatId(statMap.toStatEnt(urlDataResDTO.getStatResDTO())); return urlData; diff --git a/src/main/java/com/gcc_it_cell_services/springboot/mapper/UrlSubCategoryMapper.java b/src/main/java/com/gcc_it_cell_services/springboot/mapper/UrlSubCategoryMapper.java new file mode 100644 index 0000000..a184a23 --- /dev/null +++ b/src/main/java/com/gcc_it_cell_services/springboot/mapper/UrlSubCategoryMapper.java @@ -0,0 +1,32 @@ +package com.gcc_it_cell_services.springboot.mapper; + +import org.springframework.stereotype.Component; + +import com.gcc_it_cell_services.springboot.model.UrlSubCategory; +import com.gcc_it_cell_services.springboot.request_dto.UrlSubCategoryRequestDTO; +import com.gcc_it_cell_services.springboot.response_dto.UrlSubCategoryResponseDTO; + +@Component +public class UrlSubCategoryMapper { + + public UrlSubCategoryResponseDTO toUrlSubCatResDTO(UrlSubCategory urlSubCat) { + if (urlSubCat == null) return null; + return new UrlSubCategoryResponseDTO(urlSubCat.getUrlSubCatId(), urlSubCat.getUrlSubCatName()); + } + + public UrlSubCategory toUrlSubCatEnt(UrlSubCategoryRequestDTO urlSubCatReqDTO) { + if (urlSubCatReqDTO == null) return null; + UrlSubCategory urlSubCat = new UrlSubCategory(); + urlSubCat.setUrlSubCatName(urlSubCatReqDTO.getUrlSubCatName()); + return urlSubCat; + } + + public UrlSubCategory toUrlSubCatEnt(UrlSubCategoryResponseDTO urlSubCatResDTO) { + if (urlSubCatResDTO == null) return null; + UrlSubCategory urlSubCat = new UrlSubCategory(); + urlSubCat.setUrlSubCatId(urlSubCatResDTO.getUrlSubCatId()); + urlSubCat.setUrlSubCatName(urlSubCatResDTO.getUrlSubCatName()); + return urlSubCat; + } + +} diff --git a/src/main/java/com/gcc_it_cell_services/springboot/model/UrlData.java b/src/main/java/com/gcc_it_cell_services/springboot/model/UrlData.java index 29e115d..6c29e68 100644 --- a/src/main/java/com/gcc_it_cell_services/springboot/model/UrlData.java +++ b/src/main/java/com/gcc_it_cell_services/springboot/model/UrlData.java @@ -81,6 +81,11 @@ public class UrlData { @NotBlank private String urlDataUsrName; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "url_sub_cat_id", referencedColumnName = "url_sub_cat_id") + @JsonBackReference + private UrlSubCategory urlSubCat; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "url_cat_id", referencedColumnName = "url_cat_id", nullable = false) @JsonBackReference diff --git a/src/main/java/com/gcc_it_cell_services/springboot/model/UrlSubCategory.java b/src/main/java/com/gcc_it_cell_services/springboot/model/UrlSubCategory.java new file mode 100644 index 0000000..5ba3b40 --- /dev/null +++ b/src/main/java/com/gcc_it_cell_services/springboot/model/UrlSubCategory.java @@ -0,0 +1,112 @@ +package com.gcc_it_cell_services.springboot.model; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import java.util.List; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.PrePersist; +import javax.persistence.PreUpdate; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.validation.constraints.NotBlank; + +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedBy; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonManagedReference; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Entity +@Table(name = "tbl_url_sub_cat_det") +@Getter +@Setter +@ToString(includeFieldNames = true, doNotUseGetters = true, exclude = { "urlDatas", "createdBy", "updatedBy", "urlCat" }) +@AllArgsConstructor +@NoArgsConstructor +@EntityListeners(AuditingEntityListener.class) +public class UrlSubCategory { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "url_sub_cat_id", nullable = false, updatable = false) + private Long urlSubCatId; + + @Column(name = "url_sub_cat_name", nullable = false) + @NotBlank + private String urlSubCatName; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "url_cat_id", referencedColumnName = "url_cat_id", nullable = false) + @JsonBackReference + private UrlCategory urlCat; + + @OneToMany(mappedBy = "urlSubCat", orphanRemoval = true, cascade = CascadeType.ALL) + @JsonManagedReference + private List urlDatas; + + @CreatedBy + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "created_by_usr_id", referencedColumnName = "usr_id", updatable = false, nullable = false) + private AppUser createdBy; + + @LastModifiedBy + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "updated_by_usr_id", referencedColumnName = "usr_id", nullable = false) + private AppUser updatedBy; + + // User timestamp + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "usr_created_timestamp", nullable = false, updatable = false, columnDefinition = "TIMESTAMP(0)") + private Date usrCreatedTimestamp; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "usr_updated_timestamp", nullable = false, columnDefinition = "TIMESTAMP(0)") + private Date usrUpdatedTimestamp; + + // DB-managed timestamps + @CreatedDate + @Temporal(TemporalType.TIMESTAMP) + @Column(updatable = false, nullable = false, name = "db_created_timestamp", columnDefinition = "TIMESTAMP(0)") + private Date dbCreatedTimestamp; + + @LastModifiedDate + @Temporal(TemporalType.TIMESTAMP) + @Column(nullable = false, name = "db_updated_timestamp", columnDefinition = "TIMESTAMP(0)") + private Date dbUpdatedTimestamp; + + @PrePersist + public void onCreate() { + Date now = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS)); + this.usrCreatedTimestamp = now; + this.usrUpdatedTimestamp = now; + } + + @PreUpdate + public void onUpdate() { + Date now = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS)); + this.usrUpdatedTimestamp = now; + } + +} diff --git a/src/main/java/com/gcc_it_cell_services/springboot/repository/UrlDataRepository.java b/src/main/java/com/gcc_it_cell_services/springboot/repository/UrlDataRepository.java index bcae735..96caf30 100644 --- a/src/main/java/com/gcc_it_cell_services/springboot/repository/UrlDataRepository.java +++ b/src/main/java/com/gcc_it_cell_services/springboot/repository/UrlDataRepository.java @@ -3,9 +3,11 @@ package com.gcc_it_cell_services.springboot.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; import com.gcc_it_cell_services.springboot.model.UrlData; @@ -25,4 +27,9 @@ public interface UrlDataRepository extends JpaRepository { @Query("SELECT COUNT(u) FROM UrlData u WHERE u.urlCat.urlCatId = :urlCatId ORDER BY LOWER(u.urlDataName)") Integer findUrlCountByUrlCatId(@Param("urlCatId") Long urlCatId); + @Modifying + @Transactional + @Query(nativeQuery = true, value = "UPDATE tbl_url_data_det SET url_sub_cat_id = ?1 WHERE url_cat_id = ?2 AND (url_sub_cat_id IS NULL OR url_sub_cat_id = 0)") + void updateNullOrZeroSubCatForCategory(Long defaultSubCatId, Long urlCatId); + } diff --git a/src/main/java/com/gcc_it_cell_services/springboot/repository/UrlSubCategoryRepository.java b/src/main/java/com/gcc_it_cell_services/springboot/repository/UrlSubCategoryRepository.java new file mode 100644 index 0000000..dd51c52 --- /dev/null +++ b/src/main/java/com/gcc_it_cell_services/springboot/repository/UrlSubCategoryRepository.java @@ -0,0 +1,18 @@ +package com.gcc_it_cell_services.springboot.repository; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import com.gcc_it_cell_services.springboot.model.UrlSubCategory; + +@Repository +public interface UrlSubCategoryRepository extends JpaRepository { + + @Query(nativeQuery = true, value = "SELECT * FROM tbl_url_sub_cat_det WHERE url_sub_cat_name = ?1 AND url_cat_id = ?2") + java.util.List findUrlSubCatByNameAndCatId(@Param("urlSubCatName") String urlSubCatName, @Param("urlCatId") Long urlCatId); + +} diff --git a/src/main/java/com/gcc_it_cell_services/springboot/request_dto/AddUrlDataRequestDTO.java b/src/main/java/com/gcc_it_cell_services/springboot/request_dto/AddUrlDataRequestDTO.java index 4678a47..78dc2d9 100644 --- a/src/main/java/com/gcc_it_cell_services/springboot/request_dto/AddUrlDataRequestDTO.java +++ b/src/main/java/com/gcc_it_cell_services/springboot/request_dto/AddUrlDataRequestDTO.java @@ -18,5 +18,6 @@ public class AddUrlDataRequestDTO { private String urlDataUsrName; private String urlDataUsrPass; private String urlCatName; + private String urlSubCatName; } diff --git a/src/main/java/com/gcc_it_cell_services/springboot/request_dto/UpdateUrlDataRequestDTO.java b/src/main/java/com/gcc_it_cell_services/springboot/request_dto/UpdateUrlDataRequestDTO.java index 4fc423f..1a499d8 100644 --- a/src/main/java/com/gcc_it_cell_services/springboot/request_dto/UpdateUrlDataRequestDTO.java +++ b/src/main/java/com/gcc_it_cell_services/springboot/request_dto/UpdateUrlDataRequestDTO.java @@ -18,5 +18,6 @@ public class UpdateUrlDataRequestDTO { private String urlDataUsrPass; private String urlDataUsrName; private String urlCatName; + private String urlSubCatName; private Long statId; } diff --git a/src/main/java/com/gcc_it_cell_services/springboot/request_dto/UrlDataRequestDTO.java b/src/main/java/com/gcc_it_cell_services/springboot/request_dto/UrlDataRequestDTO.java index 220a7da..f28dc1e 100644 --- a/src/main/java/com/gcc_it_cell_services/springboot/request_dto/UrlDataRequestDTO.java +++ b/src/main/java/com/gcc_it_cell_services/springboot/request_dto/UrlDataRequestDTO.java @@ -2,6 +2,7 @@ package com.gcc_it_cell_services.springboot.request_dto; import com.gcc_it_cell_services.springboot.response_dto.StatusResponseDTO; import com.gcc_it_cell_services.springboot.response_dto.UrlCategoryResponseDTO; +import com.gcc_it_cell_services.springboot.response_dto.UrlSubCategoryResponseDTO; import lombok.AllArgsConstructor; import lombok.Getter; @@ -20,6 +21,7 @@ public class UrlDataRequestDTO { private String urlDataName; private String urlDataUsrPass; private String urlDataUsrName; + private UrlSubCategoryResponseDTO urlSubCatResDTO; private UrlCategoryResponseDTO urlCatResDTO; private StatusResponseDTO statResDTO; diff --git a/src/main/java/com/gcc_it_cell_services/springboot/request_dto/UrlSubCategoryRequestDTO.java b/src/main/java/com/gcc_it_cell_services/springboot/request_dto/UrlSubCategoryRequestDTO.java new file mode 100644 index 0000000..b741767 --- /dev/null +++ b/src/main/java/com/gcc_it_cell_services/springboot/request_dto/UrlSubCategoryRequestDTO.java @@ -0,0 +1,18 @@ +package com.gcc_it_cell_services.springboot.request_dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@ToString(doNotUseGetters = true, includeFieldNames = true) +public class UrlSubCategoryRequestDTO { + + private String urlSubCatName; + +} diff --git a/src/main/java/com/gcc_it_cell_services/springboot/response_dto/UrlDataResponseDTO.java b/src/main/java/com/gcc_it_cell_services/springboot/response_dto/UrlDataResponseDTO.java index ac92f3f..cbf1672 100644 --- a/src/main/java/com/gcc_it_cell_services/springboot/response_dto/UrlDataResponseDTO.java +++ b/src/main/java/com/gcc_it_cell_services/springboot/response_dto/UrlDataResponseDTO.java @@ -18,18 +18,20 @@ public class UrlDataResponseDTO { private String urlDataLink; private String urlDataUsrPass; private String urlDataUsrName; + private UrlSubCategoryResponseDTO urlSubCatResDTO; private UrlCategoryResponseDTO urlCatResDTO; private String urlDataNum; private StatusResponseDTO statResDTO; public UrlDataResponseDTO(Long urlDataId, String urlDataName, String urlDataLink, String urlDataUsrPass, - String urlDataUsrName, UrlCategoryResponseDTO urlCatResDTO, StatusResponseDTO statResDTO) { + String urlDataUsrName, UrlSubCategoryResponseDTO urlSubCatResDTO, UrlCategoryResponseDTO urlCatResDTO, StatusResponseDTO statResDTO) { super(); this.urlDataId = urlDataId; this.urlDataName = urlDataName; this.urlDataLink = urlDataLink; this.urlDataUsrPass = urlDataUsrPass; this.urlDataUsrName = urlDataUsrName; + this.urlSubCatResDTO = urlSubCatResDTO; this.urlCatResDTO = urlCatResDTO; this.statResDTO = statResDTO; } diff --git a/src/main/java/com/gcc_it_cell_services/springboot/response_dto/UrlSubCategoryResponseDTO.java b/src/main/java/com/gcc_it_cell_services/springboot/response_dto/UrlSubCategoryResponseDTO.java new file mode 100644 index 0000000..f3af6f4 --- /dev/null +++ b/src/main/java/com/gcc_it_cell_services/springboot/response_dto/UrlSubCategoryResponseDTO.java @@ -0,0 +1,19 @@ +package com.gcc_it_cell_services.springboot.response_dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString(doNotUseGetters = true, includeFieldNames = true) +@NoArgsConstructor +@AllArgsConstructor +public class UrlSubCategoryResponseDTO { + + private Long urlSubCatId; + private String urlSubCatName; + +} diff --git a/src/main/java/com/gcc_it_cell_services/springboot/service/UrlDataCacheService.java b/src/main/java/com/gcc_it_cell_services/springboot/service/UrlDataCacheService.java index d8f79f3..01e51c3 100644 --- a/src/main/java/com/gcc_it_cell_services/springboot/service/UrlDataCacheService.java +++ b/src/main/java/com/gcc_it_cell_services/springboot/service/UrlDataCacheService.java @@ -17,7 +17,11 @@ import com.gcc_it_cell_services.springboot.mapper.UrlDataMapper; import com.gcc_it_cell_services.springboot.repository.UrlCategoryRepository; import com.gcc_it_cell_services.springboot.repository.UrlDataRepository; import com.gcc_it_cell_services.springboot.response_dto.UrlCategoryResponseDTO; +import com.gcc_it_cell_services.springboot.response_dto.UrlSubCategoryResponseDTO; import com.gcc_it_cell_services.springboot.response_dto.UrlDataResponseDTO; +import com.gcc_it_cell_services.springboot.model.UrlSubCategory; +import com.gcc_it_cell_services.springboot.model.UrlCategory; +import com.gcc_it_cell_services.springboot.repository.UrlSubCategoryRepository; @Service public class UrlDataCacheService { @@ -25,6 +29,9 @@ public class UrlDataCacheService { @Autowired private UrlCategoryRepository urlCatRepo; + @Autowired + private UrlSubCategoryRepository urlSubCatRepo; + @Autowired private UrlDataRepository urlDataRepo; @@ -38,15 +45,37 @@ public class UrlDataCacheService { private final Map> globalUrlCatData = new ConcurrentHashMap>(); private final Map globalUrlLinks = new ConcurrentHashMap(); + @Transactional + public synchronized void initializeDefaultSubCategories() { + List categories = urlCatRepo.findAll(); + for (UrlCategory cat : categories) { + List defaultSubList = urlSubCatRepo.findUrlSubCatByNameAndCatId("-", cat.getUrlCatId()); + UrlSubCategory defaultSub; + if (defaultSubList.isEmpty()) { + defaultSub = new UrlSubCategory(); + defaultSub.setUrlSubCatName("-"); + defaultSub.setUrlCat(cat); + defaultSub.setCreatedBy(cat.getCreatedBy()); + defaultSub.setUpdatedBy(cat.getUpdatedBy()); + defaultSub = urlSubCatRepo.save(defaultSub); + } else { + defaultSub = defaultSubList.get(0); + } + urlDataRepo.updateNullOrZeroSubCatForCategory(defaultSub.getUrlSubCatId(), cat.getUrlCatId()); + } + } + // Called from CacheInitializer - @Transactional(readOnly = true) + @Transactional public synchronized void loadCache() { + initializeDefaultSubCategories(); buildCache(); } // Clear and rebuild cache - @Transactional(readOnly = true) + @Transactional public synchronized void refreshCache() { + initializeDefaultSubCategories(); buildCache(); } @@ -92,9 +121,13 @@ public class UrlDataCacheService { if (catName.equalsIgnoreCase("-")) { urlsByCat.getOrDefault("-", List.of()).forEach(urlDataResDTO -> { finalUrlDataResDTOs.add(new UrlDataResponseDTO(null, urlDataResDTO.getUrlDataName(), - urlDataResDTO.getUrlDataLink(), null, null, null, null)); + urlDataResDTO.getUrlDataLink(), null, null, urlDataResDTO.getUrlSubCatResDTO(), null, + null)); }); } else { + if (catName.equalsIgnoreCase("EMS")) { + continue; + } if (urlsByCat.containsKey(catName)) { // Check: This category has at least one ACTIVE url UrlDataResponseDTO urlDataResDTO = new UrlDataResponseDTO(); @@ -116,7 +149,7 @@ public class UrlDataCacheService { if (isNum1 && !isNum2) { return -1; } - if (isNum1 && isNum2) { + if (!isNum1 && isNum2) { return 1; } return urlDataName1.compareTo(urlDataName2); @@ -147,9 +180,27 @@ public class UrlDataCacheService { continue; } - // Sort alpahbetically + // Sort by subcategory first (with "-" at the end), then by URL name urlDataResDTOsByUrlCat = urlDataResDTOsByUrlCat.stream() - .sorted(Comparator.comparing(UrlDataResponseDTO::getUrlDataName, String.CASE_INSENSITIVE_ORDER)) + .sorted((a, b) -> { + String sub1 = (a.getUrlSubCatResDTO() != null + && a.getUrlSubCatResDTO().getUrlSubCatName() != null) + ? a.getUrlSubCatResDTO().getUrlSubCatName().trim() + : "-"; + String sub2 = (b.getUrlSubCatResDTO() != null + && b.getUrlSubCatResDTO().getUrlSubCatName() != null) + ? b.getUrlSubCatResDTO().getUrlSubCatName().trim() + : "-"; + + if (!sub1.equalsIgnoreCase(sub2)) { + if (sub1.equals("-")) + return 1; + if (sub2.equals("-")) + return -1; + return sub1.compareToIgnoreCase(sub2); + } + return a.getUrlDataName().compareToIgnoreCase(b.getUrlDataName()); + }) .toList(); // Prepare per-category numbering diff --git a/src/main/java/com/gcc_it_cell_services/springboot/service/UrlDataService.java b/src/main/java/com/gcc_it_cell_services/springboot/service/UrlDataService.java index 6910e44..0c649e2 100644 --- a/src/main/java/com/gcc_it_cell_services/springboot/service/UrlDataService.java +++ b/src/main/java/com/gcc_it_cell_services/springboot/service/UrlDataService.java @@ -14,10 +14,13 @@ import org.springframework.transaction.annotation.Transactional; import com.gcc_it_cell_services.springboot.mapper.StatusMapper; import com.gcc_it_cell_services.springboot.mapper.UrlCategoryMapper; +import com.gcc_it_cell_services.springboot.mapper.UrlSubCategoryMapper; import com.gcc_it_cell_services.springboot.mapper.UrlDataMapper; import com.gcc_it_cell_services.springboot.model.UrlData; +import com.gcc_it_cell_services.springboot.model.UrlSubCategory; import com.gcc_it_cell_services.springboot.repository.StatusRepository; import com.gcc_it_cell_services.springboot.repository.UrlCategoryRepository; +import com.gcc_it_cell_services.springboot.repository.UrlSubCategoryRepository; import com.gcc_it_cell_services.springboot.repository.UrlDataRepository; import com.gcc_it_cell_services.springboot.request_dto.AddUrlDataRequestDTO; import com.gcc_it_cell_services.springboot.request_dto.UpdateUrlDataRequestDTO; @@ -26,6 +29,7 @@ import com.gcc_it_cell_services.springboot.request_dto.UrlDataRequestDTO; import com.gcc_it_cell_services.springboot.response_dto.ServiceResponseDTO; import com.gcc_it_cell_services.springboot.response_dto.StatusResponseDTO; import com.gcc_it_cell_services.springboot.response_dto.UrlCategoryResponseDTO; +import com.gcc_it_cell_services.springboot.response_dto.UrlSubCategoryResponseDTO; import com.gcc_it_cell_services.springboot.response_dto.UrlDataResponseDTO; @Service @@ -40,6 +44,12 @@ public class UrlDataService { @Autowired private UrlCategoryRepository urlCatRepo; + @Autowired + private UrlSubCategoryRepository urlSubCatRepo; + + @Autowired + private UrlSubCategoryMapper urlSubCatMap; + @Autowired private UrlDataMapper urlDataMap; @@ -59,6 +69,54 @@ public class UrlDataService { return statRepo.findAll().stream().map(statMap::toStatResDTO).toList(); } + private int compareUrlDataNum(UrlDataResponseDTO a, UrlDataResponseDTO b) { + String num1 = a.getUrlDataNum(); + String num2 = b.getUrlDataNum(); + + if (num1 == null && num2 == null) { + return a.getUrlDataName().compareToIgnoreCase(b.getUrlDataName()); + } + if (num1 == null) + return 1; + if (num2 == null) + return -1; + + // Extract numeric + alpha suffix + boolean match1 = num1.matches("(\\d+)([A-Za-z]*)"); + boolean match2 = num2.matches("(\\d+)([A-Za-z]*)"); + + if (match1 && match2) { + String[] m1 = num1.replaceAll("(\\d+)([A-Za-z]*)", "$1,$2").split(","); + String[] m2 = num2.replaceAll("(\\d+)([A-Za-z]*)", "$1,$2").split(","); + + int n1 = Integer.parseInt(m1[0]); + int n2 = Integer.parseInt(m2[0]); + + if (n1 != n2) { + return Integer.compare(n1, n2); + } + + String s1 = m1.length > 1 ? m1[1] : ""; + String s2 = m2.length > 1 ? m2[1] : ""; + + int suffixComp = s1.compareToIgnoreCase(s2); + if (suffixComp != 0) { + return suffixComp; + } + } else if (match1) { + return -1; + } else if (match2) { + return 1; + } else { + int numComp = num1.compareToIgnoreCase(num2); + if (numComp != 0) { + return numComp; + } + } + // Fallback to alphabetical sorting if numbers are equal + return a.getUrlDataName().compareToIgnoreCase(b.getUrlDataName()); + } + @Transactional(readOnly = true) public List getAllUrlsSortedByUrlName() { // TODO Auto-generated method stub @@ -89,7 +147,7 @@ public class UrlDataService { } // Sort by numbering - urlDataResDTOs.sort(Comparator.comparing(UrlDataResponseDTO::getUrlDataName, String.CASE_INSENSITIVE_ORDER)); + urlDataResDTOs.sort(this::compareUrlDataNum); return urlDataResDTOs; } @@ -100,8 +158,7 @@ public class UrlDataService { .map(urlDataMap::toUrlDataResDTO) .filter(urlDataResDTO -> urlDataResDTO.getStatResDTO() != null && urlDataResDTO.getStatResDTO().getStatId().equals(1L)) - .sorted(Comparator.comparing(UrlDataResponseDTO::getUrlDataName, String.CASE_INSENSITIVE_ORDER)) - .collect(Collectors.toList()); + .collect(Collectors.toCollection(ArrayList::new)); // Assign numbering using cache for (UrlDataResponseDTO urlDataResDTO : urlDataResDTOs) { @@ -129,39 +186,7 @@ public class UrlDataService { } } // FINAL SORT BASED ON ASSIGNED NUMBER - urlDataResDTOs.sort((a, b) -> { - String num1 = a.getUrlDataNum(); - String num2 = b.getUrlDataNum(); - - if (num1 == null) - return 1; - if (num2 == null) - return -1; - - // Extract numeric + alpha suffix - var m1 = num1.matches("(\\d+)([A-Za-z]*)") ? num1.replaceAll("(\\d+)([A-Za-z]*)", "$1,$2").split(",") - : new String[] { "999999", "" }; - var m2 = num2.matches("(\\d+)([A-Za-z]*)") ? num2.replaceAll("(\\d+)([A-Za-z]*)", "$1,$2").split(",") - : new String[] { "999999", "" }; - - int n1 = Integer.parseInt(m1[0]); - int n2 = Integer.parseInt(m2[0]); - - // Compare numeric - if (n1 != n2) - return Integer.compare(n1, n2); - - // Compare alphabetic suffix (empty suffix should come first) - String s1 = m1.length > 1 ? m1[1] : ""; - String s2 = m2.length > 1 ? m2[1] : ""; - - if (s1.isEmpty() && !s2.isEmpty()) - return -1; - if (!s1.isEmpty() && s2.isEmpty()) - return 1; - - return s1.compareToIgnoreCase(s2); - }); + urlDataResDTOs.sort(this::compareUrlDataNum); return urlDataResDTOs; } @@ -175,9 +200,13 @@ public class UrlDataService { String reqUrlUsrName = addUrlDataReqDTO.getUrlDataUsrName().trim(); String reqUrlUsrPass = addUrlDataReqDTO.getUrlDataUsrPass().trim(); String reqUrlCatName = addUrlDataReqDTO.getUrlCatName().trim(); + String reqUrlSubCatName = addUrlDataReqDTO.getUrlSubCatName() != null + ? addUrlDataReqDTO.getUrlSubCatName().trim() + : ""; String urlUsrName = reqUrlUsrName.isEmpty() ? "-" : reqUrlUsrName; String urlUsrPass = reqUrlUsrPass.isEmpty() ? "-" : reqUrlUsrPass; String urlCatName = reqUrlCatName.isEmpty() ? "-" : reqUrlCatName; + String urlSubCatName = reqUrlSubCatName.isEmpty() ? "-" : reqUrlSubCatName; UrlCategoryResponseDTO urlCatResDTO = urlCatMap .toUrlCatResDTO(urlCatRepo.findUrlCatByUrlCatName(urlCatName).orElseGet(() -> { @@ -185,10 +214,23 @@ public class UrlDataService { return urlCatRepo.save(urlCatMap.toUrlCatEnt(urlCatReqDTO)); })); + // Retrieve or create subcategory under this category + List subCatList = urlSubCatRepo.findUrlSubCatByNameAndCatId(urlSubCatName, urlCatResDTO.getUrlCatId()); + UrlSubCategoryResponseDTO urlSubCatResDTO; + if (subCatList.isEmpty()) { + UrlSubCategory subCat = new UrlSubCategory(); + subCat.setUrlSubCatName(urlSubCatName); + subCat.setUrlCat(urlCatMap.toUrlCatEnt(urlCatResDTO)); + urlSubCatResDTO = urlSubCatMap.toUrlSubCatResDTO(urlSubCatRepo.save(subCat)); + } else { + urlSubCatResDTO = urlSubCatMap.toUrlSubCatResDTO(subCatList.get(0)); + } + StatusResponseDTO statResDTO = statMap.toStatResDTO(statRepo.findById(1L).get()); urlDataRepo.save(urlDataMap.toUrlDatEnt( - new UrlDataRequestDTO(urlLink, urlName, urlUsrPass, urlUsrName, urlCatResDTO, statResDTO))); + new UrlDataRequestDTO(urlLink, urlName, urlUsrPass, urlUsrName, urlSubCatResDTO, urlCatResDTO, + statResDTO))); // Refresh the cache after adding urlDataCacheSer.refreshCache(); logger.info("URL added successfully"); @@ -214,9 +256,13 @@ public class UrlDataService { String reqUrlUsrName = updUrlDataReqDTO.getUrlDataUsrName().trim(); String reqUrlUsrPass = updUrlDataReqDTO.getUrlDataUsrPass().trim(); String reqUrlCatName = updUrlDataReqDTO.getUrlCatName().trim(); + String reqUrlSubCatName = updUrlDataReqDTO.getUrlSubCatName() != null + ? updUrlDataReqDTO.getUrlSubCatName().trim() + : ""; String urlUsrName = reqUrlUsrName.isEmpty() ? "-" : reqUrlUsrName; String urlUsrPass = reqUrlUsrPass.isEmpty() ? "-" : reqUrlUsrPass; String urlCatName = reqUrlCatName.isEmpty() ? "-" : reqUrlCatName; + String urlSubCatName = reqUrlSubCatName.isEmpty() ? "-" : reqUrlSubCatName; UrlCategoryResponseDTO urlCatResDTO = urlCatMap .toUrlCatResDTO(urlCatRepo.findUrlCatByUrlCatName(urlCatName).orElseGet(() -> { @@ -224,6 +270,18 @@ public class UrlDataService { return urlCatRepo.save(urlCatMap.toUrlCatEnt(urlCatReqDTO)); })); + // Retrieve or create subcategory under this category + List subCatList = urlSubCatRepo.findUrlSubCatByNameAndCatId(urlSubCatName, urlCatResDTO.getUrlCatId()); + UrlSubCategoryResponseDTO urlSubCatResDTO; + if (subCatList.isEmpty()) { + UrlSubCategory subCat = new UrlSubCategory(); + subCat.setUrlSubCatName(urlSubCatName); + subCat.setUrlCat(urlCatMap.toUrlCatEnt(urlCatResDTO)); + urlSubCatResDTO = urlSubCatMap.toUrlSubCatResDTO(urlSubCatRepo.save(subCat)); + } else { + urlSubCatResDTO = urlSubCatMap.toUrlSubCatResDTO(subCatList.get(0)); + } + StatusResponseDTO statResDTO = statMap.toStatResDTO(statRepo.findById(updUrlDataReqDTO.getStatId()).get()); urlDataResDTO.setUrlDataId(updUrlDataReqDTO.getUrlDataId()); @@ -231,6 +289,7 @@ public class UrlDataService { urlDataResDTO.setUrlDataName(updUrlDataReqDTO.getUrlDataName()); urlDataResDTO.setUrlDataUsrName(urlUsrName); urlDataResDTO.setUrlDataUsrPass(urlUsrPass); + urlDataResDTO.setUrlSubCatResDTO(urlSubCatResDTO); urlDataResDTO.setUrlCatResDTO(urlCatResDTO); urlDataResDTO.setStatResDTO(statResDTO); @@ -250,20 +309,22 @@ public class UrlDataService { @Transactional(readOnly = true) public List getUrlsByUrlCatName(String urlCatName) { // TODO Auto-generated method stub - // Fetch Category ID - Long urlCatId = urlCatRepo.findUrlCatByUrlCatName(urlCatName).map(urlCatMap::toUrlCatResDTO).get() - .getUrlCatId(); + // Fetch Category ID and exact Category Name from DB + com.gcc_it_cell_services.springboot.response_dto.UrlCategoryResponseDTO catRes = urlCatRepo.findUrlCatByUrlCatName(urlCatName) + .map(urlCatMap::toUrlCatResDTO) + .orElseThrow(() -> new RuntimeException("Category not found")); + Long urlCatId = catRes.getUrlCatId(); + String exactCatName = catRes.getUrlCatName(); // Fetch URLs under category List urls = urlDataRepo.findAllUrlsByUrlCatId(urlCatId).stream() .map(urlDataMap::toUrlDataResDTO) .filter(urlDataResDTO -> urlDataResDTO.getStatResDTO() != null && urlDataResDTO.getStatResDTO().getStatId().equals(1L)) - .sorted(Comparator.comparing(UrlDataResponseDTO::getUrlDataName, String.CASE_INSENSITIVE_ORDER)) - .toList(); + .collect(Collectors.toCollection(ArrayList::new)); - // Read per-category numbering map using category key (lowercase) - Map perCatMap = urlDataCacheSer.getGlobalUrlCatData().get(urlCatName); + // Read per-category numbering map using category key + Map perCatMap = urlDataCacheSer.getGlobalUrlCatData().get(exactCatName); // Assign numbering if (perCatMap != null) { @@ -274,6 +335,9 @@ public class UrlDataService { }); } + // Sort based on assigned number + urls.sort(this::compareUrlDataNum); + return urls; } diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties index dbb3fda..2842743 100644 --- a/src/main/resources/application-local.properties +++ b/src/main/resources/application-local.properties @@ -8,15 +8,16 @@ server.address=0.0.0.0 # MySQL Database Configuration # =============================== # Local DB server -spring.datasource.url=jdbc:mysql://localhost:3306/gcc-it-cell-services +spring.datasource.url=jdbc:mysql://localhost:3306/gcc-it-cell-services_24062026 spring.datasource.username=root -spring.datasource.password=1916008_Saec +spring.datasource.password=root +#spring.datasource.password=1916008_Saec spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # spring.datasource.url=jdbc:oracle:thin:@10.1.0.14:1521:chncorp # spring.datasource.username=fileadjust # spring.datasource.password=fileadjust -# spring.datasource.driver-class-name=oracle.jdbc.OracleDriver +# spring.datasource.driver-class-name=oracle.jdbc.OracleDriver # =============================== # JPA / Hibernate Config @@ -30,4 +31,4 @@ logging.level.org.hibernate.SQL=DEBUG logging.level.org.hibernate.type.descriptor.sql=TRACE # Production context path -server.servlet.context-path=/ \ No newline at end of file +server.servlet.context-path=/ \ No newline at end of file diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 4118f5a..9c92b9e 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -8,9 +8,9 @@ server.address=0.0.0.0 # MySQL Database Configuration # =============================== # Local DB server -spring.datasource.url=jdbc:mysql://localhost:3306/gcc-it-cell-services +spring.datasource.url=jdbc:mysql://gcc-facial-db-instance-1.cf48eqcciziq.ap-south-1.rds.amazonaws.com:3306/gcc-it-cell-services spring.datasource.username=root -spring.datasource.password=1916008_Saec +spring.datasource.password=gcc-facial-password spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 82aa9ab..50e55e2 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,2 +1,3 @@ -spring.profiles.active=prod -app.getOtpUrl=https://tmegov.onex-aura.com/api/sms \ No newline at end of file +spring.profiles.active=local +#spring.profiles.active=prod +app.getOtpUrl=https://tmegov.onex-aura.com/api/sms \ No newline at end of file diff --git a/src/main/resources/static/assets/css/home-styles.css b/src/main/resources/static/assets/css/home-styles.css index ce6facc..0952b09 100644 --- a/src/main/resources/static/assets/css/home-styles.css +++ b/src/main/resources/static/assets/css/home-styles.css @@ -2,7 +2,8 @@ display: flex; justify-content: center; gap: 40px; - flex-wrap: wrap; /* allows wrapping */ + flex-wrap: wrap; + /* allows wrapping */ margin-top: 20px; text-align: center; } @@ -10,31 +11,32 @@ /* Center heading & align right-side search neatly */ .row.align-items-center.gx-3.justify-content-between { - display: flex; - align-items: center; - flex-wrap: wrap; + display: flex; + align-items: center; + flex-wrap: wrap; } .form-title { - margin: 0; - font-weight: 600; - text-align: center; - width: 100%; + margin: 0; + font-weight: 600; + text-align: center; + width: 100%; } /* Ensure dropdown + search align nicely */ .col-md-6.d-flex.justify-content-md-end { - display: flex; - justify-content: flex-end; - align-items: center; - gap: 10px; + display: flex; + justify-content: flex-end; + align-items: center; + gap: 10px; } /* Center content properly in larger screens */ .form-container { - max-width: 1200px; - margin: 0 auto; /* centers horizontally */ + max-width: 1200px; + margin: 0 auto; + /* centers horizontally */ } .form-control { @@ -44,7 +46,8 @@ .custom-button { padding: 15px 30px; - position: relative; /* Important: makes absolute badge work */ + position: relative; + /* Important: makes absolute badge work */ background: white; border: 1px solid #ccc; border-radius: 10px; @@ -53,9 +56,12 @@ cursor: pointer; transition: background-color 0.3s, transform 0.2s; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); - flex: 1 1 calc(33.333% - 40px); /* 3 per row, minus gap space */ - max-width: 300px; /* optional: limit button width */ - min-width: 200px; /* optional: keep from shrinking too small */ + flex: 1 1 calc(33.333% - 40px); + /* 3 per row, minus gap space */ + max-width: 300px; + /* optional: limit button width */ + min-width: 200px; + /* optional: keep from shrinking too small */ } .custom-button:hover { @@ -69,18 +75,18 @@ } #search-input2 { - min-width: 80px; - border-radius: 8px; + min-width: 80px; + border-radius: 8px; } #search-input { - width: 250px; - max-width: 100%; - padding: 8px 12px; - border: 1px solid #ccc; - border-radius: 8px; - box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); - transition: all 0.2s ease; + width: 250px; + max-width: 100%; + padding: 8px 12px; + border: 1px solid #ccc; + border-radius: 8px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); + transition: all 0.2s ease; } #search-input:focus { @@ -197,20 +203,21 @@ } .url-num-badge { - position: absolute; - top: -12px; - left: -12px; - background-color: #005f73; /* or any color */ - color: white; - font-size: 14px; - width: 28px; - height: 28px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-weight: bold; - z-index: 10; + position: absolute; + top: -12px; + left: -12px; + background-color: #005f73; + /* or any color */ + color: white; + font-size: 14px; + width: 28px; + height: 28px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: bold; + z-index: 10; } /* === Fade Animations === */ @@ -219,6 +226,7 @@ transform: scale(0.95); opacity: 0; } + to { transform: scale(1); opacity: 1; @@ -230,6 +238,7 @@ transform: scale(1); opacity: 1; } + to { transform: scale(0.95); opacity: 0; @@ -238,49 +247,52 @@ /* Fix heading centering in large screens */ @media (min-width: 992px) { - .row.align-items-center.gy-3.justify-content-center.text-center { - justify-content: space-between !important; - } + .row.align-items-center.gy-3.justify-content-center.text-center { + justify-content: space-between !important; + } - .row.align-items-center.gy-3.justify-content-center.text-center .col-md-6:first-child { - text-align: center !important; - flex: 1 1 100%; - max-width: 100%; - } + .row.align-items-center.gy-3.justify-content-center.text-center .col-md-6:first-child { + text-align: center !important; + flex: 1 1 100%; + max-width: 100%; + } - .row.align-items-center.gy-3.justify-content-center.text-center .col-md-6:last-child { - flex: 1 1 auto; - max-width: fit-content; - margin-left: auto; - } + .row.align-items-center.gy-3.justify-content-center.text-center .col-md-6:last-child { + flex: 1 1 auto; + max-width: fit-content; + margin-left: auto; + } } /* Tablet screens - shrink more */ -@media (max-width : 768px) { +@media (max-width : 768px) { .form-title { - font-size: 1.2rem !important; + font-size: 1.2rem !important; } - + .button-row { - justify-content: center; - align-items: center; - flex-direction: column; - gap: 15px; /* tighter spacing for small screens */ - } + justify-content: center; + align-items: center; + flex-direction: column; + gap: 15px; + /* tighter spacing for small screens */ + } - .custom-button { - flex: none; - width: 80%; /* nicely centered buttons */ - max-width: 320px; - min-width: unset; /* remove constraint */ - } + .custom-button { + flex: none; + width: 80%; + /* nicely centered buttons */ + max-width: 320px; + min-width: unset; + /* remove constraint */ + } } /* Responsive tweak for small screens */ @media (max-width: 576px) { .form-title { - font-size: 1.1rem !important; - text-align: center; + font-size: 1.1rem !important; + text-align: center; } #search-input { @@ -292,4 +304,34 @@ width: 100% !important; max-width: 100px; } +} + +/* === Subcategory State Navigation Styles === */ +.subcat-header-container { + display: none; + /* managed via JS (flex/none) */ + align-items: center; + justify-content: space-between; + width: 100%; + margin-top: 20px; + margin-bottom: 20px; + border-bottom: 2px solid #005f73; + padding-bottom: 12px; +} + +.subcat-header-title { + margin: 0; + color: #005f73; + font-size: 1.4rem; + font-weight: 600; +} + +/* Folder buttons distinctive styling */ +.subcat-folder-btn { + background-color: #f7fbfc; + border-color: #005f73; +} + +.subcat-folder-btn:hover { + background: #eef7f9; } \ No newline at end of file diff --git a/src/main/resources/static/assets/js/home-script.js b/src/main/resources/static/assets/js/home-script.js index 8f2cd6c..56de5a5 100644 --- a/src/main/resources/static/assets/js/home-script.js +++ b/src/main/resources/static/assets/js/home-script.js @@ -173,7 +173,7 @@ async function closeLoginModal() { } // Close the modal when clicking outside of it -window.onclick = function(event) { +window.onclick = function (event) { const modal = document.getElementById("login-modal"); if (event.target === modal) { closeLoginModal(); // Also reset everything when clicking outside @@ -306,14 +306,28 @@ document.addEventListener("DOMContentLoaded", () => { }); /* === PAGE BUTTON FILTER & SEARCH === */ -document.addEventListener("DOMContentLoaded", function() { - const buttonContainer = document.querySelector(".button-row"); - if (!buttonContainer) return; +document.addEventListener("DOMContentLoaded", function () { + const serviceContainer = document.getElementById("service-container"); + if (!serviceContainer) return; - const buttons = Array.from(buttonContainer.querySelectorAll(".custom-button")); + // State elements + const subcatHeaderContainer = document.getElementById("subcat-header-container"); + const subcatHeaderTitle = document.getElementById("subcat-header-title"); + const backToSubcatsBtn = document.getElementById("back-to-subcats-btn"); + const categoryBackRow = document.getElementById("category-back-row"); + const subcatBackRow = document.getElementById("subcat-back-row"); + + // Button categories + const urlButtons = Array.from(serviceContainer.querySelectorAll(".url-service-btn")); + const folderButtons = Array.from(serviceContainer.querySelectorAll(".subcat-folder-btn")); function getButtonName(btn) { - return btn.childNodes[btn.childNodes.length - 1].textContent.trim().toLowerCase(); + const cloned = btn.cloneNode(true); + const badge = cloned.querySelector(".url-num-badge"); + if (badge) badge.remove(); + const icon = cloned.querySelector("i"); + if (icon) icon.remove(); + return cloned.textContent.trim().toLowerCase(); } const searchInput = document.getElementById("search-input"); @@ -321,18 +335,17 @@ document.addEventListener("DOMContentLoaded", function() { const clearBtn = document.getElementById("clear-btn"); const noResultsMsg = document.getElementById("no-res-msg"); + // Current subcategory viewing state (null = State 1, string = State 2 for that subcategory) + let currentSubcat = null; + /* === Reusable Dropdown Renderer === */ function populateAlphabetDropdown(onlyVisible = false) { - const prevSelected = alphabetSelect.value; // keep previous selection - - // Reset dropdown + const prevSelected = alphabetSelect.value; alphabetSelect.innerHTML = ''; - const letters = new Set(); - buttons.forEach(btn => { + urlButtons.forEach(btn => { if (onlyVisible && btn.style.display === "none") return; - const name = getButtonName(btn); if (name.length > 0) { letters.add(name[0].toUpperCase()); @@ -340,7 +353,6 @@ document.addEventListener("DOMContentLoaded", function() { }); const sortedLetters = [...letters].sort(); - sortedLetters.forEach(letter => { const opt = document.createElement("option"); opt.value = letter; @@ -348,7 +360,6 @@ document.addEventListener("DOMContentLoaded", function() { alphabetSelect.appendChild(opt); }); - // Restore previous selection if still valid if (letters.has(prevSelected.toUpperCase())) { alphabetSelect.value = prevSelected.toUpperCase(); } else { @@ -356,65 +367,159 @@ document.addEventListener("DOMContentLoaded", function() { } } - // Initial dropdown load (all buttons) - populateAlphabetDropdown(false); - - /* === FILTER BUTTONS === */ - function updateButtonVisibility() { - const query = searchInput.value.trim().toLowerCase(); - const selectedLetter = alphabetSelect.value.toLowerCase(); + /* === State Management Functions === */ + function showState1() { + currentSubcat = null; + if (subcatHeaderContainer) { + subcatHeaderContainer.style.setProperty("display", "none", "important"); + } + if (categoryBackRow) { + categoryBackRow.style.setProperty("display", "flex", "important"); + } + if (subcatBackRow) { + subcatBackRow.style.setProperty("display", "none", "important"); + } - let matchedButtons = 0; + // Show folder buttons (except EMS which is nested inside FMS) + folderButtons.forEach(btn => { + if (btn.getAttribute("data-target-subcat") === "EMS") { + btn.style.display = "none"; + } else { + btn.style.display = "inline-block"; + } + }); - buttons.forEach(btn => { - const name = getButtonName(btn); + // Show direct buttons + urlButtons.forEach(btn => { + if (btn.classList.contains("direct-url-btn")) { + btn.style.display = "inline-block"; + } else { + btn.style.display = "none"; + } + }); + } - const matchesLetter = - selectedLetter === "" || name.startsWith(selectedLetter); + function showState2(subcatName) { + currentSubcat = subcatName; + if (subcatHeaderTitle) { + subcatHeaderTitle.textContent = subcatName; + } + if (subcatHeaderContainer) { + subcatHeaderContainer.style.setProperty("display", "flex", "important"); + } + if (categoryBackRow) { + categoryBackRow.style.setProperty("display", "none", "important"); + } + if (subcatBackRow) { + subcatBackRow.style.setProperty("display", "flex", "important"); + } - const matchesSearch = - query === "" || name.includes(query); + // Configure folder button displays: show EMS button box only when inside FMS + folderButtons.forEach(btn => { + if (subcatName === "FMS" && btn.getAttribute("data-target-subcat") === "EMS") { + btn.style.display = "inline-block"; + } else { + btn.style.display = "none"; + } + }); - if (matchesLetter && matchesSearch) { + // Configure URL button displays + urlButtons.forEach(btn => { + if (btn.getAttribute("data-subcat") === subcatName) { btn.style.display = "inline-block"; - matchedButtons++; } else { btn.style.display = "none"; } }); + } - noResultsMsg.style.display = matchedButtons > 0 ? "none" : "block"; + /* === FILTER BUTTONS === */ + function updateButtonVisibility() { + const query = searchInput.value.trim().toLowerCase(); + const selectedLetter = alphabetSelect.value.toLowerCase(); + const isSearching = (query !== "" || selectedLetter !== ""); - // Show or hide clear button - clearBtn.style.display = - (searchInput.value.trim() !== "" || alphabetSelect.value !== "") - ? "inline-block" - : "none"; + if (isSearching) { + // Search Mode + if (subcatHeaderContainer) { + subcatHeaderContainer.style.setProperty("display", "none", "important"); + } + if (categoryBackRow) { + categoryBackRow.style.setProperty("display", "none", "important"); + } + if (subcatBackRow) { + subcatBackRow.style.setProperty("display", "none", "important"); + } + folderButtons.forEach(btn => btn.style.display = "none"); - // Rebuild dropdown based on visible buttons - populateAlphabetDropdown(true); - } + let matchedCount = 0; + urlButtons.forEach(btn => { + const name = getButtonName(btn); + const matchesLetter = selectedLetter === "" || name.startsWith(selectedLetter); + const matchesSearch = query === "" || name.includes(query); - /* === CLEAR BUTTON HANDLER === */ - clearBtn.addEventListener("click", function() { - searchInput.value = ""; - alphabetSelect.value = ""; + if (matchesLetter && matchesSearch) { + btn.style.display = "inline-block"; + matchedCount++; + } else { + btn.style.display = "none"; + } + }); - clearBtn.style.display = "none"; + noResultsMsg.style.display = matchedCount > 0 ? "none" : "block"; + clearBtn.style.display = "inline-block"; + populateAlphabetDropdown(true); + } else { + // State-based display + if (currentSubcat) { + showState2(currentSubcat); + } else { + showState1(); + } + noResultsMsg.style.display = "none"; + clearBtn.style.display = "none"; + populateAlphabetDropdown(false); + } + } - // Reset all buttons visible - buttons.forEach(btn => btn.style.display = "inline-block"); + /* === CLICK HANDLERS === */ + // Subcategory folders + folderButtons.forEach(btn => { + btn.addEventListener("click", function (e) { + e.preventDefault(); + const targetSubcat = this.getAttribute("data-target-subcat"); + showState2(targetSubcat); + }); + }); - // Rebuild dropdown from all buttons - populateAlphabetDropdown(false); + // Back Button + if (backToSubcatsBtn) { + backToSubcatsBtn.addEventListener("click", function (e) { + e.preventDefault(); + if (currentSubcat === "EMS") { + showState2("FMS"); + } else { + showState1(); + } + }); + } + // Clear Search Button + clearBtn.addEventListener("click", function () { + searchInput.value = ""; + alphabetSelect.value = ""; + currentSubcat = null; // Clear active state back to State 1 on clear + showState1(); noResultsMsg.style.display = "none"; + clearBtn.style.display = "none"; + populateAlphabetDropdown(false); }); /* === EVENT LISTENERS === */ alphabetSelect.addEventListener("change", updateButtonVisibility); searchInput.addEventListener("keyup", updateButtonVisibility); - // Initial render - updateButtonVisibility(); -}); \ No newline at end of file + // Initialize the page in State 1 + showState1(); + populateAlphabetDropdown(false); +}); \ No newline at end of file diff --git a/src/main/resources/static/assets/js/update-url-data-script.js b/src/main/resources/static/assets/js/update-url-data-script.js index 6233597..1a33cba 100644 --- a/src/main/resources/static/assets/js/update-url-data-script.js +++ b/src/main/resources/static/assets/js/update-url-data-script.js @@ -49,6 +49,7 @@ document.addEventListener("DOMContentLoaded", function() { const urlUsrNameInput = document.getElementById("urlDataUsrName"); const urlUsrPassInput = document.getElementById("urlDataUsrPass"); const urlCatNameInput = document.getElementById("urlCatName"); + const urlSubCatNameInput = document.getElementById("urlSubCatName"); const togglePass = document.getElementById("togglePass"); // safety check @@ -68,6 +69,7 @@ document.addEventListener("DOMContentLoaded", function() { urlUsrNameInput.value = ""; urlUsrPassInput.value = ""; urlCatNameInput.value = ""; + urlSubCatNameInput.value = ""; return; } @@ -81,12 +83,14 @@ document.addEventListener("DOMContentLoaded", function() { const usrname = selected.getAttribute("data-usrname"); const pass = selected.getAttribute("data-pass"); const urlCatName = selected.getAttribute("data-urlCatName"); + const urlSubCatName = selected.getAttribute("data-urlSubCatName"); urlLinkInput.value = link || ""; urlNameInput.value = name || ""; urlUsrNameInput.value = (usrname && usrname !== "-") ? usrname : ""; urlUsrPassInput.value = (pass && pass !== "-") ? pass : ""; urlCatNameInput.value = (urlCatName && urlCatName !== "-") ? urlCatName : ""; + urlSubCatNameInput.value = (urlSubCatName && urlSubCatName !== "-") ? urlSubCatName : ""; // Reset to hidden state urlUsrPassInput.type = "password"; diff --git a/src/main/resources/templates/urls/add-url-data.html b/src/main/resources/templates/urls/add-url-data.html index 3735e0e..aa480b8 100644 --- a/src/main/resources/templates/urls/add-url-data.html +++ b/src/main/resources/templates/urls/add-url-data.html @@ -64,6 +64,11 @@ +
+ + +
diff --git a/src/main/resources/templates/urls/get-urls-by-url-category.html b/src/main/resources/templates/urls/get-urls-by-url-category.html index 1703c55..a528c6b 100644 --- a/src/main/resources/templates/urls/get-urls-by-url-category.html +++ b/src/main/resources/templates/urls/get-urls-by-url-category.html @@ -24,6 +24,17 @@
+ +
+ +
+ + +
@@ -43,17 +54,48 @@
- -
- + +
+ + + + + + + + + + + + + +
diff --git a/src/main/resources/templates/urls/update-url-data.html b/src/main/resources/templates/urls/update-url-data.html index 7549aec..010ec6b 100644 --- a/src/main/resources/templates/urls/update-url-data.html +++ b/src/main/resources/templates/urls/update-url-data.html @@ -39,8 +39,8 @@
@@ -78,6 +78,13 @@
+
+ + +
+
+