From df41720b507013cf0ebd8a2fcd93c8b997cec233 Mon Sep 17 00:00:00 2001 From: Torsten Schulz Date: Mon, 24 Feb 2025 16:21:43 +0100 Subject: [PATCH] started tournament implementation --- backend/controllers/tournamentController.js | 133 +++++++++ backend/models/Tournament.js | 37 +++ backend/models/TournamentGroup.js | 21 ++ backend/models/TournamentMatch.js | 31 +++ backend/models/TournamentMember.js | 26 ++ backend/models/TournamentResult.js | 24 ++ backend/models/index.js | 47 ++++ backend/routes/tournamentRoutes.js | 31 +++ backend/server.js | 14 +- backend/services/tournamentService.js | 249 +++++++++++++++++ frontend/src/App.vue | 1 + frontend/src/router.js | 2 + frontend/src/views/DiaryView.vue | 16 +- frontend/src/views/TournamentsView.vue | 289 ++++++++++++++++++++ 14 files changed, 906 insertions(+), 15 deletions(-) create mode 100644 backend/controllers/tournamentController.js create mode 100644 backend/models/Tournament.js create mode 100644 backend/models/TournamentGroup.js create mode 100644 backend/models/TournamentMatch.js create mode 100644 backend/models/TournamentMember.js create mode 100644 backend/models/TournamentResult.js create mode 100644 backend/routes/tournamentRoutes.js create mode 100644 backend/services/tournamentService.js create mode 100644 frontend/src/views/TournamentsView.vue diff --git a/backend/controllers/tournamentController.js b/backend/controllers/tournamentController.js new file mode 100644 index 0000000..91755d7 --- /dev/null +++ b/backend/controllers/tournamentController.js @@ -0,0 +1,133 @@ +import tournamentService from "../services/tournamentService.js"; + +export const getTournaments = async (req, res) => { + const { authcode: token } = req.headers; + const clubId = req.params.clubId; + try { + const tournaments = await tournamentService.getTournaments(token, clubId); + res.status(200).json(tournaments); + } catch (error) { + console.log(error); + res.status(500).json({ error: error.message }); + } +}; + +export const addTournament = async (req, res) => { + const { authcode: token } = req.headers; + const { clubId, tournamentName, date } = req.body; + try { + const tournament = await tournamentService.addTournament(token, clubId, tournamentName, date); + res.status(200).json(tournament); + } catch (error) { + console.log(error); + res.status(500).json({ error: error.message }); + } +} + +export const addParticipant = async (req, res) => { + const { authcode: token } = req.headers; + const { clubId, tournamentId, participant: participantId } = req.body; + try { + await tournamentService.addParticipant(token, clubId, tournamentId, participantId); + const participants = await tournamentService.getParticipants(token, clubId, tournamentId); + res.status(200).json(participants); + } catch (error) { + console.log(error); + res.status(500).json({ error: error.message }); + } +} + +export const getParticipants = async (req, res) => { + const { authcode: token } = req.headers; + const { clubId, tournamentId } = req.body; + try { + const participants = await tournamentService.getParticipants(token, clubId, tournamentId); + res.status(200).json(participants); + } catch (error) { + console.log(error); + res.status(500).json({ error: error.message }); + } +} + +export const setModus = async (req, res) => { + const { authcode: token } = req.headers; + const { clubId, tournamentId, type, numberOfGroups } = req.body; + try { + await tournamentService.setModus(token, clubId, tournamentId, type, numberOfGroups); + } catch (error) { + console.log(error); + res.status(500).json({ error: error.message }); + } +} + +export const createGroups = async (req, res) => { + const { authcode: token } = req.headers; + const { clubId, tournamentId } = req.body; + try { + await tournamentService.createGroups(token, clubId, tournamentId); + } catch (error) { + console.log(error); + res.status(500).json({ error: error.message }); + } +} + +export const fillGroups = async (req, res) => { + const { authcode: token } = req.headers; + const { clubId, tournamentId } = req.body; + try { + const updatedMembers = await tournamentService.fillGroups(token, clubId, tournamentId); + res.status(200).json(updatedMembers); + } catch (error) { + console.log(error); + res.status(500).json({ error: error.message }); + } +} + +export const getGroups = async (req, res) => { + const { authcode: token } = req.headers; + const { clubId, tournamentId } = req.query; + try { + const groups = await tournamentService.getGroupsWithParticipants(token, clubId, tournamentId); + res.status(200).json(groups); + } catch (error) { + console.log(error); + res.status(500).json({ error: error.message }); + } +}; + +export const getTournament = async (req, res) => { + const { authcode: token } = req.headers; + const { clubId, tournamentId } = req.params; + try { + const tournament = await tournamentService.getTournament(token, clubId, tournamentId); + res.status(200).json(tournament); + } catch (error) { + console.log(error); + res.status(500).json({ error: error.message }); + } +} + +export const getTournamentMatches = async (req, res) => { + const { authcode: token } = req.headers; + const { clubId, tournamentId } = req.params; + try { + const matches = await tournamentService.getTournamentMatches(token, clubId, tournamentId); + res.status(200).json(matches); + } catch (error) { + console.log(error); + res.status(500).json({ error: error.message }); + } +} + +export const addMatchResult = async (req, res) => { + const { authcode: token } = req.headers; + const { clubId, tournamentId, matchId, result } = req.body; + + try { + await tournamentService.addMatchResult(token, clubId, tournamentId, matchId, result); + res.status(200).json({ message: "Result added successfully" }); + } catch (error) { + console.error(error); + res.status(500).json({ error: error.message }); + } +}; diff --git a/backend/models/Tournament.js b/backend/models/Tournament.js new file mode 100644 index 0000000..f93c286 --- /dev/null +++ b/backend/models/Tournament.js @@ -0,0 +1,37 @@ +import { DataTypes } from 'sequelize'; +import sequelize from '../database.js'; + +const Tournament = sequelize.define('Tournament', { + name: { + type: DataTypes.STRING, + allowNull: false, + }, + date: { + type: DataTypes.DATEONLY, + allowNull: false, + }, + type: { + type: DataTypes.STRING, + allowNull: false, + }, + bestOfEndroundSize: { + type: DataTypes.INTEGER, + allowNull: false, + }, + numberOfGroups: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: 0, + }, + clubId: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 1 + } +}, { + underscored: true, + tableName: 'tournament', + timestamps: true, +}); + +export default Tournament; diff --git a/backend/models/TournamentGroup.js b/backend/models/TournamentGroup.js new file mode 100644 index 0000000..f095e77 --- /dev/null +++ b/backend/models/TournamentGroup.js @@ -0,0 +1,21 @@ +import { DataTypes } from 'sequelize'; +import sequelize from '../database.js'; + +const TournamentGroup = sequelize.define('TournamentGroup', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false + }, + tournamentId : { + type: DataTypes.INTEGER, + allowNull: false + }, + }, { + underscored: true, + tableName: 'tournament_group', + timestamps: true, +}); + +export default TournamentGroup; diff --git a/backend/models/TournamentMatch.js b/backend/models/TournamentMatch.js new file mode 100644 index 0000000..d05f6a3 --- /dev/null +++ b/backend/models/TournamentMatch.js @@ -0,0 +1,31 @@ +import { DataTypes } from 'sequelize'; +import sequelize from '../database.js'; + +const TournamentMatch = sequelize.define('TournamentMatch', { + tournamentId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + groupId: { + type: DataTypes.INTEGER, + allowNull: true, + }, + round: { + type: DataTypes.STRING, + allowNull: false, + }, + player1Id: { + type: DataTypes.INTEGER, + allowNull: false, + }, + player2Id: { + type: DataTypes.INTEGER, + allowNull: false, + }, +}, { + underscored: true, + tableName: 'tournament_match', + timestamps: true, +}); + +export default TournamentMatch; diff --git a/backend/models/TournamentMember.js b/backend/models/TournamentMember.js new file mode 100644 index 0000000..528a53d --- /dev/null +++ b/backend/models/TournamentMember.js @@ -0,0 +1,26 @@ +import { DataTypes } from 'sequelize'; +import sequelize from '../database.js'; + +const TournamentMember = sequelize.define('TournamentMember', { + tournamentId: { + type: DataTypes.INTEGER, + autoIncrement: false, + allowNull: true + }, + groupId : { + type: DataTypes.INTEGER, + autoIncrement: false, + allowNull: true + }, + clubMemberId: { + type: DataTypes.INTEGER, + autoIncrement: false, + allowNull: false + } + }, { + underscored: true, + tableName: 'tournament_member', + timestamps: true, +}); + +export default TournamentMember; diff --git a/backend/models/TournamentResult.js b/backend/models/TournamentResult.js new file mode 100644 index 0000000..82ed514 --- /dev/null +++ b/backend/models/TournamentResult.js @@ -0,0 +1,24 @@ +import { DataTypes } from 'sequelize'; +import sequelize from '../database.js'; + +const TournamentResult = sequelize.define('TournamentResult', { + matchId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + set: { + type: DataTypes.INTEGER, + }, + pointsPlayer1: { + type: DataTypes.INTEGER, + }, + pointsPlayer2: { + type: DataTypes.INTEGER, + }, + }, { + underscored: true, + tableName: 'tournament_result', + timestamps: true, +}); + +export default TournamentResult; diff --git a/backend/models/index.js b/backend/models/index.js index 2d3486c..d7533a1 100644 --- a/backend/models/index.js +++ b/backend/models/index.js @@ -21,6 +21,11 @@ import Season from './Season.js'; import Location from './Location.js'; import Group from './Group.js'; import GroupActivity from './GroupActivity.js'; +import Tournament from './Tournament.js'; +import TournamentGroup from './TournamentGroup.js'; +import TournamentMember from './TournamentMember.js'; +import TournamentMatch from './TournamentMatch.js'; +import TournamentResult from './TournamentResult.js'; User.hasMany(Log, { foreignKey: 'userId' }); Log.belongsTo(User, { foreignKey: 'userId' }); @@ -114,6 +119,43 @@ DiaryDateTag.belongsTo(DiaryTag, { foreignKey: 'tagId', as: 'tag' }); DiaryMemberTag.belongsTo(DiaryDate, { foreignKey: 'diaryDateId', as: 'diaryDates' }); DiaryDate.hasMany(DiaryMemberTag, { foreignKey: 'diaryDateId', as: 'diaryMemberTags' }); +Tournament.belongsTo(Club, { foreignKey: 'clubId', as: 'tournamentclub' }); +Club.hasMany(Tournament, { foreignKey: 'clubId', as: 'tournaments' }); + +TournamentGroup.belongsTo(Tournament, { foreignKey: 'tournamentId', as: 'tournaments' }); +Tournament.hasMany(TournamentGroup, { foreignKey: 'tournamentId', as: 'tournamentGroups' }); + +TournamentMember.belongsTo(TournamentGroup, { + foreignKey: 'groupId', + targetKey: 'id', + as: 'group', + constraints: false +}); +TournamentGroup.hasMany(TournamentMember, { + foreignKey: 'groupId', + as: 'tournamentGroupMembers' +}); + +TournamentMember.belongsTo(Member, { foreignKey: 'clubMemberId', as: 'member' }); +Member.hasMany(TournamentMember, { foreignKey: 'clubMemberId', as: 'tournamentGroupMembers' }); + +TournamentMember.belongsTo(Tournament, { foreignKey: 'tournamentId', as: 'tournament' }); +Tournament.hasMany(TournamentMember, { foreignKey: 'tournamentId', as: 'tournamentMembers' }); + +TournamentMatch.belongsTo(Tournament, { foreignKey: 'tournamentId', as: 'tournament' }); +Tournament.hasMany(TournamentMatch, { foreignKey: 'tournamentId', as: 'tournamentMatches' }); + +TournamentMatch.belongsTo(TournamentGroup, { foreignKey: 'groupId', as: 'group' }); +TournamentGroup.hasMany(TournamentMatch, { foreignKey: 'groupId', as: 'tournamentMatches' }); + +TournamentResult.belongsTo(TournamentMatch, { foreignKey: 'matchId', as: 'match' }); +TournamentMatch.hasMany(TournamentResult, { foreignKey: 'matchId', as: 'tournamentResults' }); + +TournamentMatch.belongsTo(TournamentMember, { foreignKey: 'player1Id', as: 'player1' }); +TournamentMatch.belongsTo(TournamentMember, { foreignKey: 'player2Id', as: 'player2' }); +TournamentMember.hasMany(TournamentMatch, { foreignKey: 'player1Id', as: 'player1Matches' }); +TournamentMember.hasMany(TournamentMatch, { foreignKey: 'player2Id', as: 'player2Matches' }); + export { User, Log, @@ -137,4 +179,9 @@ export { Team, Group, GroupActivity, + Tournament, + TournamentGroup, + TournamentMember, + TournamentMatch, + TournamentResult, }; diff --git a/backend/routes/tournamentRoutes.js b/backend/routes/tournamentRoutes.js new file mode 100644 index 0000000..3c9799a --- /dev/null +++ b/backend/routes/tournamentRoutes.js @@ -0,0 +1,31 @@ +import express from 'express'; +import { + getTournaments, + addTournament, + addParticipant, + getParticipants, + setModus, + createGroups, + fillGroups, + getGroups, + getTournament, + getTournamentMatches, + addMatchResult, +} from '../controllers/tournamentController.js'; +import { authenticate } from '../middleware/authMiddleware.js'; + +const router = express.Router(); + +router.post('/participant', authenticate, addParticipant); +router.post('/participants', authenticate, getParticipants); +router.post('/modus', authenticate, setModus); +router.put('/groups', authenticate, createGroups); +router.post('/groups', authenticate, fillGroups); +router.get('/groups', authenticate, getGroups); +router.post('/match/result', authenticate, addMatchResult); +router.get('/matches/:clubId/:tournamentId', authenticate, getTournamentMatches); +router.get('/:clubId/:tournamentId', authenticate, getTournament); +router.get('/:clubId', authenticate, getTournaments); +router.post('/', authenticate, addTournament); + +export default router; diff --git a/backend/server.js b/backend/server.js index 46b1825..4fd1bb3 100644 --- a/backend/server.js +++ b/backend/server.js @@ -7,7 +7,12 @@ import { User, Log, Club, UserClub, Member, DiaryDate, Participant, Activity, MemberNote, DiaryNote, DiaryTag, MemberDiaryTag, DiaryDateTag, DiaryMemberNote, DiaryMemberTag, PredefinedActivity, DiaryDateActivity, Match, League, Team, Group, - GroupActivity + GroupActivity, + Tournament, + TournamentGroup, + TournamentMatch, + TournamentResult, + TournamentMember } from './models/index.js'; import authRoutes from './routes/authRoutes.js'; import clubRoutes from './routes/clubRoutes.js'; @@ -27,6 +32,7 @@ import Location from './models/Location.js'; import groupRoutes from './routes/groupRoutes.js'; import diaryDateTagRoutes from './routes/diaryDateTagRoutes.js'; import sessionRoutes from './routes/sessionRoutes.js'; +import tournamentRoutes from './routes/tournamentRoutes.js'; const app = express(); const port = process.env.PORT || 3000; @@ -53,6 +59,7 @@ app.use('/api/matches', matchRoutes); app.use('/api/group', groupRoutes); app.use('/api/diarydatetags', diaryDateTagRoutes); app.use('/api/session', sessionRoutes); +app.use('/api/tournament', tournamentRoutes); app.use(express.static(path.join(__dirname, '../frontend/dist'))); @@ -88,6 +95,11 @@ app.get('*', (req, res) => { await Match.sync({ alter: true }); await Group.sync({ alter: true }); await GroupActivity.sync({ alter: true }); + await Tournament.sync({ alter: true }); + await TournamentGroup.sync({ alter: true }); + await TournamentMember.sync({ alter: true }); + await TournamentMatch.sync({ alter: true }); + await TournamentResult.sync({ alter: true }); app.listen(port, () => { console.log(`Server is running on http://localhost:${port}`); diff --git a/backend/services/tournamentService.js b/backend/services/tournamentService.js new file mode 100644 index 0000000..978e341 --- /dev/null +++ b/backend/services/tournamentService.js @@ -0,0 +1,249 @@ +import Club from "../models/Club.js"; +import Member from "../models/Member.js"; +import Tournament from "../models/Tournament.js"; +import TournamentGroup from "../models/TournamentGroup.js"; +import TournamentMatch from "../models/TournamentMatch.js"; +import TournamentMember from "../models/TournamentMember.js"; +import { checkAccess } from '../utils/userUtils.js'; + +class TournamentService { + async getTournaments(userToken, clubId) { + await checkAccess(userToken, clubId); + const tournaments = await Tournament.findAll( + { + where: { clubId }, + order: [['date', 'DESC']], + attributes: ['id', 'name', 'date'] + } + ); + return JSON.parse(JSON.stringify(tournaments)); + } + + async addTournament(userToken, clubId, tournamentName, date) { + await checkAccess(userToken, clubId); + const club = await Club.findByPk(clubId); + await Tournament.create({ + name: tournamentName, + date: date, + clubId: club.id, + bestOfEndroundSize: 0, + type: '', + name: '', + }); + return await this.getTournaments(userToken, clubId); + } + + async addParticipant(token, clubId, tournamentId, participantId) { + await checkAccess(token, clubId); + const tournament = await Tournament.findByPk(tournamentId); + if (!tournament || tournament.clubId != clubId) { + throw new Error('Tournament not found'); + } + const participant = TournamentMember.findAll({ + where: { tournamentId: tournamentId, groupId: participantId, clubMemberId: participantId }, + }); + if (participant) { + throw new Error('Participant already exists'); + } + await TournamentMember.create({ + tournamentId: tournamentId, + groupId: participantId, + clubMemberId: participantId, + }); + } + + async getParticipants(token, clubId, tournamentId) { + await checkAccess(token, clubId); + const tournament = await Tournament.findByPk(tournamentId); + if (!tournament || tournament.clubId != clubId) { + throw new Error('Tournament not found'); + } + return await TournamentMember.findAll({ + where: { + tournamentId: tournamentId, + }, + include: [ + { + model: Member, + as: 'member', + attributes: ['id', 'lastName', 'firstName'], + order: [['firstName', 'ASC'], ['lastName', 'ASC']], + } + ] + }); + } + + async setModus(token, clubId, tournamentId, type, numberOfGroups) { + await checkAccess(token, clubId); + const tournament = await Tournament.findByPk(tournamentId); + if (!tournament || tournament.clubId != clubId) { + throw new Error('Tournament not found'); + } + await tournament.update({ type, numberOfGroups }); + } + + async createGroups(token, clubId, tournamentId) { + await checkAccess(token, clubId); + const tournament = await Tournament.findByPk(tournamentId); + if (!tournament || tournament.clubId != clubId) { + throw new Error('Tournament not found'); + } + const existingGroups = await TournamentGroup.findAll({ where: { tournamentId } }); + const desiredGroupCount = tournament.numberOfGroups; + if (existingGroups.length < desiredGroupCount) { + const missingGroups = desiredGroupCount - existingGroups.length; + for (let i = 0; i < missingGroups; i++) { + await TournamentGroup.create({ tournamentId }); + } + } else if (existingGroups.length > desiredGroupCount) { + existingGroups.sort((a, b) => a.id - b.id); + const groupsToRemove = existingGroups.slice(desiredGroupCount); + for (const group of groupsToRemove) { + await group.destroy(); + } + } + } + + async fillGroups(token, clubId, tournamentId) { + await checkAccess(token, clubId); + const tournament = await Tournament.findByPk(tournamentId); + if (!tournament || tournament.clubId != clubId) { + throw new Error('Tournament not found'); + } + const groups = await TournamentGroup.findAll({ where: { tournamentId } }); + if (!groups || groups.length === 0) { + throw new Error('No groups available. Please create groups first.'); + } + const members = await TournamentMember.findAll({ where: { tournamentId } }); + if (!members || members.length === 0) { + throw new Error('No tournament members found.'); + } + await TournamentMatch.destroy({ where: { tournamentId } }); + const shuffledMembers = [...members]; + for (let i = shuffledMembers.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [shuffledMembers[i], shuffledMembers[j]] = [shuffledMembers[j], shuffledMembers[i]]; + } + const numberOfGroups = groups.length; + for (let i = 0; i < shuffledMembers.length; i++) { + const groupAssignment = groups[i % numberOfGroups].id; + await shuffledMembers[i].update({ groupId: groupAssignment }); + } + for (const group of groups) { + const groupMembers = await TournamentMember.findAll({ where: { groupId: group.id } }); + for (let i = 0; i < groupMembers.length; i++) { + for (let j = i + 1; j < groupMembers.length; j++) { + await TournamentMatch.create({ + tournamentId: tournamentId, + groupId: group.id, + round: 'group', + player1Id: groupMembers[i].id, + player2Id: groupMembers[j].id, + }); + } + } + } + return await TournamentMember.findAll({ where: { tournamentId } }); + } + + async getGroups(token, clubId, tournamentId) { + await checkAccess(token, clubId); + const tournament = await Tournament.findByPk(tournamentId); + if (!tournament || tournament.clubId != clubId) { + throw new Error('Tournament not found'); + } + const groups = await TournamentGroup.findAll({ where: { tournamentId } }); + return groups; + } + + async getGroupsWithParticipants(token, clubId, tournamentId) { + await checkAccess(token, clubId); + const tournament = await Tournament.findByPk(tournamentId); + if (!tournament || tournament.clubId != clubId) { + throw new Error('Tournament not found'); + } + const groups = await TournamentGroup.findAll({ + where: { tournamentId }, + include: [ + { + model: TournamentMember, + as: 'tournamentGroupMembers', + required: false, + include: [ + { + model: Member, + as: 'member', + attributes: ['id', 'firstName', 'lastName'] + } + ] + } + ], + order: [['id', 'ASC']] + }); + return groups.map(group => ({ + groupId: group.id, + participants: group.tournamentGroupMembers.map(p => ({ + id: p.id, + name: `${p.member.firstName} ${p.member.lastName}` + })) + })); + } + + async getTournament(token, clubId, tournamentId) { + await checkAccess(token, clubId); + const tournament = await Tournament.findOne({ + where: { id: tournamentId, clubId }, + }); + if (!tournament) { + throw new Error('Tournament not found'); + } + return tournament; + } + + async getTournamentMatches(token, clubId, tournamentId) { + await checkAccess(token, clubId); + const tournament = await Tournament.findOne({ + where: { id: tournamentId, clubId }, + }); + if (!tournament) { + throw new Error('Tournament not found'); + } + const matches = await TournamentMatch.findAll({ + where: { tournamentId }, + include: [ + { + model: TournamentMember, + as: 'player1', + include: { + model: Member, + as: 'member', + } + }, + { + model: TournamentMember, + as: 'player2', + include: { + model: Member, + as: 'member', + } + } + ], + order: [['id', 'ASC']] + }); + return matches; + } + + async addMatchResult(token, clubId, tournamentId, matchId, result) { + await checkAccess(token, clubId); + + const match = await TournamentMatch.findByPk(matchId); + if (!match || match.tournamentId != tournamentId) { + throw new Error("Match not found or doesn't belong to this tournament"); + } + + match.result = result; + await match.save(); + } +} + +export default new TournamentService(); diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 6f6e907..5221c58 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -22,6 +22,7 @@ Tagebuch Freigaben Spielpläne + Turniere
diff --git a/frontend/src/router.js b/frontend/src/router.js index 955b0ff..f966c87 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -9,6 +9,7 @@ import MembersView from './views/MembersView.vue'; import DiaryView from './views/DiaryView.vue'; import PendingApprovalsView from './views/PendingApprovalsView.vue'; import ScheduleView from './views/ScheduleView.vue'; +import TournamentsView from './views/TournamentsView.vue'; const routes = [ { path: '/register', component: Register }, @@ -21,6 +22,7 @@ const routes = [ { path: '/diary', component: DiaryView }, { path: '/pending-approvals', component: PendingApprovalsView}, { path: '/schedule', component: ScheduleView}, + { path: '/tournaments', component: TournamentsView }, ]; const router = createRouter({ diff --git a/frontend/src/views/DiaryView.vue b/frontend/src/views/DiaryView.vue index 2fb055a..89103db 100644 --- a/frontend/src/views/DiaryView.vue +++ b/frontend/src/views/DiaryView.vue @@ -1004,23 +1004,11 @@ export default { }, playBellSound() { - this.bellSound.play() - .then(() => { - console.log("Bell sound played successfully"); - }) - .catch(error => { - console.error("Error playing bell sound:", error); - }); + this.bellSound.play(); }, playThumbSound() { - this.thumbSound.play() - .then(() => { - console.log("Thumb sound played successfully"); - }) - .catch(error => { - console.error("Error playing thumb sound:", error); - }); + this.thumbSound.play(); }, calculateIntermediateTimes() { diff --git a/frontend/src/views/TournamentsView.vue b/frontend/src/views/TournamentsView.vue new file mode 100644 index 0000000..e6d856d --- /dev/null +++ b/frontend/src/views/TournamentsView.vue @@ -0,0 +1,289 @@ + + + + + \ No newline at end of file