Skip to content
Snippets Groups Projects
Commit 50506d2d authored by John Watkins's avatar John Watkins
Browse files

Merge branch 'issueThirty' into 'develop'

Issue thirty

See merge request !105
parents e20ee1f3 bb0c507c
No related branches found
No related tags found
3 merge requests!114LoggingService service class, new method to add a log to the "Logs" table when...,!106Branch Update,!105Issue thirty
...@@ -23,6 +23,7 @@ repositories { ...@@ -23,6 +23,7 @@ repositories {
} }
dependencies { dependencies {
implementation 'com.google.code.gson:gson:2.8.9'
implementation 'commons-io:commons-io:2.8.0' implementation 'commons-io:commons-io:2.8.0'
implementation 'io.jsonwebtoken:jjwt:0.9.1' implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'org.springframework.session:spring-session-jdbc:2.4.3' implementation 'org.springframework.session:spring-session-jdbc:2.4.3'
......
package com.example.clientproject.web.restControllers;
import com.example.clientproject.data.shops.Shops;
import com.example.clientproject.data.shops.ShopsRepo;
import com.example.clientproject.data.tags.Tags;
import com.example.clientproject.data.tags.TagsRepo;
import com.example.clientproject.services.RecommendationGenerator;
import com.google.gson.Gson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
import java.util.*;
import java.util.stream.Collectors;
@RestController
public class ShopSearch {
@Autowired
ShopsRepo shopsRepo;
@Autowired
TagsRepo tagsRepo;
@Autowired
RecommendationGenerator recommendationGenerator;
@GetMapping("/shop/search")
public String searchShops(@RequestParam(value = "q", required = false) String query,
@RequestParam(value = "p", required = false) Integer page,
@RequestParam(value = "t", required = false) List<String> tags,
HttpSession session) throws Exception {
final Integer ITEMS_PER_PAGE = 6;
//Get all the active shops
List<Shops> allShops = shopsRepo.findActiveShops();
//Filter the shops using the query provided
if(query != null){
allShops = allShops
.stream()
.filter(s -> s.getShopName().toLowerCase(Locale.ROOT).strip().contains(query.toLowerCase(Locale.ROOT).strip()))
.collect(Collectors.toList());
}
//Filter using the tags provided
if(tags!=null){
List<Long> validTagIds = new ArrayList<>();
for (String t : tags){
Optional<Tags> tagsOptional = tagsRepo.findByTagNameIgnoreCase(t);
if(tagsOptional.isPresent()){
Long tagId = tagsOptional.get().getTagId();
if (!validTagIds.contains(tagId)){
validTagIds.add(tagId);
}
}
}
List<Shops> validShops = new ArrayList<>();
for (Shops s : allShops){
boolean match = false;
for (Tags t : s.getShopTags()){
if (validTagIds.contains(t.getTagId())){
match = true;
break;
}
}
if (match){
validShops.add(s);
}
}
allShops = validShops;
}
//Paginate
boolean hasNextPage = false;
if (allShops.size() > ITEMS_PER_PAGE){
if(page==null){
page = 1;
}
List<List<Shops>> pages = getPages(allShops, ITEMS_PER_PAGE);
if(page > pages.size()){
page = 1;
}
if (pages.size() >= page){
allShops = pages.get(page-1);
}
if (pages.size() >= page + 1){
hasNextPage = true;
}
}
//Sort in order of relevance
allShops = recommendationGenerator.getRecommendations(session, allShops);
//Convert to required format
List<HashMap<String, String>> formattedShops = new ArrayList<>();
for(Shops shop : allShops){
HashMap<String,String> data = new HashMap<>();
data.put("name",shop.getShopName());
data.put("banner",shop.getShopBanner());
data.put("id", String.valueOf(shop.getShopId()));
data.put("category",shop.getCategory().getCategoryName());
data.put("website",shop.getShopWebsite());
Integer reward_count = shop.getStampBoard().getRewards().size();
data.put("reward_count",String.valueOf(reward_count));
if(reward_count != 0){
data.put("next_reward_name",shop.getStampBoard().getRewards().get(0).getRewardName());
data.put("next_reward_pos",String.valueOf(shop.getStampBoard().getRewards().get(0).getRewardStampLocation()));
}else{
data.put("next_reward_name","No Rewards");
}
formattedShops.add(data);
}
Map<String,Object> returnMap = new HashMap<>();
returnMap.put("shops",formattedShops);
returnMap.put("hasNextPage", hasNextPage);
Gson gson = new Gson();
String json = gson.toJson(returnMap);
return json;
}
public <T> List<List<T>> getPages(Collection<T> c, Integer pageSize) {
if (c == null)
return Collections.emptyList();
List<T> list = new ArrayList<T>(c);
if (pageSize == null || pageSize <= 0 || pageSize > list.size())
pageSize = list.size();
int numPages = (int) Math.ceil((double)list.size() / (double)pageSize);
List<List<T>> pages = new ArrayList<List<T>>(numPages);
for (int pageNum = 0; pageNum < numPages;)
pages.add(list.subList(pageNum * pageSize, Math.min(++pageNum * pageSize, list.size())));
return pages;
}
}
src/main/resources/static/imgs/uploaded/02b3324f_113c_4c98_8ad6_7f1cf28f74c9.jpg

6.35 KiB

src/main/resources/static/imgs/uploaded/1eab1fb2_7744_4eb6_9505_8a1e9b59981b.jpg

6.35 KiB

src/main/resources/static/imgs/uploaded/7615374a_c54e_4854_ad96_14b719c5af9c.jpg

6.35 KiB

src/main/resources/static/imgs/uploaded/8d96f724_c612_4da0_9ab0_26a0ea2ad161.jpg

6.35 KiB

var tags = [];
var query = "";
var page = 1;
var searchBar = document.getElementById("main-search");
var tagGroup = document.getElementById("search-tag-group");
function updateSearch(e){
page = 1;
query = searchBar.value;
doSearch(false)
}
function removeTag(i){
tags.splice(i, 1);
page=1;
updateUI()
doSearch(false)
}
function addTag(e){
if(e.key== "Enter"){
if (searchBar.value != ""){
page = 1;
query = "";
tags.push(searchBar.value.toLowerCase());
searchBar.value = "";
updateUI();
doSearch(false);
}
}
}
function updateUI(){
tagGroup.innerHTML = "";
for(let [i,tag] of tags.entries()){
tagGroup.innerHTML += `
<div class="control mr-3">
<div class="tags has-addons">
<span class="tag gradient">${tag}</span>
<a class="tag is-delete" onclick="removeTag(${i})"></a>
</div>
</div>`
}
}
function doSearch(fromNextPageBtn){
let url = "/shop/search"
url += "?q=" + query
url += "&p=" + page.toString()
for (let t of tags){
url += "&t=" + t.toString()
}
fetch(url)
.then(e=>e.json())
.then(data=>{
if(!fromNextPageBtn){
document.getElementById("business_card_container").innerHTML = "";
}
for(let shop of data["shops"]){
addShop(shop);
}
if(data["hasNextPage"] == true){
document.getElementById("loadMoreBtn").style.display = "flex";
}else{
document.getElementById("loadMoreBtn").setAttribute('style', 'display:none!important');
}
});
}
function addShop(shopInfo){
let img_path = shopInfo["banner"]
let title = shopInfo["name"]
let reward_text = shopInfo["next_reward_name"] + " at " + shopInfo["next_reward_pos"] + " stamps"
let reward_amount = shopInfo["reward_count"]
let shopId = shopInfo["id"]
document.getElementById("business_card_container").innerHTML +=`
<div class="business_container box" style="position:relative;">
<div class="image" style="background-image:url(${img_path});"></div>
<div class="favouriteStar starContainer" onclick="favouriteBusiness(this,${shopId});">
<span class="icon favouriteStar">
<i class="far fa-star"></i>
</span>
<span class="icon favouriteStar">
<i class="fas fa-star"></i>
</span>
</div>
<div class="content">
<h1 class="title is-4 mb-1">${title}</h1>
<p class="mb-1">${reward_text}</p>
<div class="is-full-width" style="display:flex;justify-content: space-between;align-items: center">
<div class="level-left">
<span class="icon is-small is-left ml-1 mr-1">
<i class="fas fa-gift"></i>
</span>
<p>${reward_amount}</p>
</div>
<div class="level-right">
<button class="button is-rounded gradient" onclick="redirect(${shopId})">
View Shop
<span class="icon is-small is-left ml-1">
<i class="fas fa-arrow-right"></i>
</span>
</button>
</div>
</div>
</div>
</div>`
}
function loadNextPage(){
page++;
doSearch(true);
}
\ No newline at end of file
...@@ -62,22 +62,24 @@ ...@@ -62,22 +62,24 @@
<div class="container is-full-width is-flex is-justify-content-center is-align-items-center is-flex-direction-column mb-4"> <div class="container is-full-width is-flex is-justify-content-center is-align-items-center is-flex-direction-column mb-4">
<h1 class="title is-3">Where else can I earn rewards?</h1> <h1 class="title is-3">Where else can I earn rewards?</h1>
<div class="control has-icons-left mb-2" style="width: 60%;"> <div class="control has-icons-left mb-2" style="width: 60%;">
<input class="input" type="text" placeholder="Enter Brands or keywords e.g. Vegan, Clothing etc.."> <input class="input" type="text" placeholder="Enter Brands or keywords e.g. Vegan, Clothing etc.."
oninput="updateSearch(event)" onkeydown="addTag(event)" id="main-search">
<span class="icon is-small is-left"> <span class="icon is-small is-left">
<i class="fas fa-search"></i> <i class="fas fa-search"></i>
</span> </span>
</div> </div>
<!--Tags--> <!--Tags-->
<div class="field is-grouped is-grouped-multiline" style="width: 60%;"> <div class="field is-grouped is-grouped-multiline" style="width: 60%;" id="search-tag-group">
<div th:each="tag: ${tags}" th:include="fragments/tag.html :: tag" <!-- <div th:each="tag: ${tags}" th:include="fragments/tag.html :: tag"-->
th:with="text=${tag}"></div> <!-- th:with="text=${tag}"></div>-->
</div> </div>
</div> </div>
<div class="container is-full-width is-flex is-justify-content-center is-align-items-center is-flex-wrap-wrap"> <div class="container is-full-width is-flex is-justify-content-center is-align-items-center is-flex-wrap-wrap"
<div th:each="shop,i: ${normalShops}" th:include="fragments/business_card.html :: business_card" id="business_card_container">
th:with="title=${shop.shopName}, reward_text='Free coffee at 6 stamps', reward_amount=4, </div>
img_path=${shop.shopImage}, shopId=${shop.shopId}"></div> <div class="is-full-width is-flex is-justify-content-center is-align-items-center" id="loadMoreBtn" style="display: none!important;;">
<a onclick="loadNextPage()">Load More</a>
</div> </div>
</div> </div>
...@@ -97,5 +99,7 @@ ...@@ -97,5 +99,7 @@
</div> </div>
</div> </div>
</div> </div>
<script src="/js/searchBar.js"></script>
<script>doSearch(false);</script>
</body> </body>
</html> </html>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment