Browse Source

chages in UI main category divided into two part has external and internal services

master
yoga-agoy 4 months ago
parent
commit
f56f6260d2
  1. 15
      src/main/java/com/gcc_it_cell_services/springboot/controller/UrlDataController.java
  2. 9
      src/main/java/com/gcc_it_cell_services/springboot/mapper/UrlDataMapper.java
  3. 32
      src/main/java/com/gcc_it_cell_services/springboot/mapper/UrlSubCategoryMapper.java
  4. 5
      src/main/java/com/gcc_it_cell_services/springboot/model/UrlData.java
  5. 112
      src/main/java/com/gcc_it_cell_services/springboot/model/UrlSubCategory.java
  6. 7
      src/main/java/com/gcc_it_cell_services/springboot/repository/UrlDataRepository.java
  7. 18
      src/main/java/com/gcc_it_cell_services/springboot/repository/UrlSubCategoryRepository.java
  8. 1
      src/main/java/com/gcc_it_cell_services/springboot/request_dto/AddUrlDataRequestDTO.java
  9. 1
      src/main/java/com/gcc_it_cell_services/springboot/request_dto/UpdateUrlDataRequestDTO.java
  10. 2
      src/main/java/com/gcc_it_cell_services/springboot/request_dto/UrlDataRequestDTO.java
  11. 18
      src/main/java/com/gcc_it_cell_services/springboot/request_dto/UrlSubCategoryRequestDTO.java
  12. 4
      src/main/java/com/gcc_it_cell_services/springboot/response_dto/UrlDataResponseDTO.java
  13. 19
      src/main/java/com/gcc_it_cell_services/springboot/response_dto/UrlSubCategoryResponseDTO.java
  14. 63
      src/main/java/com/gcc_it_cell_services/springboot/service/UrlDataCacheService.java
  15. 152
      src/main/java/com/gcc_it_cell_services/springboot/service/UrlDataService.java
  16. 5
      src/main/resources/application-local.properties
  17. 4
      src/main/resources/application-prod.properties
  18. 3
      src/main/resources/application.properties
  19. 178
      src/main/resources/static/assets/css/home-styles.css
  20. 205
      src/main/resources/static/assets/js/home-script.js
  21. 4
      src/main/resources/static/assets/js/update-url-data-script.js
  22. 5
      src/main/resources/templates/urls/add-url-data.html
  23. 62
      src/main/resources/templates/urls/get-urls-by-url-category.html
  24. 11
      src/main/resources/templates/urls/update-url-data.html

15
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<UrlDataResponseDTO> urls = urlDataSer.getUrlsByUrlCatName(urlCatName);
urlCatName = urls.get(0).getUrlCatResDTO().getUrlCatName();
String resolvedCatName = urls.isEmpty() ? urlCatName : urls.get(0).getUrlCatResDTO().getUrlCatName();
java.util.Map<String, List<UrlDataResponseDTO>> 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
}

9
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;

32
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;
}
}

5
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

112
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<UrlData> 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;
}
}

7
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<UrlData, Long> {
@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);
}

18
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<UrlSubCategory, Long> {
@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<UrlSubCategory> findUrlSubCatByNameAndCatId(@Param("urlSubCatName") String urlSubCatName, @Param("urlCatId") Long urlCatId);
}

1
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;
}

1
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;
}

2
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;

18
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;
}

4
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;
}

19
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;
}

63
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<String, Map<String, String>> globalUrlCatData = new ConcurrentHashMap<String, Map<String, String>>();
private final Map<String, String> globalUrlLinks = new ConcurrentHashMap<String, String>();
@Transactional
public synchronized void initializeDefaultSubCategories() {
List<UrlCategory> categories = urlCatRepo.findAll();
for (UrlCategory cat : categories) {
List<UrlSubCategory> 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

152
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<UrlDataResponseDTO> 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<UrlSubCategory> 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<UrlSubCategory> 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<UrlDataResponseDTO> 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<UrlDataResponseDTO> 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<String, String> perCatMap = urlDataCacheSer.getGlobalUrlCatData().get(urlCatName);
// Read per-category numbering map using category key
Map<String, String> 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;
}

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

@ -8,9 +8,10 @@ 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

4
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

3
src/main/resources/application.properties

@ -1,2 +1,3 @@
spring.profiles.active=prod
spring.profiles.active=local
#spring.profiles.active=prod
app.getOtpUrl=https://tmegov.onex-aura.com/api/sms

178
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) {
.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 {
@ -293,3 +305,33 @@
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;
}

205
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 = '<option value="">-</option>';
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();
// Initialize the page in State 1
showState1();
populateAlphabetDropdown(false);
});

4
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";

5
src/main/resources/templates/urls/add-url-data.html

@ -64,6 +64,11 @@
<input type="text" id="urlCatName" class="form-control" name="urlCatName"
placeholder="Enter URL Category Name">
</div>
<div class="form-group">
<label for="urlSubCatName">URL Sub Category Name: </label>
<input type="text" id="urlSubCatName" class="form-control" name="urlSubCatName"
placeholder="Enter URL Sub Category Name">
</div>
</div>
<div class="submit-btn">
<input type="submit" value="Submit">

62
src/main/resources/templates/urls/get-urls-by-url-category.html

