From 845bc4126f0000a169cbbeca6bcc176307cffee1 Mon Sep 17 00:00:00 2001 From: Anas Khan Date: Sun, 16 Nov 2025 23:09:10 +0530 Subject: [PATCH] Employee host search is using the DB table 16-11-25 --- .../vms/cache/EmployeeNameCache.java | 75 ++++++++++++++----- .../vms/controller/HomeController.java | 48 +++++++++--- .../templates/host-search-results.html | 21 +++--- 3 files changed, 105 insertions(+), 39 deletions(-) diff --git a/web-backend/src/main/java/com/statusneo/vms/cache/EmployeeNameCache.java b/web-backend/src/main/java/com/statusneo/vms/cache/EmployeeNameCache.java index b529157..98a4fea 100644 --- a/web-backend/src/main/java/com/statusneo/vms/cache/EmployeeNameCache.java +++ b/web-backend/src/main/java/com/statusneo/vms/cache/EmployeeNameCache.java @@ -1,21 +1,20 @@ package com.statusneo.vms.cache; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.springframework.stereotype.Component; - import com.statusneo.vms.model.Employee; import com.statusneo.vms.repository.EmployeeRepository; import com.statusneo.vms.util.TrieNode; - import jakarta.annotation.PostConstruct; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +/** + * Lightweight in-memory cache of employees (name/email) used by UI host-search. + * Added search(...) helper so UI queries use the cache rather than hitting DB + * or external APIs. + */ @Component public class EmployeeNameCache { @@ -40,7 +39,8 @@ public void initializeCache() { } public void insert(String name) { - if (name == null || name.isBlank()) return; + if (name == null || name.isBlank()) + return; TrieNode node = root; for (char c : name.toLowerCase().toCharArray()) { node = node.getChildren().computeIfAbsent(c, k -> new TrieNode()); @@ -50,11 +50,13 @@ public void insert(String name) { } public List getEmployeeNamesByPrefix(String prefix) { - if (prefix == null) prefix = ""; + if (prefix == null) + prefix = ""; TrieNode node = root; for (char c : prefix.toLowerCase().toCharArray()) { node = node.getChildren().get(c); - if (node == null) return Collections.emptyList(); + if (node == null) + return Collections.emptyList(); } // Use LinkedHashSet to preserve insertion order and avoid duplicates @@ -70,17 +72,20 @@ public List getEmployeeNamesByPrefix(String prefix) { } private void collectNames(TrieNode node, Set results) { - if (results.size() >= MAX_SUGGESTIONS) return; + if (results.size() >= MAX_SUGGESTIONS) + return; if (node.isEndOfWord()) { // add originals for this terminal node for (String orig : node.getOriginals()) { - if (results.size() >= MAX_SUGGESTIONS) break; + if (results.size() >= MAX_SUGGESTIONS) + break; results.add(orig); } } for (Map.Entry entry : node.getChildren().entrySet()) { - if (results.size() >= MAX_SUGGESTIONS) break; + if (results.size() >= MAX_SUGGESTIONS) + break; collectNames(entry.getValue(), results); } } @@ -96,4 +101,40 @@ public void bulkInsert(Collection names) { } } } + + // New code starts here + private final Map byId = new ConcurrentHashMap<>(); + + /** + * Search cached employees by name/email/identifier. Case-insensitive substring + * match. + * If query is null/empty returns a small default list (first 25). + */ + public List search(String query) { + Collection all = byId.values(); + if (query == null || query.isBlank()) { + return all.stream() + .sorted(Comparator.comparing(Employee::getName, Comparator.nullsLast(String::compareToIgnoreCase))) + .limit(25) + .collect(Collectors.toList()); + } + String q = query.toLowerCase(Locale.ROOT).trim(); + return all.stream() + .filter(e -> { + if (e == null) + return false; + String name = e.getName() == null ? "" : e.getName().toLowerCase(Locale.ROOT); + String email = e.getEmail() == null ? "" : e.getEmail().toLowerCase(Locale.ROOT); + return name.contains(q) || email.contains(q); + }) + .sorted(Comparator.comparing(Employee::getName, Comparator.nullsLast(String::compareToIgnoreCase))) + .limit(25) + .collect(Collectors.toList()); + } + + // Optional convenience + public Optional getById(Long id) { + return Optional.ofNullable(byId.get(id)); + } + // New code ends here } \ No newline at end of file diff --git a/web-backend/src/main/java/com/statusneo/vms/controller/HomeController.java b/web-backend/src/main/java/com/statusneo/vms/controller/HomeController.java index 4fbaa89..5472744 100644 --- a/web-backend/src/main/java/com/statusneo/vms/controller/HomeController.java +++ b/web-backend/src/main/java/com/statusneo/vms/controller/HomeController.java @@ -18,37 +18,61 @@ */ package com.statusneo.vms.controller; -import com.statusneo.vms.model.Employee; -import com.statusneo.vms.service.EmployeeService; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; -import java.util.List; +import com.statusneo.vms.cache.EmployeeNameCache; +import com.statusneo.vms.model.Employee; +/** + * Expose host search endpoint backed by EmployeeNameCache so UI queries use + * cache. + */ @Controller public class HomeController { - private final EmployeeService employeeService; - public HomeController(EmployeeService employeeService) { - this.employeeService = employeeService; + private final EmployeeNameCache employeeNameCache; + + @Autowired + public HomeController(EmployeeNameCache employeeNameCache /* , other deps if present */) { + this.employeeNameCache = employeeNameCache; + } + + /** + * HTMX endpoint used by the host-search box. + * Returns a small fragment containing search results. The fragment has + * id="host-results" + * so client-side JS/HTMX can reveal it after swap. + * + * Example request: GET /api/hosts/search?hostSearch=anas + */ + @GetMapping("/api/hosts/search") + public String searchHosts(@RequestParam(name = "hostSearch", required = false) String hostSearch, Model model) { + List results = employeeNameCache.search(hostSearch); + model.addAttribute("hosts", results); + // return Thymeleaf fragment - ensure fragment name "results" exists in template + return "host-search-results :: results"; } @GetMapping("/search-employees") public String employees(@RequestParam(value = "hostSearch", required = false) String hostSearch, - @RequestParam(value = "employee", required = false) String employee, - @RequestParam(value = "query", required = false) String query, - Model model) { + @RequestParam(value = "employee", required = false) String employee, + @RequestParam(value = "query", required = false) String query, + Model model) { // Prefer hostSearch (used by index.jte), then employee, then query - String q = (hostSearch != null && !hostSearch.isBlank()) ? hostSearch : - ((employee != null && !employee.isBlank()) ? employee : (query == null ? "" : query)); + String q = (hostSearch != null && !hostSearch.isBlank()) ? hostSearch + : ((employee != null && !employee.isBlank()) ? employee : (query == null ? "" : query)); if (q.isBlank()) { return "employees"; } - List employees = employeeService.searchEmployeesByName(q); + List employees = employeeNameCache.search(q); model.addAttribute("employees", employees); return "employees"; } diff --git a/web-backend/src/main/resources/templates/host-search-results.html b/web-backend/src/main/resources/templates/host-search-results.html index ee91188..f03f98b 100644 --- a/web-backend/src/main/resources/templates/host-search-results.html +++ b/web-backend/src/main/resources/templates/host-search-results.html @@ -1,14 +1,15 @@
-
-
-
John Smith
-
john.smith@company.com
+ +
+
+
    +
  • +
    Employee Name
    +
    email@example.com
    +
  • +
  • No results
  • +
-
- No employee found -
\ No newline at end of file