diff --git a/src/main/java/uk/ac/cf/spring/demo/sports/match/MatchController.java b/src/main/java/uk/ac/cf/spring/demo/sports/match/MatchController.java index 167b19df6fce765706dc4f6945e12407ee7f87ca..7187714d98bf0645345a1cc3befbd6055567acba 100644 --- a/src/main/java/uk/ac/cf/spring/demo/sports/match/MatchController.java +++ b/src/main/java/uk/ac/cf/spring/demo/sports/match/MatchController.java @@ -2,11 +2,8 @@ package uk.ac.cf.spring.demo.sports.match; import org.springframework.beans.factory.annotation.Autowired; //import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.bind.annotation.*; +//import org.springframework.web.servlet.ModelAndView; import java.util.List; @RestController @@ -23,11 +20,33 @@ public class MatchController { public List<MatchItem> getAllMenuItems() { return matchService.getMatchItems(); } + // get Match by id @GetMapping("/{id}") public MatchItem getMenuItem(@PathVariable Long id) { return matchService.getMatchItemById(id); } -// @GetMapping("/match") + + // add + @PostMapping + public void addMatchItem(@RequestBody MatchItem matchItem) { + matchService.addMatchItem(matchItem); + } + + // update + @PutMapping("/{id}") + public void updateMatchItem(@PathVariable Long id, @RequestBody MatchItem matchItem) { + matchItem.setId(id); + matchService.updateMatchItem(matchItem); + } + + //delete + @DeleteMapping("/{id}") + public void deleteMatchItem(@PathVariable Long id) { + matchService.deleteMatchItem(id); + } + + + // @GetMapping("/match") // public ModelAndView getMatch() { // ModelAndView modelAndView = new ModelAndView("match/allMatchList"); // List<MatchItem> matchItems = matchService.getMatchItems(); @@ -41,39 +60,38 @@ public class MatchController { // MatchItem matchItem = matchService.getMatchItemById(id); // modelAndView.addObject("matchItem", matchItem); // return modelAndView; +// }//matchItemForm add match +// @GetMapping("/match/add") +// public ModelAndView addMatchItem() { +// ModelAndView modelAndView = new ModelAndView("match/matchItemForm"); +// MatchItemForm emptyMatchItem = new MatchItemForm(); +// modelAndView.addObject("matchItem", emptyMatchItem); +// return modelAndView; // } - //matchItemForm add match - @GetMapping("/match/add") - public ModelAndView addMatchItem() { - ModelAndView modelAndView = new ModelAndView("match/matchItemForm"); - MatchItemForm emptyMatchItem = new MatchItemForm(); - modelAndView.addObject("matchItem", emptyMatchItem); - return modelAndView; - } // - @GetMapping("/match/edit/{id}") - public ModelAndView editMatchItemTh(@PathVariable("id") Long id) { - ModelAndView modelAndView = new ModelAndView("match/matchItemForm"); - // construct matchToUpdate object to get matchItem by id - MatchItem matchToUpdate = matchService.getMatchItemById(id); - // Create the form object for the match - MatchItemForm matchItemForm = new MatchItemForm( - matchToUpdate.getId(), - matchToUpdate.getSport(), - matchToUpdate.getPlayerAId(), - matchToUpdate.getPlayerBId(), - matchToUpdate.getPlanTime(), - matchToUpdate.getStatus(), - matchToUpdate.getScoreA(), - matchToUpdate.getScoreB(), - matchToUpdate.getConfirmByA(), - matchToUpdate.getConfirmByB(), - matchToUpdate.getWinnerId() - ); - - modelAndView.addObject("matchItem", matchItemForm); - return modelAndView; - } +// @GetMapping("/match/edit/{id}") +// public ModelAndView editMatchItemTh(@PathVariable("id") Long id) { +// ModelAndView modelAndView = new ModelAndView("match/matchItemForm"); +// // construct matchToUpdate object to get matchItem by id +// MatchItem matchToUpdate = matchService.getMatchItemById(id); +// // Create the form object for the match +// MatchItemForm matchItemForm = new MatchItemForm( +// matchToUpdate.getId(), +// matchToUpdate.getSport(), +// matchToUpdate.getPlayerAId(), +// matchToUpdate.getPlayerBId(), +// matchToUpdate.getPlanTime(), +// matchToUpdate.getStatus(), +// matchToUpdate.getScoreA(), +// matchToUpdate.getScoreB(), +// matchToUpdate.getConfirmByA(), +// matchToUpdate.getConfirmByB(), +// matchToUpdate.getWinnerId() +// ); +// +// modelAndView.addObject("matchItem", matchItemForm); +// return modelAndView; +// } } diff --git a/src/main/java/uk/ac/cf/spring/demo/sports/match/MatchRepository.java b/src/main/java/uk/ac/cf/spring/demo/sports/match/MatchRepository.java index e25408f8bf46eb57b5b6a25e5a1ad04ed6352e49..cdce9b7b21fef6deebe0059a0648f9a000ec4415 100644 --- a/src/main/java/uk/ac/cf/spring/demo/sports/match/MatchRepository.java +++ b/src/main/java/uk/ac/cf/spring/demo/sports/match/MatchRepository.java @@ -11,5 +11,9 @@ public interface MatchRepository { List<MatchItem> getMatchItemBySport(String sport); // add MatchItem void addMatchItem(MatchItem matchItem); +// update MatchItem + void updateMatchItem(MatchItem matchItem); +// delete MatchItem + void deleteMatchItem(Long id); } diff --git a/src/main/java/uk/ac/cf/spring/demo/sports/match/MatchRepositoryImpl.java b/src/main/java/uk/ac/cf/spring/demo/sports/match/MatchRepositoryImpl.java index 03556e55369d344ef0c2afd62a2269e99a5686cd..ec8c33707ebfb0fbe4c0a0776915d32745ea44fe 100644 --- a/src/main/java/uk/ac/cf/spring/demo/sports/match/MatchRepositoryImpl.java +++ b/src/main/java/uk/ac/cf/spring/demo/sports/match/MatchRepositoryImpl.java @@ -9,7 +9,7 @@ import java.util.List; @Repository public class MatchRepositoryImpl implements MatchRepository{ // jdbcTemplate - dependency injection - private JdbcTemplate jdbc; + private final JdbcTemplate jdbc; private RowMapper<MatchItem> matchItemMapper; public MatchRepositoryImpl(JdbcTemplate aJdbc) { this.jdbc = aJdbc; @@ -70,4 +70,28 @@ public class MatchRepositoryImpl implements MatchRepository{ matchItem.getWinnerId() ); } + + @Override + public void updateMatchItem(MatchItem matchItem) { + String sql = "update match_item set sport = ?, player_AId = ?, player_BId = ?, plan_Time = ?, status = ?, score_a = ?, score_b = ?, confirm_a = ?, confirm_b = ?, winner_id = ? where id = ?"; + jdbc.update(sql, + matchItem.getSport(), + matchItem.getPlayerAId(), + matchItem.getPlayerBId(), + Timestamp.valueOf(matchItem.getPlanTime()), + matchItem.getStatus(), + matchItem.getScoreA(), + matchItem.getScoreB(), + matchItem.getConfirmByA(), + matchItem.getConfirmByB(), + matchItem.getWinnerId(), + matchItem.getId() + ); + } + + @Override + public void deleteMatchItem(Long id) { + String sql = "DELETE FROM match_item WHERE id = ?"; + jdbc.update(sql, id); + } } diff --git a/src/main/java/uk/ac/cf/spring/demo/sports/match/MatchService.java b/src/main/java/uk/ac/cf/spring/demo/sports/match/MatchService.java index 9b784ce149c64ad144f365af77a07c4a16037d81..bd0900e3b2dda196774ed77f2e6cd77276bcfda7 100644 --- a/src/main/java/uk/ac/cf/spring/demo/sports/match/MatchService.java +++ b/src/main/java/uk/ac/cf/spring/demo/sports/match/MatchService.java @@ -11,4 +11,8 @@ public interface MatchService { List<MatchItem> getMatchItemBySport(String sport); // add MatchItem void addMatchItem(MatchItem matchItem); + // update MatchItem + void updateMatchItem(MatchItem matchItem); + // delete MatchItem + void deleteMatchItem(Long id); } diff --git a/src/main/java/uk/ac/cf/spring/demo/sports/match/MatchServiceListImpl.java b/src/main/java/uk/ac/cf/spring/demo/sports/match/MatchServiceListImpl.java index 723d174b5062bd4815e92c776db5d5fc750ccc66..8c17f955a87680877d0aa9a74bc5535d4f501f0f 100644 --- a/src/main/java/uk/ac/cf/spring/demo/sports/match/MatchServiceListImpl.java +++ b/src/main/java/uk/ac/cf/spring/demo/sports/match/MatchServiceListImpl.java @@ -27,9 +27,28 @@ public class MatchServiceListImpl implements MatchService{ public List<MatchItem> getMatchItemBySport(String sport) { return matchRepository.getMatchItemBySport(sport); } - + // add @Override public void addMatchItem(MatchItem matchItem) { matchRepository.addMatchItem(matchItem); } + // update + @Override + public void updateMatchItem(MatchItem matchItem) { + if (matchItem.getId() == null || matchItem.getId() <= 0) { + throw new IllegalArgumentException("Invalid Match Item ID"); + } + MatchItem existingItem = matchRepository.getMatchItemById(matchItem.getId()); + if (existingItem != null) { + matchRepository.updateMatchItem(matchItem); + } else { + throw new IllegalArgumentException("Match item not found"); + } + + } + // delete + @Override + public void deleteMatchItem(Long id) { + matchRepository.deleteMatchItem(id); + } } diff --git a/src/main/java/uk/ac/cf/spring/demo/sports/rankingtable/RankingController.java b/src/main/java/uk/ac/cf/spring/demo/sports/rankingtable/RankingController.java new file mode 100644 index 0000000000000000000000000000000000000000..51b0dbb4d593a1d6d8aa744cc12a77a65eaaa06c --- /dev/null +++ b/src/main/java/uk/ac/cf/spring/demo/sports/rankingtable/RankingController.java @@ -0,0 +1,48 @@ +package uk.ac.cf.spring.demo.sports.rankingtable; + +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/rankings") // 公共路径 +public class RankingController { + private final RankingService rankingService; + + public RankingController(RankingService rankingService) { + this.rankingService = rankingService; + } + + /** + * get all rankings + * + * @return all rankings + */ + @GetMapping + public List<RankingTable> getAllRankings() { + return rankingService.getAllRankings(); + } + @GetMapping("/byallandsort") + public List<TotalWinsDTO> getRankingsByAllSort(String order) { + //rankingService.processCompletedMatches(); + return rankingService.getTotalWinsByAll(order); + + } + + /** + * get rankings filtered by sports and wins + * + * @return rankings + */ + @GetMapping("/bysportandsort") + public List<RankingTable> getRankingsBySportAndOrder( + @RequestParam(required = false) String sport, + @RequestParam(defaultValue = "desc") String order + ) { + //rankingService.processCompletedMatches(); + return rankingService.getRankingsBySportAndOrder(sport, order); + } + + + +} diff --git a/src/main/java/uk/ac/cf/spring/demo/sports/rankingtable/RankingRepository.java b/src/main/java/uk/ac/cf/spring/demo/sports/rankingtable/RankingRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..884f5637d825fd96543d89ef23b96a02ba025b2c --- /dev/null +++ b/src/main/java/uk/ac/cf/spring/demo/sports/rankingtable/RankingRepository.java @@ -0,0 +1,133 @@ +package uk.ac.cf.spring.demo.sports.rankingtable; + +import lombok.Getter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +import java.util.List; +@Repository + +public class RankingRepository { + public List<RankingTable> findBySportAndOrder(String sport, String order) { + // 确保 order 参数是有效值 + if (!"asc".equalsIgnoreCase(order) && !"desc".equalsIgnoreCase(order)) { + order = "desc"; + } + + String sql = "SELECT * FROM ranking WHERE sport = ? ORDER BY wins " + order.toUpperCase(); + return jdbcTemplate.query(sql, mapRowToRanking(), sport); + } + + + @Getter + public static class MatchResult { + private final Long winnerId; + private final String sport; + + public MatchResult(Long winnerId, String sport) { + this.winnerId = winnerId; + this.sport = sport; + } + + } + + private final JdbcTemplate jdbcTemplate; + + + public RankingRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + // get all rankings sql code + public List<RankingTable> findAll() { + String sql = "SELECT * FROM ranking"; + return jdbcTemplate.query(sql, mapRowToRanking()); + } + + public List<RankingTable> findBySport(String sport) { + System.out.println(sport); + String sql = "SELECT * FROM ranking WHERE sport = ?"; + return jdbcTemplate.query(sql, mapRowToRanking(), sport); + } + + + // get ranking by userid and sport + public RankingTable findByUserIdAndSport(Long userId, String sport) { + String sql = "SELECT * FROM ranking WHERE user_id = ? AND sport = ?"; + return jdbcTemplate.queryForObject(sql, mapRowToRanking(), userId, sport); + } + + // update rankings + public void saveOrUpdate(Long userId, String sport, int newWins) { + String sql = """ + INSERT INTO ranking (user_id, sport, wins) + VALUES (?, ?, ?) + ON DUPLICATE KEY UPDATE wins = ? + """; + jdbcTemplate.update(sql, userId, sport, newWins, newWins); + } + + + // get result from match where status is completed + public List<MatchResult> findWinnersAndSportsFromCompletedMatches() { + String sql = """ + SELECT winner_id, sport + FROM match_item + WHERE status = 'completed' + """; + return jdbcTemplate.query(sql, (rs, rowNum) -> new MatchResult( + rs.getLong("winnerId"), + rs.getString("sport") + + )); + } + public List<RankingTable> findAllSortedByWins(String order) { + // 确保order参数的值是"asc"或"desc",如果不是则默认为"desc" + if (order == null || (!order.equalsIgnoreCase("asc") && !order.equalsIgnoreCase("desc"))) { + order = "asc"; // 默认降序 + } + + String sql = "SELECT * FROM ranking ORDER BY wins " + order.toUpperCase(); + return jdbcTemplate.query(sql, mapRowToRanking()); + } + //总胜利数查询 + public List<TotalWinsDTO> findTotalWinsByAll(String order) { + // 验证 order 参数是否合法,只允许 "asc" 或 "desc" + String sortOrder = "desc"; // 默认值 + if ("asc".equalsIgnoreCase(order)) { + sortOrder = "asc"; + } + + String sql = "SELECT user_id,username,'totalwins' AS sport, SUM(wins) AS wins FROM ranking GROUP BY user_id ORDER BY wins " + sortOrder; + + + return jdbcTemplate.query(sql, mapRowToTotalWinsDTO()); + } + + // a function can merge result into a ranking + private RowMapper<RankingTable> mapRowToRanking() { + return (rs, rowNum) -> { + RankingTable ranking = new RankingTable(); + ranking.setId(rs.getLong("id")); // 映射 id + ranking.setUserId(rs.getLong("user_id")); // 映射 user_id + ranking.setSport(rs.getString("sport")); // 映射 sport + ranking.setWins(rs.getInt("wins")); // 映射 wins + ranking.setUsername(rs.getString("username")); // 映射 username + return ranking; + }; + } + //聚合查询专用映射方法 + private RowMapper<TotalWinsDTO> mapRowToTotalWinsDTO() { + return (rs, rowNum) -> { + TotalWinsDTO dto = new TotalWinsDTO(); + dto.setUsername(rs.getString("username")); + dto.setUserId(rs.getLong("user_id")); + dto.setSport("totalwins"); // 聚合查询时 sport 固定为 "totalwins" + dto.setWins(rs.getInt("wins")); + return dto; + }; + } + +} + diff --git a/src/main/java/uk/ac/cf/spring/demo/sports/rankingtable/RankingService.java b/src/main/java/uk/ac/cf/spring/demo/sports/rankingtable/RankingService.java new file mode 100644 index 0000000000000000000000000000000000000000..9eacf144a6cb4bc211c818881e09fa789fa405b1 --- /dev/null +++ b/src/main/java/uk/ac/cf/spring/demo/sports/rankingtable/RankingService.java @@ -0,0 +1,28 @@ +package uk.ac.cf.spring.demo.sports.rankingtable; + +import org.springframework.stereotype.Service; + +import java.util.List; +@Service + +public interface RankingService { + + List<RankingTable> getAllRankings(); + + + List<RankingTable> getRankingsBySport(String sport); + + + void updateRanking(Long userId, String sport, int wins); + + + void processCompletedMatches(); + + List<RankingTable> getAllRankingsSortedByWins(String order); + + List<RankingTable> getRankingsBySportAndOrder(String sport, String order); + + List<TotalWinsDTO> getTotalWinsByAll(String order); + + +} diff --git a/src/main/java/uk/ac/cf/spring/demo/sports/rankingtable/RankingServiceImpl.java b/src/main/java/uk/ac/cf/spring/demo/sports/rankingtable/RankingServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..5f352391d40b2050a2878b0442538d623ad2156b --- /dev/null +++ b/src/main/java/uk/ac/cf/spring/demo/sports/rankingtable/RankingServiceImpl.java @@ -0,0 +1,72 @@ +package uk.ac.cf.spring.demo.sports.rankingtable; + +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class RankingServiceImpl implements RankingService { + private final RankingRepository rankingRepository; + + public RankingServiceImpl(RankingRepository rankingRepository) { + this.rankingRepository = rankingRepository; + } + + + @Override + public List<RankingTable> getAllRankings() { + return rankingRepository.findAll(); + } + + + @Override + public List<RankingTable> getRankingsBySport(String sport) { + return rankingRepository.findBySport(sport); + } + + + @Override + public void updateRanking(Long userId, String sport, int wins) { + rankingRepository.saveOrUpdate(userId, sport, wins); + } + + + @Override + public void processCompletedMatches() { + + List<RankingRepository.MatchResult> completedMatches = rankingRepository.findWinnersAndSportsFromCompletedMatches(); + + // loop every match result + for (RankingRepository.MatchResult result : completedMatches) { + Long winnerId = result.getWinnerId(); + String sport = result.getSport(); + + // current user wins in particular sport + RankingTable currentRanking = rankingRepository.findByUserIdAndSport(winnerId, sport); + int currentWins = currentRanking != null ? currentRanking.getWins() : 0; + + // update + rankingRepository.saveOrUpdate(winnerId, sport, currentWins + 1); + } + } + @Override + public List<RankingTable> getAllRankingsSortedByWins(String order) { + return rankingRepository.findAllSortedByWins(order); + } + + @Override + public List<RankingTable> getRankingsBySportAndOrder(String sport, String order) { + if (sport == null || sport.equalsIgnoreCase("all")) { + // 如果没有选择特定运动,则按总胜利数排序 + return rankingRepository.findAllSortedByWins(order); + } else { + // 按特定运动和排序方式筛选 + return rankingRepository.findBySportAndOrder(sport, order); + } + } + public List<TotalWinsDTO> getTotalWinsByAll(String order) { + return rankingRepository.findTotalWinsByAll(order); + } + +} + diff --git a/src/main/java/uk/ac/cf/spring/demo/sports/rankingtable/RankingTable.java b/src/main/java/uk/ac/cf/spring/demo/sports/rankingtable/RankingTable.java new file mode 100644 index 0000000000000000000000000000000000000000..f483afac15c65f4f23af743f9b9574b68ecb38cf --- /dev/null +++ b/src/main/java/uk/ac/cf/spring/demo/sports/rankingtable/RankingTable.java @@ -0,0 +1,17 @@ +package uk.ac.cf.spring.demo.sports.rankingtable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +@Data +@NoArgsConstructor + +public class RankingTable { + //create entity class + private Long id; // + private Long userId; // 用户 ID + private int wins; // 获胜场次 + private String sport; + private String username; + +} + diff --git a/src/main/java/uk/ac/cf/spring/demo/sports/rankingtable/TotalWinsDTO.java b/src/main/java/uk/ac/cf/spring/demo/sports/rankingtable/TotalWinsDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..2107882393bd1837776695ff41475fdea3a11927 --- /dev/null +++ b/src/main/java/uk/ac/cf/spring/demo/sports/rankingtable/TotalWinsDTO.java @@ -0,0 +1,12 @@ +package uk.ac.cf.spring.demo.sports.rankingtable; +import lombok.Data; +import lombok.NoArgsConstructor; +@Data +@NoArgsConstructor + +public class TotalWinsDTO { + private Long userId; + private String sport; + private Integer wins; + private String username; +} diff --git a/src/main/java/uk/ac/cf/spring/demo/sports/security/SecurityConfig.java b/src/main/java/uk/ac/cf/spring/demo/sports/security/SecurityConfig.java index 7080a81636a8aac0d2384a24959029f1bd257a58..8069727926f844c9599be21513619f97ad7444df 100644 --- a/src/main/java/uk/ac/cf/spring/demo/sports/security/SecurityConfig.java +++ b/src/main/java/uk/ac/cf/spring/demo/sports/security/SecurityConfig.java @@ -27,7 +27,8 @@ public class SecurityConfig { "/html/**", // static HTML "/html/register.html", // register "/html/login.html", // login - "/rankings", + "/html/login.html" , // login + "/rankings", //ranking data path "/rankings/**" }; diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index a972476b4317082773551cdcacb0cec991ddce5c..20f0049e108b141d5cabd5e5a74c4afb4ee87063 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,4 +1,5 @@ delete from match_item; +delete from ranking; insert into match_item (sport, player_AId, player_BId, plan_Time, status, score_a, score_b, confirm_a, confirm_b, winner_id) values -- test data @@ -14,3 +15,9 @@ values ('Darts', 1, 2, '2024-11-29 15:30:00', 'completed', 300, 280, TRUE, TRUE, 2), -- 6: Confirmed status, Player 1 wins ('TableTennis', 1, 2, '2024-11-30 17:00:00', 'confirmed', 3, 1, TRUE, TRUE, 1); +INSERT INTO ranking (user_id,username,sport, wins) VALUES + (1, 'xzc','Pools', 5), + (2, 'shy','Darts', 3), + (3, 'xx','TableTennis', 10), + (2, 'shy','TableTennis', 4); + diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index cdaafc342af5058ecb92c48686a23aaa4732fe7c..5b0ebe8fc11112f5182b1a8e7c81f6628c3c0efb 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -1,4 +1,5 @@ drop table if exists match_item; +drop table if exists rankings; create table if not exists match_item ( -- Match ID id BIGINT AUTO_INCREMENT PRIMARY KEY, @@ -24,4 +25,21 @@ CREATE TABLE information ( email VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(255) NOT NULL, role VARCHAR(20) DEFAULT 'USER' NOT NULL -); \ No newline at end of file +); +CREATE TABLE IF NOT EXISTS ranking ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + sport ENUM('Pools', 'Darts', 'TableTennis') NOT NULL, + wins INT DEFAULT 0, + username VARCHAR(255)DEFAULT NULL + + + + +); +CREATE TABLE images ( + id INT AUTO_INCREMENT PRIMARY KEY, + filename VARCHAR(255) NOT NULL, -- 图片文件名或路径 + uploader VARCHAR(100) NOT NULL, -- 上传者名称 + upload_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 上传时间 +); diff --git a/src/main/resources/static/css/backGroundMatch.css b/src/main/resources/static/css/backGroundMatch.css new file mode 100644 index 0000000000000000000000000000000000000000..8e40e3b8970e757b6914c9eb8b94ae2915a08468 --- /dev/null +++ b/src/main/resources/static/css/backGroundMatch.css @@ -0,0 +1,46 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} +h1, h2 { + color: darkblue; +} +#matchForm { + /*display: grid;*/ + align-items: center; +} +form input, form button { + margin: 5px; + padding: 10px; + font-size: 14px; + +} + +table { + width: 100%; + border-collapse: collapse; + margin-top: 20px; + border: 1px solid white; +} + +th, td { + padding: 10px; + text-align: left; + border: 1px solid white; +} + +button { + background-color: dodgerblue; + color: white; + border: none; + border-radius: 10px; + cursor: pointer; + margin: 5px; + padding: 10px; + width: 60px; +} + +button:hover { + background-color: cornflowerblue; +} diff --git a/src/main/resources/static/css/footer.css b/src/main/resources/static/css/footer.css index 9e7d27ae331e6de627995dafb11679dd7e8849c2..c4616f08f9410f2f4966569a942f600258905f4e 100644 --- a/src/main/resources/static/css/footer.css +++ b/src/main/resources/static/css/footer.css @@ -1,4 +1,3 @@ - * { margin: 0; padding: 0; diff --git a/src/main/resources/static/css/matchDetail.css b/src/main/resources/static/css/matchDetail.css new file mode 100644 index 0000000000000000000000000000000000000000..d46cdf49bc4bab984e61f67a36a39187d8ba85d4 --- /dev/null +++ b/src/main/resources/static/css/matchDetail.css @@ -0,0 +1,93 @@ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: Arial, sans-serif; + background-color: #f4f4f4; + color: #333; +} +.title-h1{ + h1 { + background-color: #4a4a4a; + p { + font-size: 36px; + color: white; + + font-weight: bold; + text-align: center; + } + + } +} +.container { + width: 80%; + margin: 20px auto; +} + +header { + text-align: center; + margin-bottom: 30px; +} +#matchDetails { + margin-left: 45px; + align-items: center; + background-color: #fff; + border-radius: 12px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + width: 100%; + max-width: 900px; + padding: 30px; + box-sizing: border-box; + margin-top: 30px; +} + +h2 { + text-align: center; + font-size: 2.5em; + color: #333; + margin-bottom: 20px; +} + +#matchScore { + font-size: 2.4em; + line-height: 1.6; + color: #444; + margin: 20px 0; +} + +#matchScore p { + text-align: center; +} + +#matchInfo { + font-size: 1.2em; + line-height: 1.6; + color: #444; +} + +#matchInfo p { + margin: 15px 0; + padding: 10px; + background-color: #f9f9f9; + border-radius: 6px; +} + +strong { + color: #2d2d2d; +} +/*adjust for phone*/ +@media screen and (max-width: 768px) { + .container { + width: 95%; + } + + .match-item { + flex-direction: column; + align-items: flex-start; + } +} + diff --git a/src/main/resources/static/css/matchSchedule.css b/src/main/resources/static/css/matchSchedule.css index 2d0d859800442c5317c8b46829f6e15ad1f9709e..e408266f605ac005e4092e405b59bf9096448c3c 100644 --- a/src/main/resources/static/css/matchSchedule.css +++ b/src/main/resources/static/css/matchSchedule.css @@ -99,6 +99,7 @@ button { justify-content: space-between; align-items: center; gap: 20px; + cursor: pointer; } .match-item p { diff --git a/src/main/resources/static/css/navBar.css b/src/main/resources/static/css/navBar.css index 379656e02c12ad690733b5e8e3b14d2c2f39b4ad..d82debcbde6a3b8dc4b8aa938a5e4a0cd1d9aeed 100644 --- a/src/main/resources/static/css/navBar.css +++ b/src/main/resources/static/css/navBar.css @@ -93,7 +93,7 @@ body { .navbar .auth-buttons .login-btn { background-color: #007BFF; width: 100px; - text-align-all: center; + text-align: center; margin-bottom: 5px; color: white; font-weight: bold; @@ -102,7 +102,7 @@ body { .navbar .auth-buttons .signup-btn { background-color: #28a745; width: 100px; - text-align-all: center; + text-align: center; margin-bottom: 5px; color: white; font-weight: bold; @@ -110,7 +110,7 @@ body { .navbar .auth-buttons .subscribe-btn { background-color: #d68418; width: 100px; - text-align-all: center; + text-align: center; margin-bottom: 5px; color: white; font-weight: bold; diff --git a/src/main/resources/static/css/rankingTable.css b/src/main/resources/static/css/rankingTable.css new file mode 100644 index 0000000000000000000000000000000000000000..f116967ca25363ea131a73e83416651cbaa0d185 --- /dev/null +++ b/src/main/resources/static/css/rankingTable.css @@ -0,0 +1,117 @@ +/* 全局样式重置 */ +body { + font-family: Arial, sans-serif; + background-color: #f8f9fa; + margin: 0; + padding: 0; + box-sizing: border-box; + color: #333; +} + +/* 滤镜部分 */ +.filter-container { + text-align: center; + margin: 20px 0; + font-size: 14px; +} + +.filter-container label { + margin: 0 10px; + font-weight: bold; + color: #555; +} + +.filter-container select { + padding: 8px 12px; + border-radius: 25px; + border: 2px solid #ff758c; + outline: none; + font-size: 14px; + cursor: pointer; + transition: all 0.3s ease; + background: white; +} + +.filter-container select:hover { + background: #ff758c; + color: white; +} + +/* 表格容器样式 */ +.table-container { + width: 90%; + margin: 20px auto; + text-align: center; /* 子元素居中 */ +} + +/* 表格标题 */ +.table-title { + font-size: 24px; + font-weight: bold; + color: #333; + margin-bottom: 10px; + text-transform: uppercase; + letter-spacing: 1px; +} + +/* 表格样式 */ +table { + width: 100%; + border-collapse: collapse; + margin: 0 auto; + background: #2d2d2d; + color: #f9f9f9; + border-radius: 10px; + overflow: hidden; + box-shadow: 0 8px 15px rgba(0, 0, 0, 0.2); + transition: all 0.3s ease; +} + +thead { + background: linear-gradient(90deg, #ff7eb3, #ff758c); + color: white; + font-weight: bold; +} + +thead th { + padding: 12px 15px; + text-align: left; +} + +tbody tr:nth-child(odd) { + background: rgba(255, 255, 255, 0.05); +} + +tbody tr:nth-child(even) { + background: rgba(0, 0, 0, 0.05); +} + +tbody tr:hover { + background: #ff7eb3; + color: white; + cursor: pointer; + transform: scale(1.02); + transition: all 0.2s ease-in-out; +} + +tbody td { + padding: 10px 15px; + text-align: left; +} + +/* 动画效果 */ +.fade-in { + animation: fadeIn 0.5s ease-in-out forwards; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + diff --git a/src/main/resources/static/html/backGroundMatch.html b/src/main/resources/static/html/backGroundMatch.html new file mode 100644 index 0000000000000000000000000000000000000000..68434033b4d832d1f3feaa880a7a436630a0def4 --- /dev/null +++ b/src/main/resources/static/html/backGroundMatch.html @@ -0,0 +1,61 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Match Management</title> + <link rel="stylesheet" href="/css/backGroundMatch.css"> +</head> +<body> + <h1>Match Management</h1> + <h2>Match List</h2> + <table id="matchTable"> + <thead> + <tr> + <th>ID</th> + <th>Sport</th> + <th>Player A ID</th> + <th>Player B ID</th> + <th>Plan Time</th> + <th>Status</th> + <th>Score A</th> + <th>Score B</th> + <th>Confirm A</th> + <th>Confirm B</th> + <th>Winner ID</th> + </tr> + </thead> + <tbody> + <!-- matches show threr ++ --> + </tbody> + </table> + <hr> + <div id="addMatchForm"> + <h2>Add Match</h2> + <form id="matchForm"> + <label for="sport">Sport: </label> + <input type="text" id="sport" name="sport" required><br> + <label for="playerAId">Player A ID: </label> + <input type="number" id="playerAId" name="playerAId" required><br> + <label for="playerBId">Player B ID: </label> + <input type="number" id="playerBId" name="playerBId" required><br> + <label for="planTime">Plan Time: </label> + <input type="datetime-local" id="planTime" name="planTime" required><br> + <label for="status">Status: </label> + <input type="text" id="status" name="status" required><br> + <label for="scoreA">Score A: </label> + <input type="number" id="scoreA" name="scoreA" required><br> + <label for="scoreB">Score B: </label> + <input type="number" id="scoreB" name="scoreB" required><br> + <label for="confirmByA">Confirm by A: </label> + <input type="checkbox" id="confirmByA" name="confirmByA"><br> + <label for="confirmByB">Confirm by B: </label> + <input type="checkbox" id="confirmByB" name="confirmByB"><br> + <label for="winnerId">Winner ID: </label> + <input type="number" id="winnerId" name="winnerId"><br> + <button type="button" onclick="addMatch()">Add Match</button> + </form> + </div> + <script src="/js/backGroundMatch.js"></script> +</body> +</html> diff --git a/src/main/resources/static/html/matchDetail.html b/src/main/resources/static/html/matchDetail.html index 504dfd3ce4e338089fa40908e5e6c84ddf699fa1..5b992d0ed09f8b69d18433b9de41c0159d482c6b 100644 --- a/src/main/resources/static/html/matchDetail.html +++ b/src/main/resources/static/html/matchDetail.html @@ -3,100 +3,7 @@ <head> <meta charset="UTF-8"> <title>Match Detail</title> - <style> - * { - margin: 0; - padding: 0; - box-sizing: border-box; - } - - body { - font-family: Arial, sans-serif; - background-color: #f4f4f4; - color: #333; - } - .title-h1{ - h1 { - background-color: #4a4a4a; - p { - font-size: 36px; - color: white; - - font-weight: bold; - text-align: center; - } - - } - } - .container { - width: 80%; - margin: 20px auto; - } - - header { - text-align: center; - margin-bottom: 30px; - } - #matchDetails { - margin-left: 45px; - align-items: center; - background-color: #fff; - border-radius: 12px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - width: 100%; - max-width: 900px; - padding: 30px; - box-sizing: border-box; - margin-top: 30px; - } - - h2 { - text-align: center; - font-size: 2.5em; - color: #333; - margin-bottom: 20px; - } - - #matchScore { - font-size: 2.4em; - line-height: 1.6; - color: #444; - margin: 20px 0; - } - - #matchScore p { - text-align: center; - } - - #matchInfo { - font-size: 1.2em; - line-height: 1.6; - color: #444; - } - - #matchInfo p { - margin: 15px 0; - padding: 10px; - background-color: #f9f9f9; - border-radius: 6px; - } - - strong { - color: #2d2d2d; - } - /*adjust for phone*/ - @media screen and (max-width: 768px) { - .container { - width: 95%; - } - - .match-item { - flex-direction: column; - align-items: flex-start; - } - } - - </style> + <link rel="stylesheet" href="/css/matchDetail.css"> <link rel="stylesheet" href="/css/navBar.css"> <link rel="stylesheet" href="/css/footer.css"> </head> @@ -123,33 +30,5 @@ loadFooter('footer-container'); </script> </body> -<script> - const urlParams = new URLSearchParams(window.location.search); - const matchId = urlParams.get(`id`); - // 获取比赛详情 - async function fetchMatchDetails(id) { - try { - const response = await fetch(`/match/${id}`); - const matchData = await response.json(); - - const matchInfo = document.getElementById('matchInfo'); - const matchScore = document.getElementById('matchScore'); - matchScore.innerHTML = `<p><strong></strong>${matchData.scoreA} - ${matchData.scoreB}</p> -` - matchInfo.innerHTML = ` - <p><strong>Sport: </strong>${matchData.sport}</p> - <p><strong>Players: </strong>${matchData.playerAId} vs ${matchData.playerBId}</p> - <p><strong>Scheduled Time: </strong>${new Date(matchData.planTime).toLocaleString()}</p> - <p><strong>Status: </strong>${matchData.status}</p> - <p><strong>Winner: </strong>${matchData.winnerId ? matchData.winnerId : 'N/A'}</p> - `; - } catch (error) { - console.error('Error fetching match details:', error); - } - } - - // 加载比赛详情 - fetchMatchDetails(matchId); -</script> - +<script src="/js/matchDetail.js"></script> </html> \ No newline at end of file diff --git a/src/main/resources/static/html/matchSchedule.html b/src/main/resources/static/html/matchSchedule.html index 2ee5d891cc708d532beb131f431090d7e3620bb9..884a117f7dbd436c7aa1778c3730cb8e66e65d44 100644 --- a/src/main/resources/static/html/matchSchedule.html +++ b/src/main/resources/static/html/matchSchedule.html @@ -17,7 +17,6 @@ <div class="container"> <!-- <h1>Sports League Schedule</h1>--> - <!-- Filter --> <div class="filter-panel"> <label for="sportSelect">Filter by Sport:</label> @@ -38,7 +37,7 @@ <!-- time sort button --> <button id="sortTimeBtn">Sort by Time</button> </div> - <!-- 搜索框 --> + <!-- search --> <input type="text" id="searchInput" placeholder="Search matches id or player id" /> <!-- Matches List --> <div id="matchesList" class="matches-list"> diff --git a/src/main/resources/static/html/navBar.html b/src/main/resources/static/html/navBar.html index 061922a6868a12548c21868511595645c181a042..fa6157844d83f9834b3f8cb2e2cfae1b8a22d941 100644 --- a/src/main/resources/static/html/navBar.html +++ b/src/main/resources/static/html/navBar.html @@ -9,7 +9,7 @@ <li><a href="#">Rules</a></li> <li><a href="#">Players</a></li> <li><a href="http://localhost:8080/html/matchSchedule.html">Matches</a></li> - <li><a href="#">Rankings</a></li> + <li><a href="http://localhost:8080/html/ranking.html">Rankings</a></li> </ul> <!-- put login、 signup subscribe links here--> <div class="auth-buttons"> diff --git a/src/main/resources/static/html/ranking.html b/src/main/resources/static/html/ranking.html new file mode 100644 index 0000000000000000000000000000000000000000..c9ff9753da94074d46005e75bb706310c07c6d9c --- /dev/null +++ b/src/main/resources/static/html/ranking.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>Sports league rankings</title> + <link rel="stylesheet" href="/css/navBar.css"> + <link rel="stylesheet" href="/css/footer.css"> + <link rel="stylesheet" href="/css/rankingTable.css"> +</head> +<body> +<div id="navbar-container"></div> + +<div class="filter-container"> + <label for="filter-sport">Filter by Sport:</label> + <select id="filter-sport" onchange="loadRankings()"> + <option value="all">All</option> + <option value="TableTennis">Table Tennis</option> + <option value="Darts">Dart</option> + <option value="Pools">Pools</option> + </select> + + <label for="sort-option">Sort by:</label> + <select id="sort-option" onchange="loadRankings()"> + <option value="asc">Ascending</option> + <option value="desc">Descending</option> + </select> +</div> +<div class="table-container"> + <h1 class="table-title">Rankings</h1> + +<table> + <thead> + <tr> + <th>Ranking</th> + <th>Username</th> + <th>Wins</th> + <th>sport</th> + + </tr> + </thead> + <tbody id="ranking-table"> + <!-- Data will be dynamically inserted here --> + </tbody> +</table> +</div> + +<div id="footer-container"></div> +<script src="/js/navBar.js"></script> +<script src="/js/footer.js"></script> +<script> + loadNavbar('navbar-container'); + loadFooter('footer-container'); +</script> +<script src="/js/rankingTable.js"></script> +<script src="/js/matchSchedule.js"></script> +</body> +</html> \ No newline at end of file diff --git a/src/main/resources/static/js/backGroundMatch.js b/src/main/resources/static/js/backGroundMatch.js new file mode 100644 index 0000000000000000000000000000000000000000..7e5948c1a690fe7768d9b7c2957869dcdd0a61fb --- /dev/null +++ b/src/main/resources/static/js/backGroundMatch.js @@ -0,0 +1,183 @@ +const MATCHES_API_URL = '/match'; +// let matches = [] +window.onload = function() { + fetchMatchItems(); +}; +// // get all match items +// async function fetchMatch() { +// function displayMatch(match) { +// +// } +// +// try { +// const res = await fetch(MATCHES_API_URL); +// matches = await res.json(); +// displayMatch(matches); +// }catch(err){ +// console.error('error fetching match data', err) +// } +// } +// function displayMatch(matches) { +// // generate the match in tbody +// const tableBody = document.querySelector('#matchTable tbody'); +// // Clear previous rows +// tableBody.innerHTML = ''; +// // all match +// matches.forEach(match => { +// const row = document.createElement('tr'); +// row.innerHTML = ` +// <td>${match.id}</td> +// <td>${match.sport}</td> +// <td>${match.playerAId}</td> +// <td>${match.playerBId}</td> +// <td>${match.planTime}</td> +// <td>${match.status}</td> +// <td>${match.scoreA}</td> +// <td>${match.scoreB}</td> +// <td>${match.confirmByA}</td> +// <td>${match.confirmByB}</td> +// <td>${match.winnerId}</td> +// <td> +// <!-- edit button --> +// <button onclick="editMatch(${match.id})">Edit</button> +// <!-- delete button --> +// <button onclick="deleteMatch(${match.id})">Delete</button> +// </td> +// `; +// tableBody.appendChild(row); +// }); +// } + +// show all matches +function fetchMatchItems() { + fetch(MATCHES_API_URL) + .then(res => res.json()) + .then(data => { + // console.log(data) + // generate the match in tbody + const tableBody = document.querySelector('#matchTable tbody'); + // Clear previous rows + tableBody.innerHTML = ''; + // all match + data.forEach(match => { + const matchItem = document.createElement('tr'); + matchItem.innerHTML = ` + <td>${match.id}</td> + <td>${match.sport}</td> + <td>${match.playerAId}</td> + <td>${match.playerBId}</td> + <td>${match.planTime}</td> + <td>${match.status}</td> + <td>${match.scoreA}</td> + <td>${match.scoreB}</td> + <td>${match.confirmByA}</td> + <td>${match.confirmByB}</td> + <td>${match.winnerId}</td> + <td> + <!-- edit button --> + <button onclick="editMatch(${match.id})">Edit</button> + <!-- delete button --> + <button onclick="deleteMatch(${match.id})">Delete</button> + </td> + `; + tableBody.appendChild(matchItem); + }); + }) + .catch(err => console.error('error:', err)); +} + +// Add a new match +function addMatch() { + const matchForm = document.getElementById('matchForm'); + // formData object + const formData = new FormData(matchForm); + const matchData = {}; + + formData.forEach((value, key) => { + matchData[key] = value; + }); + fetch(MATCHES_API_URL, { + method: 'POST', + // for post must set headers for json + headers: { + 'Content-Type': 'application/json', + }, + // json => string + body: JSON.stringify(matchData), + }) + .then(res => res.json()) + .then(() => { + alert('Match added successfully!'); + // refresh form + fetchMatchItems(); + matchForm.reset(); + }) + .catch(err => console.error('error when add match:', err)); +} + +// delete a match by id +function deleteMatch(id) { + fetch(`/match/${id}`, { + method: 'DELETE', + }) + .then(res => res.json()) + .then(() => { + alert('match deleted successfully!'); + fetchMatchItems(); // Refresh match list + }) + .catch(err => console.error('error when delete match:', err)); +} + +// edit a match and show it in the same palce as add +function editMatch(id) { + fetch(`/match/${id}`) + .then(res => res.json()) + .then(match => { + document.getElementById('sport').value = match.sport; + document.getElementById('playerAId').value = match.playerAId; + document.getElementById('playerBId').value = match.playerBId; + document.getElementById('planTime').value = match.planTime; + document.getElementById('status').value = match.status; + document.getElementById('scoreA').value = match.scoreA; + document.getElementById('scoreB').value = match.scoreB; + document.getElementById('confirmByA').checked = match.confirmByA; + document.getElementById('confirmByB').checked = match.confirmByB; + document.getElementById('winnerId').value = match.winnerId; + // change the add button to an update button + const addButton = document.querySelector('#matchForm button'); + addButton.textContent = 'Update Match'; + // update an existing match + addButton.setAttribute('onclick', `updateMatch(${id})`); + }) + .catch(error => console.error('error when edit match:', error)); +} + +// update an existing match +function updateMatch(id) { + const matchForm = document.getElementById('matchForm'); + const formData = new FormData(matchForm); + const matchData = {}; + + formData.forEach((value, key) => { + matchData[key] = value; + }); + + fetch(`/match/${id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(matchData), + }) + .then(res => res.json()) + .then(() => { + alert('match updated successfully!'); + fetchMatchItems(); + matchForm.reset(); + // change the Update button back to Add button + const addButton = document.querySelector('#matchForm button'); + addButton.textContent = 'Add Match'; + addButton.setAttribute('onclick', 'addMatch()'); + }) + .catch(err => console.error('error update match:', err)); +} diff --git a/src/main/resources/static/js/matchDetail.js b/src/main/resources/static/js/matchDetail.js new file mode 100644 index 0000000000000000000000000000000000000000..85ae5c2c7fd346128501164f943e6b6cc2029765 --- /dev/null +++ b/src/main/resources/static/js/matchDetail.js @@ -0,0 +1,24 @@ + const urlParams = new URLSearchParams(window.location.search); + const matchId = urlParams.get(`id`); + // 获取比赛详情 + async function fetchMatchDetails(id) { + try { + const response = await fetch(`/match/${id}`); + const matchData = await response.json(); + + const matchInfo = document.getElementById('matchInfo'); + const matchScore = document.getElementById('matchScore'); + matchScore.innerHTML = `<p><strong></strong>${matchData.scoreA} - ${matchData.scoreB}</p> +` + matchInfo.innerHTML = ` + <p><strong>Sport: </strong>${matchData.sport}</p> + <p><strong>Players: </strong>${matchData.playerAId} vs ${matchData.playerBId}</p> + <p><strong>Scheduled Time: </strong>${new Date(matchData.planTime).toLocaleString()}</p> + <p><strong>Status: </strong>${matchData.status}</p> + <p><strong>Winner: </strong>${matchData.winnerId ? matchData.winnerId : 'N/A'}</p> + `; +} catch (error) { + console.error('Error fetching match details:', error); +} +} + fetchMatchDetails(matchId); \ No newline at end of file diff --git a/src/main/resources/static/js/rankingTable.js b/src/main/resources/static/js/rankingTable.js new file mode 100644 index 0000000000000000000000000000000000000000..4fc73715d56666ebf495744b70e9ecc44d899a4f --- /dev/null +++ b/src/main/resources/static/js/rankingTable.js @@ -0,0 +1,55 @@ +// Function to add fade-in animation +function applyFadeIn() { + const tableBody = document.getElementById('ranking-table'); + tableBody.classList.add('fade-in'); // Add animation class + setTimeout(() => tableBody.classList.remove('fade-in'), 500); // Remove class after animation +} + +async function loadRankings() { + const sport = document.getElementById('filter-sport').value; + const sortOption = document.getElementById('sort-option').value; + + let apiUrl; + + if (sport === "all") { + apiUrl = `/rankings/byallandsort?order=${sortOption}`; + } else { + apiUrl = `/rankings/bysportandsort?sport=${sport}&order=${sortOption}`; + } + try { + const response = await fetch(apiUrl); + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const rankings = await response.json(); + + const tableBody = document.getElementById('ranking-table'); + tableBody.innerHTML = ''; // Clear current table content + + // Add rows to the table + rankings.forEach((ranking, index) => { + const row = document.createElement('tr'); + row.innerHTML = ` + <td>${index + 1}</td> + <td>${ranking.username || 'N/A'}</td> + <td>${ranking.wins || 0}</td> + <td>${ranking.sport || 'All'}</td> + `; + tableBody.appendChild(row); + }); + + // Apply fade-in effect + applyFadeIn(); + } catch (error) { + console.error('Error fetching rankings:', error); + } +} + +// Add event listeners +document.getElementById('filter-sport').addEventListener('change', loadRankings); +document.getElementById('sort-option').addEventListener('change', loadRankings); + +window.onload = loadRankings; + + diff --git a/src/main/resources/templates/ranking/ranking.html b/src/main/resources/templates/ranking/ranking.html new file mode 100644 index 0000000000000000000000000000000000000000..566549bdf8fae810809c1a81066000687cb338f6 --- /dev/null +++ b/src/main/resources/templates/ranking/ranking.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>Title</title> +</head> +<body> + +</body> +</html> \ No newline at end of file