@ -24,6 +24,17 @@
<div class="container">
<div class="form-container">
<!-- Back Button to Home (State 1) -->
<div class="header-row" id="category-back-row">
<button type="button" class="back-button" onclick="window.history.back()">&#8592; Back</button>
</div>
<!-- Back Button to Subcategories (State 2) -->
<div class="header-row" id="subcat-back-row" style="display: none;">
<button type="button" id="back-to-subcats-btn" class="back-button">
&#8592; Back
</button>
</div>
<!-- Heading + Search Bar on same line -->
<div class="row align-items-center gy-3 justify-content-center text-center">
@ -43,17 +54,48 @@
</div>
</div>
<!-- Buttons Section -->
<div class="button-row" id="service-container">
<button type="button" th:each="url : ${urls}" th:attr="data-link = ${url.urlDataLink.startsWith('/urls')}
? @{${url.urlDataLink}}
: ${url.urlDataLink}" th:onclick="${url.urlDataLink.startsWith('/urls')}
? 'window.location.href=this.dataset.link'
: 'window.open(this.dataset.link, \'_blank\')'" class="custom-button">
<!-- State 2 Header (Hidden by default in State 1) -->
<div id="subcat-header-container" class="subcat-header-container">
<h3 id="subcat-header-title" class="subcat-header-title">Subcategory</h3>
</div>
<span class="url-num-badge">[[${url.urlDataNum}]]</span>
[[${url.urlDataName}]]
</button>
<!-- Grouped Buttons Section -->
<div class="button-row" id="service-container" style="margin-top: 10px;">
<!-- 1. Direct buttons (where subcategory is '-') -->
<th:block th:each="entry : ${groupedUrls}" th:if="${entry.key == '-'}">
<button type="button" th:each="url : ${entry.value}" th:attr="data-link = ${url.urlDataLink.startsWith('/urls')}
? @{${url.urlDataLink}}
: ${url.urlDataLink}" th:onclick="${url.urlDataLink.startsWith('/urls')}
? 'window.location.href=this.dataset.link'
: 'window.open(this.dataset.link, \'_blank\')'" class="custom-button url-service-btn direct-url-btn"
data-subcat="-">
<span class="url-num-badge">[[${url.urlDataNum}]]</span>
[[${url.urlDataName}]]
</button>
</th:block>
<!-- 2. Subcategory Folder Buttons (for subcategories other than '-') -->
<th:block th:each="entry : ${groupedUrls}" th:if="${entry.key != '-'}">
<button type="button" class="custom-button subcat-folder-btn" th:data-target-subcat="${entry.key}"
th:id="'subcat-folder-' + ${#strings.replace(entry.key, ' ', '_')}">
<span class="url-num-badge"
th:text="${(entry.value != null && !entry.value.isEmpty() && entry.value.get(0).urlDataNum != null) ? entry.value.get(0).urlDataNum.substring(0, 1) : ''}">1</span>
<span class="subcat-title-text">[[${entry.key}]]</span>
</button>
</th:block>
<!-- 3. Subcategory service buttons (initially hidden) -->
<th:block th:each="entry : ${groupedUrls}" th:if="${entry.key != '-'}">
<button type="button" th:each="url : ${entry.value}" th:attr="data-link = ${url.urlDataLink.startsWith('/urls')}
? @{${url.urlDataLink}}
: ${url.urlDataLink}" th:onclick="${url.urlDataLink.startsWith('/urls')}
? 'window.location.href=this.dataset.link'
: 'window.open(this.dataset.link, \'_blank\')'" class="custom-button url-service-btn subcat-url-btn"
th:data-subcat="${entry.key}" style="display: none;">
<span class="url-num-badge">[[${url.urlDataNum}]]</span>
[[${url.urlDataName}]]
</button>
</th:block>
</div>
<!-- Optional "No results" message -->

11
src/main/resources/templates/urls/update-url-data.html

@ -39,8 +39,8 @@
<option th:each="url : ${urls}" th:value="${url.urlDataId}"
th:data-link="${url.urlDataLink}" th:data-name="${url.urlDataName}"
th:data-usrname="${url.urlDataUsrName}" th:data-pass="${url.urlDataUsrPass}"
th:data-urlCatName="${url.urlCatResDTO.urlCatName}" th:text="${url.urlDataName}"
th:data-statId="${url.statResDTO.statId}">
th:data-urlCatName="${url.urlCatResDTO.urlCatName}" th:data-urlSubCatName="${url.urlSubCatResDTO != null ? url.urlSubCatResDTO.urlSubCatName : ''}"
th:text="${url.urlDataName}" th:data-statId="${url.statResDTO.statId}">
</option>
</select>
</div>
@ -78,6 +78,13 @@
<input type="text" id="urlCatName" class="form-control" name="urlCatName"
placeholder="Enter URL Category Name">
</div>
<div class="form-group">
<label for="urlSubCatName">URL Sub Category Name: </label>
<input type="text" id="urlSubCatName" class="form-control" name="urlSubCatName"
placeholder="Enter URL Sub Category Name">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="urlStatDropdown">Select URL Status:</label>
<select id="urlStatDropdown" name="statId" class="form-control">

Loading…
Cancel
Save