Erweitert die Backend- und Frontend-Funktionalität zur Unterstützung von Teams und Saisons. Fügt neue Routen für Team- und Club-Team-Management hinzu, aktualisiert die Match- und Team-Modelle zur Berücksichtigung von Saisons, und implementiert die Saison-Auswahl in der Benutzeroberfläche. Optimiert die Logik zur Abfrage von Ligen und Spielen basierend auf der ausgewählten Saison.
This commit is contained in:
137
backend/controllers/clubTeamController.js
Normal file
137
backend/controllers/clubTeamController.js
Normal file
@@ -0,0 +1,137 @@
|
||||
import ClubTeamService from '../services/clubTeamService.js';
|
||||
import { getUserByToken } from '../utils/userUtils.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
export const getClubTeams = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubid: clubId } = req.params;
|
||||
const { seasonid: seasonId } = req.query;
|
||||
|
||||
devLog('[getClubTeams] - Getting club teams for club:', clubId, 'season:', seasonId);
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
// Check if user has access to this club
|
||||
const clubTeams = await ClubTeamService.getAllClubTeamsByClub(clubId, seasonId);
|
||||
devLog('[getClubTeams] - Found club teams:', clubTeams.length);
|
||||
|
||||
res.status(200).json(clubTeams);
|
||||
} catch (error) {
|
||||
console.error('[getClubTeams] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const getClubTeam = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubteamid: clubTeamId } = req.params;
|
||||
devLog('[getClubTeam] - Getting club team:', clubTeamId);
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const clubTeam = await ClubTeamService.getClubTeamById(clubTeamId);
|
||||
if (!clubTeam) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
res.status(200).json(clubTeam);
|
||||
} catch (error) {
|
||||
console.error('[getClubTeam] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const createClubTeam = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubid: clubId } = req.params;
|
||||
const { name, leagueId, seasonId } = req.body;
|
||||
|
||||
devLog('[createClubTeam] - Creating club team:', { name, clubId, leagueId, seasonId });
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
if (!name) {
|
||||
return res.status(400).json({ error: "missingname" });
|
||||
}
|
||||
|
||||
const clubTeamData = {
|
||||
name,
|
||||
clubId: parseInt(clubId),
|
||||
leagueId: leagueId ? parseInt(leagueId) : null,
|
||||
seasonId: seasonId ? parseInt(seasonId) : null
|
||||
};
|
||||
|
||||
const newClubTeam = await ClubTeamService.createClubTeam(clubTeamData);
|
||||
devLog('[createClubTeam] - Club team created with ID:', newClubTeam.id);
|
||||
|
||||
res.status(201).json(newClubTeam);
|
||||
} catch (error) {
|
||||
console.error('[createClubTeam] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const updateClubTeam = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubteamid: clubTeamId } = req.params;
|
||||
const { name, leagueId, seasonId } = req.body;
|
||||
|
||||
devLog('[updateClubTeam] - Updating club team:', clubTeamId, { name, leagueId, seasonId });
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const updateData = {};
|
||||
if (name !== undefined) updateData.name = name;
|
||||
if (leagueId !== undefined) updateData.leagueId = leagueId ? parseInt(leagueId) : null;
|
||||
if (seasonId !== undefined) updateData.seasonId = seasonId ? parseInt(seasonId) : null;
|
||||
|
||||
const success = await ClubTeamService.updateClubTeam(clubTeamId, updateData);
|
||||
if (!success) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
const updatedClubTeam = await ClubTeamService.getClubTeamById(clubTeamId);
|
||||
res.status(200).json(updatedClubTeam);
|
||||
} catch (error) {
|
||||
console.error('[updateClubTeam] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteClubTeam = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubteamid: clubTeamId } = req.params;
|
||||
devLog('[deleteClubTeam] - Deleting club team:', clubTeamId);
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const success = await ClubTeamService.deleteClubTeam(clubTeamId);
|
||||
if (!success) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
res.status(200).json({ message: "Club team deleted successfully" });
|
||||
} catch (error) {
|
||||
console.error('[deleteClubTeam] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const getLeagues = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubid: clubId } = req.params;
|
||||
const { seasonid: seasonId } = req.query;
|
||||
|
||||
devLog('[getLeagues] - Getting leagues for club:', clubId, 'season:', seasonId);
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const leagues = await ClubTeamService.getLeaguesByClub(clubId, seasonId);
|
||||
devLog('[getLeagues] - Found leagues:', leagues.length);
|
||||
|
||||
res.status(200).json(leagues);
|
||||
} catch (error) {
|
||||
console.error('[getLeagues] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
@@ -25,7 +25,8 @@ export const getLeaguesForCurrentSeason = async (req, res) => {
|
||||
devLog(req.headers, req.params);
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId } = req.params;
|
||||
const leagues = await MatchService.getLeaguesForCurrentSeason(userToken, clubId);
|
||||
const { seasonid: seasonId } = req.query;
|
||||
const leagues = await MatchService.getLeaguesForCurrentSeason(userToken, clubId, seasonId);
|
||||
return res.status(200).json(leagues);
|
||||
} catch (error) {
|
||||
console.error('Error retrieving leagues:', error);
|
||||
@@ -37,7 +38,8 @@ export const getMatchesForLeagues = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId } = req.params;
|
||||
const matches = await MatchService.getMatchesForLeagues(userToken, clubId);
|
||||
const { seasonid: seasonId } = req.query;
|
||||
const matches = await MatchService.getMatchesForLeagues(userToken, clubId, seasonId);
|
||||
return res.status(200).json(matches);
|
||||
} catch (error) {
|
||||
console.error('Error retrieving matches:', error);
|
||||
|
||||
111
backend/controllers/seasonController.js
Normal file
111
backend/controllers/seasonController.js
Normal file
@@ -0,0 +1,111 @@
|
||||
import SeasonService from '../services/seasonService.js';
|
||||
import { getUserByToken } from '../utils/userUtils.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
export const getSeasons = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
devLog('[getSeasons] - Getting all seasons');
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const seasons = await SeasonService.getAllSeasons();
|
||||
devLog('[getSeasons] - Found seasons:', seasons.length);
|
||||
|
||||
res.status(200).json(seasons);
|
||||
} catch (error) {
|
||||
console.error('[getSeasons] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const getCurrentSeason = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
devLog('[getCurrentSeason] - Getting current season');
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const season = await SeasonService.getOrCreateCurrentSeason();
|
||||
devLog('[getCurrentSeason] - Current season:', season.season);
|
||||
|
||||
res.status(200).json(season);
|
||||
} catch (error) {
|
||||
console.error('[getCurrentSeason] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const createSeason = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { season } = req.body;
|
||||
|
||||
devLog('[createSeason] - Creating season:', season);
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
if (!season) {
|
||||
return res.status(400).json({ error: "missingseason" });
|
||||
}
|
||||
|
||||
// Validiere Saison-Format (z.B. "2023/2024")
|
||||
const seasonRegex = /^\d{4}\/\d{4}$/;
|
||||
if (!seasonRegex.test(season)) {
|
||||
return res.status(400).json({ error: "invalidseasonformat" });
|
||||
}
|
||||
|
||||
const newSeason = await SeasonService.createSeason(season);
|
||||
devLog('[createSeason] - Season created with ID:', newSeason.id);
|
||||
|
||||
res.status(201).json(newSeason);
|
||||
} catch (error) {
|
||||
console.error('[createSeason] - Error:', error);
|
||||
if (error.message === 'Season already exists') {
|
||||
res.status(409).json({ error: "alreadyexists" });
|
||||
} else {
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getSeason = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { seasonid: seasonId } = req.params;
|
||||
|
||||
devLog('[getSeason] - Getting season:', seasonId);
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const season = await SeasonService.getSeasonById(seasonId);
|
||||
if (!season) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
res.status(200).json(season);
|
||||
} catch (error) {
|
||||
console.error('[getSeason] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteSeason = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { seasonid: seasonId } = req.params;
|
||||
|
||||
devLog('[deleteSeason] - Deleting season:', seasonId);
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const success = await SeasonService.deleteSeason(seasonId);
|
||||
if (!success) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
res.status(200).json({ message: "deleted" });
|
||||
} catch (error) {
|
||||
console.error('[deleteSeason] - Error:', error);
|
||||
if (error.message === 'Season is used by teams' || error.message === 'Season is used by leagues') {
|
||||
res.status(409).json({ error: "seasoninuse" });
|
||||
} else {
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
}
|
||||
};
|
||||
139
backend/controllers/teamController.js
Normal file
139
backend/controllers/teamController.js
Normal file
@@ -0,0 +1,139 @@
|
||||
import TeamService from '../services/teamService.js';
|
||||
import { getUserByToken } from '../utils/userUtils.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
export const getTeams = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubid: clubId } = req.params;
|
||||
const { seasonid: seasonId } = req.query;
|
||||
|
||||
devLog('[getTeams] - Getting teams for club:', clubId, 'season:', seasonId);
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
// Check if user has access to this club
|
||||
const teams = await TeamService.getAllTeamsByClub(clubId, seasonId);
|
||||
devLog('[getTeams] - Found teams:', teams.length);
|
||||
|
||||
res.status(200).json(teams);
|
||||
} catch (error) {
|
||||
console.error('[getTeams] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const getTeam = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { teamid: teamId } = req.params;
|
||||
|
||||
devLog('[getTeam] - Getting team:', teamId);
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const team = await TeamService.getTeamById(teamId);
|
||||
if (!team) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
res.status(200).json(team);
|
||||
} catch (error) {
|
||||
console.error('[getTeam] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const createTeam = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubid: clubId } = req.params;
|
||||
const { name, leagueId, seasonId } = req.body;
|
||||
|
||||
devLog('[createTeam] - Creating team:', { name, clubId, leagueId, seasonId });
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
if (!name) {
|
||||
return res.status(400).json({ error: "missingname" });
|
||||
}
|
||||
|
||||
const teamData = {
|
||||
name,
|
||||
clubId: parseInt(clubId),
|
||||
leagueId: leagueId ? parseInt(leagueId) : null,
|
||||
seasonId: seasonId ? parseInt(seasonId) : null
|
||||
};
|
||||
|
||||
const newTeam = await TeamService.createTeam(teamData);
|
||||
devLog('[createTeam] - Team created with ID:', newTeam.id);
|
||||
|
||||
res.status(201).json(newTeam);
|
||||
} catch (error) {
|
||||
console.error('[createTeam] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const updateTeam = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { teamid: teamId } = req.params;
|
||||
const { name, leagueId, seasonId } = req.body;
|
||||
|
||||
devLog('[updateTeam] - Updating team:', teamId, { name, leagueId, seasonId });
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const updateData = {};
|
||||
if (name !== undefined) updateData.name = name;
|
||||
if (leagueId !== undefined) updateData.leagueId = leagueId ? parseInt(leagueId) : null;
|
||||
if (seasonId !== undefined) updateData.seasonId = seasonId ? parseInt(seasonId) : null;
|
||||
|
||||
const success = await TeamService.updateTeam(teamId, updateData);
|
||||
if (!success) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
const updatedTeam = await TeamService.getTeamById(teamId);
|
||||
res.status(200).json(updatedTeam);
|
||||
} catch (error) {
|
||||
console.error('[updateTeam] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteTeam = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { teamid: teamId } = req.params;
|
||||
|
||||
devLog('[deleteTeam] - Deleting team:', teamId);
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const success = await TeamService.deleteTeam(teamId);
|
||||
if (!success) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
res.status(200).json({ message: "deleted" });
|
||||
} catch (error) {
|
||||
console.error('[deleteTeam] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const getLeagues = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubid: clubId } = req.params;
|
||||
const { seasonid: seasonId } = req.query;
|
||||
|
||||
devLog('[getLeagues] - Getting leagues for club:', clubId, 'season:', seasonId);
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const leagues = await TeamService.getLeaguesByClub(clubId, seasonId);
|
||||
devLog('[getLeagues] - Found leagues:', leagues.length);
|
||||
|
||||
res.status(200).json(leagues);
|
||||
} catch (error) {
|
||||
console.error('[getLeagues] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
44
backend/migrations/add_season_to_teams.sql
Normal file
44
backend/migrations/add_season_to_teams.sql
Normal file
@@ -0,0 +1,44 @@
|
||||
-- Migration: Add season_id to teams table
|
||||
-- First, add the column as nullable
|
||||
ALTER TABLE `team` ADD COLUMN `season_id` INT NULL;
|
||||
|
||||
-- Get or create current season
|
||||
SET @current_season_id = (
|
||||
SELECT id FROM `season`
|
||||
WHERE season = (
|
||||
CASE
|
||||
WHEN MONTH(CURDATE()) >= 7 THEN CONCAT(YEAR(CURDATE()), '/', YEAR(CURDATE()) + 1)
|
||||
ELSE CONCAT(YEAR(CURDATE()) - 1, '/', YEAR(CURDATE()))
|
||||
END
|
||||
)
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
-- If no season exists, create it
|
||||
INSERT IGNORE INTO `season` (season) VALUES (
|
||||
CASE
|
||||
WHEN MONTH(CURDATE()) >= 7 THEN CONCAT(YEAR(CURDATE()), '/', YEAR(CURDATE()) + 1)
|
||||
ELSE CONCAT(YEAR(CURDATE()) - 1, '/', YEAR(CURDATE()))
|
||||
END
|
||||
);
|
||||
|
||||
-- Get the season ID again (in case we just created it)
|
||||
SET @current_season_id = (
|
||||
SELECT id FROM `season`
|
||||
WHERE season = (
|
||||
CASE
|
||||
WHEN MONTH(CURDATE()) >= 7 THEN CONCAT(YEAR(CURDATE()), '/', YEAR(CURDATE()) + 1)
|
||||
ELSE CONCAT(YEAR(CURDATE()) - 1, '/', YEAR(CURDATE()))
|
||||
END
|
||||
)
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
-- Update all existing teams to use the current season
|
||||
UPDATE `team` SET `season_id` = @current_season_id WHERE `season_id` IS NULL;
|
||||
|
||||
-- Now make the column NOT NULL and add the foreign key constraint
|
||||
ALTER TABLE `team` MODIFY COLUMN `season_id` INT NOT NULL;
|
||||
ALTER TABLE `team` ADD CONSTRAINT `team_season_id_foreign_idx`
|
||||
FOREIGN KEY (`season_id`) REFERENCES `season` (`id`)
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
54
backend/models/ClubTeam.js
Normal file
54
backend/models/ClubTeam.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
import Club from './Club.js';
|
||||
import League from './League.js';
|
||||
import Season from './Season.js';
|
||||
|
||||
const ClubTeam = sequelize.define('ClubTeam', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false,
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
clubId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: Club,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
leagueId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: League,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'SET NULL',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
seasonId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: Season,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'club_team',
|
||||
timestamps: true,
|
||||
});
|
||||
|
||||
export default ClubTeam;
|
||||
@@ -3,7 +3,6 @@ import sequelize from '../database.js';
|
||||
import Club from './Club.js';
|
||||
import League from './League.js';
|
||||
import Team from './Team.js';
|
||||
import Season from './Season.js';
|
||||
import Location from './Location.js';
|
||||
|
||||
const Match = sequelize.define('Match', {
|
||||
@@ -21,14 +20,6 @@ const Match = sequelize.define('Match', {
|
||||
type: DataTypes.TIME,
|
||||
allowNull: true,
|
||||
},
|
||||
seasonId: {
|
||||
type: DataTypes.INTEGER,
|
||||
references: {
|
||||
model: Season,
|
||||
key: 'id',
|
||||
},
|
||||
allowNull: false,
|
||||
},
|
||||
locationId: {
|
||||
type: DataTypes.INTEGER,
|
||||
references: {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
import Club from './Club.js';
|
||||
import League from './League.js';
|
||||
import Season from './Season.js';
|
||||
|
||||
const Team = sequelize.define('Team', {
|
||||
id: {
|
||||
@@ -23,6 +25,26 @@ const Team = sequelize.define('Team', {
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
leagueId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: League,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'SET NULL',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
seasonId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: Season,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'team',
|
||||
|
||||
@@ -19,6 +19,7 @@ import DiaryDateActivity from './DiaryDateActivity.js';
|
||||
import Match from './Match.js';
|
||||
import League from './League.js';
|
||||
import Team from './Team.js';
|
||||
import ClubTeam from './ClubTeam.js';
|
||||
import Season from './Season.js';
|
||||
import Location from './Location.js';
|
||||
import Group from './Group.js';
|
||||
@@ -118,8 +119,21 @@ Team.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
|
||||
Club.hasMany(League, { foreignKey: 'clubId', as: 'leagues' });
|
||||
League.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
|
||||
|
||||
Match.belongsTo(Season, { foreignKey: 'seasonId', as: 'season' });
|
||||
Season.hasMany(Match, { foreignKey: 'seasonId', as: 'matches' });
|
||||
League.hasMany(Team, { foreignKey: 'leagueId', as: 'teams' });
|
||||
Team.belongsTo(League, { foreignKey: 'leagueId', as: 'league' });
|
||||
|
||||
Season.hasMany(Team, { foreignKey: 'seasonId', as: 'teams' });
|
||||
Team.belongsTo(Season, { foreignKey: 'seasonId', as: 'season' });
|
||||
|
||||
// ClubTeam relationships
|
||||
Club.hasMany(ClubTeam, { foreignKey: 'clubId', as: 'clubTeams' });
|
||||
ClubTeam.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
|
||||
|
||||
League.hasMany(ClubTeam, { foreignKey: 'leagueId', as: 'clubTeams' });
|
||||
ClubTeam.belongsTo(League, { foreignKey: 'leagueId', as: 'league' });
|
||||
|
||||
Season.hasMany(ClubTeam, { foreignKey: 'seasonId', as: 'clubTeams' });
|
||||
ClubTeam.belongsTo(Season, { foreignKey: 'seasonId', as: 'season' });
|
||||
|
||||
Match.belongsTo(Location, { foreignKey: 'locationId', as: 'location' });
|
||||
Location.hasMany(Match, { foreignKey: 'locationId', as: 'matches' });
|
||||
@@ -231,6 +245,7 @@ export {
|
||||
Match,
|
||||
League,
|
||||
Team,
|
||||
ClubTeam,
|
||||
Group,
|
||||
GroupActivity,
|
||||
Tournament,
|
||||
|
||||
32
backend/routes/clubTeamRoutes.js
Normal file
32
backend/routes/clubTeamRoutes.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import express from 'express';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
import {
|
||||
getClubTeams,
|
||||
getClubTeam,
|
||||
createClubTeam,
|
||||
updateClubTeam,
|
||||
deleteClubTeam,
|
||||
getLeagues
|
||||
} from '../controllers/clubTeamController.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get all club teams for a club
|
||||
router.get('/club/:clubid', authenticate, getClubTeams);
|
||||
|
||||
// Create a new club team
|
||||
router.post('/club/:clubid', authenticate, createClubTeam);
|
||||
|
||||
// Get leagues for a club
|
||||
router.get('/leagues/:clubid', authenticate, getLeagues);
|
||||
|
||||
// Get a specific club team
|
||||
router.get('/:clubteamid', authenticate, getClubTeam);
|
||||
|
||||
// Update a club team
|
||||
router.put('/:clubteamid', authenticate, updateClubTeam);
|
||||
|
||||
// Delete a club team
|
||||
router.delete('/:clubteamid', authenticate, deleteClubTeam);
|
||||
|
||||
export default router;
|
||||
28
backend/routes/seasonRoutes.js
Normal file
28
backend/routes/seasonRoutes.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import express from 'express';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
import {
|
||||
getSeasons,
|
||||
getCurrentSeason,
|
||||
createSeason,
|
||||
getSeason,
|
||||
deleteSeason
|
||||
} from '../controllers/seasonController.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get all seasons
|
||||
router.get('/', authenticate, getSeasons);
|
||||
|
||||
// Get current season (creates if not exists)
|
||||
router.get('/current', authenticate, getCurrentSeason);
|
||||
|
||||
// Get a specific season
|
||||
router.get('/:seasonid', authenticate, getSeason);
|
||||
|
||||
// Create a new season
|
||||
router.post('/', authenticate, createSeason);
|
||||
|
||||
// Delete a season
|
||||
router.delete('/:seasonid', authenticate, deleteSeason);
|
||||
|
||||
export default router;
|
||||
32
backend/routes/teamRoutes.js
Normal file
32
backend/routes/teamRoutes.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import express from 'express';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
import {
|
||||
getTeams,
|
||||
getTeam,
|
||||
createTeam,
|
||||
updateTeam,
|
||||
deleteTeam,
|
||||
getLeagues
|
||||
} from '../controllers/teamController.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get all teams for a club
|
||||
router.get('/club/:clubid', authenticate, getTeams);
|
||||
|
||||
// Get leagues for a club
|
||||
router.get('/leagues/:clubid', authenticate, getLeagues);
|
||||
|
||||
// Get a specific team
|
||||
router.get('/:teamid', authenticate, getTeam);
|
||||
|
||||
// Create a new team
|
||||
router.post('/club/:clubid', authenticate, createTeam);
|
||||
|
||||
// Update a team
|
||||
router.put('/:teamid', authenticate, updateTeam);
|
||||
|
||||
// Delete a team
|
||||
router.delete('/:teamid', authenticate, deleteTeam);
|
||||
|
||||
export default router;
|
||||
@@ -6,7 +6,7 @@ import cors from 'cors';
|
||||
import {
|
||||
User, Log, Club, UserClub, Member, DiaryDate, Participant, Activity, MemberNote,
|
||||
DiaryNote, DiaryTag, MemberDiaryTag, DiaryDateTag, DiaryMemberNote, DiaryMemberTag,
|
||||
PredefinedActivity, PredefinedActivityImage, DiaryDateActivity, DiaryMemberActivity, Match, League, Team, Group,
|
||||
PredefinedActivity, PredefinedActivityImage, DiaryDateActivity, DiaryMemberActivity, Match, League, Team, ClubTeam, Group,
|
||||
GroupActivity, Tournament, TournamentGroup, TournamentMatch, TournamentResult,
|
||||
TournamentMember, Accident, UserToken, OfficialTournament, OfficialCompetition, OfficialCompetitionMember, MyTischtennis
|
||||
} from './models/index.js';
|
||||
@@ -34,6 +34,9 @@ import accidentRoutes from './routes/accidentRoutes.js';
|
||||
import trainingStatsRoutes from './routes/trainingStatsRoutes.js';
|
||||
import officialTournamentRoutes from './routes/officialTournamentRoutes.js';
|
||||
import myTischtennisRoutes from './routes/myTischtennisRoutes.js';
|
||||
import teamRoutes from './routes/teamRoutes.js';
|
||||
import clubTeamRoutes from './routes/clubTeamRoutes.js';
|
||||
import seasonRoutes from './routes/seasonRoutes.js';
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3000;
|
||||
@@ -79,6 +82,9 @@ app.use('/api/accident', accidentRoutes);
|
||||
app.use('/api/training-stats', trainingStatsRoutes);
|
||||
app.use('/api/official-tournaments', officialTournamentRoutes);
|
||||
app.use('/api/mytischtennis', myTischtennisRoutes);
|
||||
app.use('/api/teams', teamRoutes);
|
||||
app.use('/api/club-teams', clubTeamRoutes);
|
||||
app.use('/api/seasons', seasonRoutes);
|
||||
|
||||
app.use(express.static(path.join(__dirname, '../frontend/dist')));
|
||||
|
||||
|
||||
192
backend/services/clubTeamService.js
Normal file
192
backend/services/clubTeamService.js
Normal file
@@ -0,0 +1,192 @@
|
||||
import ClubTeam from '../models/ClubTeam.js';
|
||||
import League from '../models/League.js';
|
||||
import Season from '../models/Season.js';
|
||||
import SeasonService from './seasonService.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
class ClubTeamService {
|
||||
/**
|
||||
* Holt alle ClubTeams für einen Verein, optional gefiltert nach Saison.
|
||||
* Wenn keine Saison-ID angegeben ist, wird die aktuelle Saison verwendet.
|
||||
* @param {number} clubId - Die ID des Vereins.
|
||||
* @param {number|null} seasonId - Optionale Saison-ID.
|
||||
* @returns {Promise<Array<ClubTeam>>} Eine Liste von ClubTeams.
|
||||
*/
|
||||
static async getAllClubTeamsByClub(clubId, seasonId = null) {
|
||||
try {
|
||||
devLog('[ClubTeamService.getAllClubTeamsByClub] - Getting club teams for club:', clubId, 'season:', seasonId);
|
||||
|
||||
// Wenn keine Saison angegeben, verwende die aktuelle
|
||||
if (!seasonId) {
|
||||
const currentSeason = await SeasonService.getOrCreateCurrentSeason();
|
||||
seasonId = currentSeason.id;
|
||||
}
|
||||
|
||||
const clubTeams = await ClubTeam.findAll({
|
||||
where: { clubId, seasonId },
|
||||
order: [['name', 'ASC']]
|
||||
});
|
||||
|
||||
// Manuelle Datenanreicherung für Liga und Saison
|
||||
const enrichedClubTeams = [];
|
||||
for (const clubTeam of clubTeams) {
|
||||
const enrichedTeam = {
|
||||
id: clubTeam.id,
|
||||
name: clubTeam.name,
|
||||
clubId: clubTeam.clubId,
|
||||
leagueId: clubTeam.leagueId,
|
||||
seasonId: clubTeam.seasonId,
|
||||
createdAt: clubTeam.createdAt,
|
||||
updatedAt: clubTeam.updatedAt,
|
||||
league: { name: 'Unbekannt' },
|
||||
season: { season: 'Unbekannt' }
|
||||
};
|
||||
|
||||
// Lade Liga-Daten
|
||||
if (clubTeam.leagueId) {
|
||||
const league = await League.findByPk(clubTeam.leagueId, { attributes: ['name'] });
|
||||
if (league) enrichedTeam.league = league;
|
||||
}
|
||||
|
||||
// Lade Saison-Daten
|
||||
if (clubTeam.seasonId) {
|
||||
const season = await Season.findByPk(clubTeam.seasonId, { attributes: ['season'] });
|
||||
if (season) enrichedTeam.season = season;
|
||||
}
|
||||
|
||||
enrichedClubTeams.push(enrichedTeam);
|
||||
}
|
||||
devLog('[ClubTeamService.getAllClubTeamsByClub] - Found club teams:', enrichedClubTeams.length);
|
||||
return enrichedClubTeams;
|
||||
} catch (error) {
|
||||
console.error('[ClubTeamService.getAllClubTeamsByClub] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt ein ClubTeam anhand seiner ID
|
||||
* @param {number} clubTeamId - Die ID des ClubTeams
|
||||
* @returns {Promise<ClubTeam|null>} Das ClubTeam oder null, wenn nicht gefunden
|
||||
*/
|
||||
static async getClubTeamById(clubTeamId) {
|
||||
try {
|
||||
devLog('[ClubTeamService.getClubTeamById] - Getting club team:', clubTeamId);
|
||||
const clubTeam = await ClubTeam.findByPk(clubTeamId, {
|
||||
include: [
|
||||
{
|
||||
model: League,
|
||||
as: 'league',
|
||||
attributes: ['id', 'name']
|
||||
},
|
||||
{
|
||||
model: Season,
|
||||
as: 'season',
|
||||
attributes: ['id', 'season']
|
||||
}
|
||||
]
|
||||
});
|
||||
devLog('[ClubTeamService.getClubTeamById] - Found club team:', clubTeam ? 'yes' : 'no');
|
||||
return clubTeam;
|
||||
} catch (error) {
|
||||
console.error('[ClubTeamService.getClubTeamById] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt ein neues ClubTeam.
|
||||
* Wenn keine Saison-ID angegeben ist, wird die aktuelle Saison zugewiesen.
|
||||
* @param {object} clubTeamData - Die Daten des neuen ClubTeams (name, clubId, optional leagueId, seasonId).
|
||||
* @returns {Promise<ClubTeam>} Das erstellte ClubTeam.
|
||||
*/
|
||||
static async createClubTeam(clubTeamData) {
|
||||
try {
|
||||
devLog('[ClubTeamService.createClubTeam] - Creating club team:', clubTeamData);
|
||||
|
||||
// Wenn keine Saison angegeben, verwende die aktuelle
|
||||
if (!clubTeamData.seasonId) {
|
||||
const currentSeason = await SeasonService.getOrCreateCurrentSeason();
|
||||
clubTeamData.seasonId = currentSeason.id;
|
||||
}
|
||||
|
||||
const clubTeam = await ClubTeam.create(clubTeamData);
|
||||
devLog('[ClubTeamService.createClubTeam] - Club team created with ID:', clubTeam.id);
|
||||
return clubTeam;
|
||||
} catch (error) {
|
||||
console.error('[ClubTeamService.createClubTeam] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualisiert ein bestehendes ClubTeam.
|
||||
* @param {number} clubTeamId - Die ID des zu aktualisierenden ClubTeams.
|
||||
* @param {object} updateData - Die zu aktualisierenden Daten.
|
||||
* @returns {Promise<boolean>} True, wenn das ClubTeam aktualisiert wurde, sonst false.
|
||||
*/
|
||||
static async updateClubTeam(clubTeamId, updateData) {
|
||||
try {
|
||||
devLog('[ClubTeamService.updateClubTeam] - Updating club team:', clubTeamId, updateData);
|
||||
const [updatedRowsCount] = await ClubTeam.update(updateData, {
|
||||
where: { id: clubTeamId }
|
||||
});
|
||||
devLog('[ClubTeamService.updateClubTeam] - Updated rows:', updatedRowsCount);
|
||||
return updatedRowsCount > 0;
|
||||
} catch (error) {
|
||||
console.error('[ClubTeamService.updateClubTeam] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Löscht ein ClubTeam.
|
||||
* @param {number} clubTeamId - Die ID des zu löschenden ClubTeams.
|
||||
* @returns {Promise<boolean>} True, wenn das ClubTeam gelöscht wurde, sonst false.
|
||||
*/
|
||||
static async deleteClubTeam(clubTeamId) {
|
||||
try {
|
||||
devLog('[ClubTeamService.deleteClubTeam] - Deleting club team:', clubTeamId);
|
||||
const deletedRows = await ClubTeam.destroy({
|
||||
where: { id: clubTeamId }
|
||||
});
|
||||
devLog('[ClubTeamService.deleteClubTeam] - Deleted rows:', deletedRows);
|
||||
return deletedRows > 0;
|
||||
} catch (error) {
|
||||
console.error('[ClubTeamService.deleteClubTeam] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt alle Ligen für einen Verein, optional gefiltert nach Saison.
|
||||
* Wenn keine Saison-ID angegeben ist, wird die aktuelle Saison verwendet.
|
||||
* @param {number} clubId - Die ID des Vereins.
|
||||
* @param {number|null} seasonId - Optionale Saison-ID.
|
||||
* @returns {Promise<Array<League>>} Eine Liste von Ligen.
|
||||
*/
|
||||
static async getLeaguesByClub(clubId, seasonId = null) {
|
||||
try {
|
||||
devLog('[ClubTeamService.getLeaguesByClub] - Getting leagues for club:', clubId, 'season:', seasonId);
|
||||
|
||||
// Wenn keine Saison angegeben, verwende die aktuelle
|
||||
if (!seasonId) {
|
||||
const currentSeason = await SeasonService.getOrCreateCurrentSeason();
|
||||
seasonId = currentSeason.id;
|
||||
}
|
||||
|
||||
const leagues = await League.findAll({
|
||||
where: { clubId, seasonId },
|
||||
attributes: ['id', 'name', 'seasonId'],
|
||||
order: [['name', 'ASC']]
|
||||
});
|
||||
devLog('[ClubTeamService.getLeaguesByClub] - Found leagues:', leagues.length);
|
||||
return leagues;
|
||||
} catch (error) {
|
||||
console.error('[ClubTeamService.getLeaguesByClub] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ClubTeamService;
|
||||
104
backend/services/leagueService.js
Normal file
104
backend/services/leagueService.js
Normal file
@@ -0,0 +1,104 @@
|
||||
import League from '../models/League.js';
|
||||
import Season from '../models/Season.js';
|
||||
import SeasonService from './seasonService.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
class LeagueService {
|
||||
static async getAllLeaguesByClub(clubId, seasonId = null) {
|
||||
try {
|
||||
devLog('[LeagueService.getAllLeaguesByClub] - Getting leagues for club:', clubId, 'season:', seasonId);
|
||||
|
||||
// Wenn keine Saison angegeben, verwende die aktuelle
|
||||
if (!seasonId) {
|
||||
const currentSeason = await SeasonService.getOrCreateCurrentSeason();
|
||||
seasonId = currentSeason.id;
|
||||
}
|
||||
|
||||
const leagues = await League.findAll({
|
||||
where: { clubId, seasonId },
|
||||
include: [
|
||||
{
|
||||
model: Season,
|
||||
as: 'season',
|
||||
attributes: ['id', 'season']
|
||||
}
|
||||
],
|
||||
order: [['name', 'ASC']]
|
||||
});
|
||||
devLog('[LeagueService.getAllLeaguesByClub] - Found leagues:', leagues.length);
|
||||
return leagues;
|
||||
} catch (error) {
|
||||
console.error('[LeagueService.getAllLeaguesByClub] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async getLeagueById(leagueId) {
|
||||
try {
|
||||
devLog('[LeagueService.getLeagueById] - Getting league:', leagueId);
|
||||
const league = await League.findByPk(leagueId, {
|
||||
include: [
|
||||
{
|
||||
model: Season,
|
||||
as: 'season',
|
||||
attributes: ['id', 'season']
|
||||
}
|
||||
]
|
||||
});
|
||||
devLog('[LeagueService.getLeagueById] - Found league:', league ? 'yes' : 'no');
|
||||
return league;
|
||||
} catch (error) {
|
||||
console.error('[LeagueService.getLeagueById] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async createLeague(leagueData) {
|
||||
try {
|
||||
devLog('[LeagueService.createLeague] - Creating league:', leagueData);
|
||||
|
||||
// Wenn keine Saison angegeben, verwende die aktuelle
|
||||
if (!leagueData.seasonId) {
|
||||
const currentSeason = await SeasonService.getOrCreateCurrentSeason();
|
||||
leagueData.seasonId = currentSeason.id;
|
||||
}
|
||||
|
||||
const league = await League.create(leagueData);
|
||||
devLog('[LeagueService.createLeague] - League created with ID:', league.id);
|
||||
return league;
|
||||
} catch (error) {
|
||||
console.error('[LeagueService.createLeague] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async updateLeague(leagueId, updateData) {
|
||||
try {
|
||||
devLog('[LeagueService.updateLeague] - Updating league:', leagueId, updateData);
|
||||
const [updatedRowsCount] = await League.update(updateData, {
|
||||
where: { id: leagueId }
|
||||
});
|
||||
devLog('[LeagueService.updateLeague] - Updated rows:', updatedRowsCount);
|
||||
return updatedRowsCount > 0;
|
||||
} catch (error) {
|
||||
console.error('[LeagueService.updateLeague] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async deleteLeague(leagueId) {
|
||||
try {
|
||||
devLog('[LeagueService.deleteLeague] - Deleting league:', leagueId);
|
||||
const deletedRowsCount = await League.destroy({
|
||||
where: { id: leagueId }
|
||||
});
|
||||
devLog('[LeagueService.deleteLeague] - Deleted rows:', deletedRowsCount);
|
||||
return deletedRowsCount > 0;
|
||||
} catch (error) {
|
||||
console.error('[LeagueService.deleteLeague] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default LeagueService;
|
||||
@@ -7,6 +7,7 @@ import Season from '../models/Season.js';
|
||||
import Location from '../models/Location.js';
|
||||
import League from '../models/League.js';
|
||||
import Team from '../models/Team.js';
|
||||
import SeasonService from './seasonService.js';
|
||||
import { checkAccess } from '../utils/userUtils.js';
|
||||
import { Op } from 'sequelize';
|
||||
|
||||
@@ -22,8 +23,7 @@ class MatchService {
|
||||
seasonStartYear = currentYear - 1;
|
||||
}
|
||||
const seasonEndYear = seasonStartYear + 1;
|
||||
const seasonEndYearString = seasonEndYear.toString().slice(-2);
|
||||
return `${seasonStartYear}/${seasonEndYearString}`;
|
||||
return `${seasonStartYear}/${seasonEndYear}`;
|
||||
}
|
||||
|
||||
async importCSV(userToken, clubId, filePath) {
|
||||
@@ -58,7 +58,6 @@ class MatchService {
|
||||
},
|
||||
});
|
||||
matches.push({
|
||||
seasonId: season.id,
|
||||
date: parsedDate,
|
||||
time: row['Termin'].split(' ')[1],
|
||||
homeTeamId: homeTeamId,
|
||||
@@ -72,7 +71,14 @@ class MatchService {
|
||||
if (seasonString) {
|
||||
season = await Season.findOne({ where: { season: seasonString } });
|
||||
if (season) {
|
||||
await Match.destroy({ where: { clubId, seasonId: season.id } });
|
||||
// Lösche alle Matches für Ligen dieser Saison
|
||||
const leagues = await League.findAll({
|
||||
where: { seasonId: season.id, clubId }
|
||||
});
|
||||
const leagueIds = leagues.map(league => league.id);
|
||||
if (leagueIds.length > 0) {
|
||||
await Match.destroy({ where: { clubId, leagueId: leagueIds } });
|
||||
}
|
||||
}
|
||||
}
|
||||
const result = await Match.bulkCreate(matches);
|
||||
@@ -99,33 +105,28 @@ class MatchService {
|
||||
}
|
||||
|
||||
|
||||
async getLeaguesForCurrentSeason(userToken, clubId) {
|
||||
async getLeaguesForCurrentSeason(userToken, clubId, seasonId = null) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const seasonString = this.generateSeasonString();
|
||||
const season = await Season.findOne({
|
||||
where: {
|
||||
season: {
|
||||
[Op.like]: `%${seasonString}%`
|
||||
}
|
||||
|
||||
// Verwende SeasonService für korrekte Saison-Verwaltung
|
||||
let season;
|
||||
if (!seasonId) {
|
||||
season = await SeasonService.getOrCreateCurrentSeason();
|
||||
} else {
|
||||
season = await SeasonService.getSeasonById(seasonId);
|
||||
if (!season) {
|
||||
throw new Error('Season not found');
|
||||
}
|
||||
});
|
||||
if (!season) {
|
||||
await Season.create({ season: seasonString });
|
||||
throw new Error('Season not found');
|
||||
}
|
||||
|
||||
try {
|
||||
const leagues = await League.findAll({
|
||||
include: [{
|
||||
model: Match,
|
||||
as: 'leagueMatches',
|
||||
where: {
|
||||
seasonId: season.id,
|
||||
clubId: clubId
|
||||
},
|
||||
attributes: [],
|
||||
}],
|
||||
where: {
|
||||
clubId: clubId,
|
||||
seasonId: season.id
|
||||
},
|
||||
attributes: ['id', 'name'],
|
||||
group: ['League.id'],
|
||||
order: [['name', 'ASC']]
|
||||
});
|
||||
return leagues;
|
||||
} catch (error) {
|
||||
@@ -134,48 +135,66 @@ class MatchService {
|
||||
}
|
||||
}
|
||||
|
||||
async getMatchesForLeagues(userToken, clubId) {
|
||||
async getMatchesForLeagues(userToken, clubId, seasonId = null) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const seasonString = this.generateSeasonString();
|
||||
const season = await Season.findOne({
|
||||
where: {
|
||||
season: {
|
||||
[Op.like]: `%${seasonString}%`
|
||||
}
|
||||
|
||||
// Wenn keine Saison angegeben, verwende die aktuelle
|
||||
let season;
|
||||
if (!seasonId) {
|
||||
season = await SeasonService.getOrCreateCurrentSeason();
|
||||
} else {
|
||||
season = await SeasonService.getSeasonById(seasonId);
|
||||
if (!season) {
|
||||
throw new Error('Season not found');
|
||||
}
|
||||
});
|
||||
if (!season) {
|
||||
throw new Error('Season not found');
|
||||
}
|
||||
const matches = await Match.findAll({
|
||||
where: {
|
||||
seasonId: season.id,
|
||||
clubId: clubId,
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: League,
|
||||
as: 'leagueDetails',
|
||||
attributes: ['name'],
|
||||
},
|
||||
{
|
||||
model: Team,
|
||||
as: 'homeTeam', // Assuming your associations are set correctly
|
||||
attributes: ['name'],
|
||||
},
|
||||
{
|
||||
model: Team,
|
||||
as: 'guestTeam',
|
||||
attributes: ['name'],
|
||||
},
|
||||
{
|
||||
model: Location,
|
||||
as: 'location',
|
||||
attributes: ['name', 'address', 'city', 'zip'],
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
return matches;
|
||||
|
||||
// Filtere Matches nach Liga-Saison und lade Daten manuell
|
||||
const enrichedMatches = [];
|
||||
for (const match of matches) {
|
||||
// Lade Liga-Daten
|
||||
const league = await League.findByPk(match.leagueId, { attributes: ['name', 'seasonId'] });
|
||||
if (!league || league.seasonId !== season.id) {
|
||||
continue; // Skip matches from other seasons
|
||||
}
|
||||
|
||||
const enrichedMatch = {
|
||||
id: match.id,
|
||||
date: match.date,
|
||||
time: match.time,
|
||||
homeTeamId: match.homeTeamId,
|
||||
guestTeamId: match.guestTeamId,
|
||||
locationId: match.locationId,
|
||||
leagueId: match.leagueId,
|
||||
homeTeam: { name: 'Unbekannt' },
|
||||
guestTeam: { name: 'Unbekannt' },
|
||||
location: { name: 'Unbekannt', address: '', city: '', zip: '' },
|
||||
leagueDetails: { name: league.name }
|
||||
};
|
||||
|
||||
if (match.homeTeamId) {
|
||||
const homeTeam = await Team.findByPk(match.homeTeamId, { attributes: ['name'] });
|
||||
if (homeTeam) enrichedMatch.homeTeam = homeTeam;
|
||||
}
|
||||
if (match.guestTeamId) {
|
||||
const guestTeam = await Team.findByPk(match.guestTeamId, { attributes: ['name'] });
|
||||
if (guestTeam) enrichedMatch.guestTeam = guestTeam;
|
||||
}
|
||||
if (match.locationId) {
|
||||
const location = await Location.findByPk(match.locationId, {
|
||||
attributes: ['name', 'address', 'city', 'zip']
|
||||
});
|
||||
if (location) enrichedMatch.location = location;
|
||||
}
|
||||
|
||||
enrichedMatches.push(enrichedMatch);
|
||||
}
|
||||
return enrichedMatches;
|
||||
}
|
||||
|
||||
async getMatchesForLeague(userToken, clubId, leagueId) {
|
||||
@@ -193,34 +212,50 @@ class MatchService {
|
||||
}
|
||||
const matches = await Match.findAll({
|
||||
where: {
|
||||
seasonId: season.id,
|
||||
clubId: clubId,
|
||||
leagueId: leagueId
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: League,
|
||||
as: 'leagueDetails',
|
||||
attributes: ['name'],
|
||||
},
|
||||
{
|
||||
model: Team,
|
||||
as: 'homeTeam',
|
||||
attributes: ['name'],
|
||||
},
|
||||
{
|
||||
model: Team,
|
||||
as: 'guestTeam',
|
||||
attributes: ['name'],
|
||||
},
|
||||
{
|
||||
model: Location,
|
||||
as: 'location',
|
||||
attributes: ['name', 'address', 'city', 'zip'],
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
return matches;
|
||||
|
||||
// Lade Team- und Location-Daten manuell
|
||||
const enrichedMatches = [];
|
||||
for (const match of matches) {
|
||||
const enrichedMatch = {
|
||||
id: match.id,
|
||||
date: match.date,
|
||||
time: match.time,
|
||||
homeTeamId: match.homeTeamId,
|
||||
guestTeamId: match.guestTeamId,
|
||||
locationId: match.locationId,
|
||||
leagueId: match.leagueId,
|
||||
homeTeam: { name: 'Unbekannt' },
|
||||
guestTeam: { name: 'Unbekannt' },
|
||||
location: { name: 'Unbekannt', address: '', city: '', zip: '' },
|
||||
leagueDetails: { name: 'Unbekannt' }
|
||||
};
|
||||
|
||||
if (match.homeTeamId) {
|
||||
const homeTeam = await Team.findByPk(match.homeTeamId, { attributes: ['name'] });
|
||||
if (homeTeam) enrichedMatch.homeTeam = homeTeam;
|
||||
}
|
||||
if (match.guestTeamId) {
|
||||
const guestTeam = await Team.findByPk(match.guestTeamId, { attributes: ['name'] });
|
||||
if (guestTeam) enrichedMatch.guestTeam = guestTeam;
|
||||
}
|
||||
if (match.locationId) {
|
||||
const location = await Location.findByPk(match.locationId, {
|
||||
attributes: ['name', 'address', 'city', 'zip']
|
||||
});
|
||||
if (location) enrichedMatch.location = location;
|
||||
}
|
||||
if (match.leagueId) {
|
||||
const league = await League.findByPk(match.leagueId, { attributes: ['name'] });
|
||||
if (league) enrichedMatch.leagueDetails = league;
|
||||
}
|
||||
|
||||
enrichedMatches.push(enrichedMatch);
|
||||
}
|
||||
return enrichedMatches;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
160
backend/services/seasonService.js
Normal file
160
backend/services/seasonService.js
Normal file
@@ -0,0 +1,160 @@
|
||||
import Season from '../models/Season.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
class SeasonService {
|
||||
/**
|
||||
* Ermittelt die aktuelle Saison basierend auf dem aktuellen Datum
|
||||
* @returns {string} Saison im Format "2023/2024"
|
||||
*/
|
||||
static getCurrentSeasonString() {
|
||||
const now = new Date();
|
||||
const currentYear = now.getFullYear();
|
||||
const currentMonth = now.getMonth() + 1; // getMonth() ist 0-basiert
|
||||
|
||||
// Ab 1. Juli: neue Saison beginnt
|
||||
if (currentMonth >= 7) {
|
||||
return `${currentYear}/${currentYear + 1}`;
|
||||
} else {
|
||||
return `${currentYear - 1}/${currentYear}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt oder erstellt die aktuelle Saison
|
||||
* @returns {Promise<Season>} Die aktuelle Saison
|
||||
*/
|
||||
static async getOrCreateCurrentSeason() {
|
||||
try {
|
||||
const currentSeasonString = this.getCurrentSeasonString();
|
||||
devLog('[SeasonService.getOrCreateCurrentSeason] - Current season string:', currentSeasonString);
|
||||
|
||||
// Versuche die aktuelle Saison zu finden
|
||||
let season = await Season.findOne({
|
||||
where: { season: currentSeasonString }
|
||||
});
|
||||
|
||||
// Falls nicht vorhanden, erstelle sie
|
||||
if (!season) {
|
||||
devLog('[SeasonService.getOrCreateCurrentSeason] - Creating new season:', currentSeasonString);
|
||||
season = await Season.create({
|
||||
season: currentSeasonString
|
||||
});
|
||||
}
|
||||
|
||||
devLog('[SeasonService.getOrCreateCurrentSeason] - Season found/created:', season.id);
|
||||
return season;
|
||||
} catch (error) {
|
||||
console.error('[SeasonService.getOrCreateCurrentSeason] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt alle verfügbaren Saisons
|
||||
* @returns {Promise<Array<Season>>} Alle Saisons sortiert nach Name
|
||||
*/
|
||||
static async getAllSeasons() {
|
||||
try {
|
||||
devLog('[SeasonService.getAllSeasons] - Getting all seasons');
|
||||
const seasons = await Season.findAll({
|
||||
order: [['season', 'DESC']] // Neueste zuerst
|
||||
});
|
||||
devLog('[SeasonService.getAllSeasons] - Found seasons:', seasons.length);
|
||||
return seasons;
|
||||
} catch (error) {
|
||||
console.error('[SeasonService.getAllSeasons] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine neue Saison
|
||||
* @param {string} seasonString - Saison im Format "2023/2024"
|
||||
* @returns {Promise<Season>} Die erstellte Saison
|
||||
*/
|
||||
static async createSeason(seasonString) {
|
||||
try {
|
||||
devLog('[SeasonService.createSeason] - Creating season:', seasonString);
|
||||
|
||||
// Prüfe ob Saison bereits existiert
|
||||
const existingSeason = await Season.findOne({
|
||||
where: { season: seasonString }
|
||||
});
|
||||
|
||||
if (existingSeason) {
|
||||
throw new Error('Season already exists');
|
||||
}
|
||||
|
||||
const season = await Season.create({
|
||||
season: seasonString
|
||||
});
|
||||
|
||||
devLog('[SeasonService.createSeason] - Season created with ID:', season.id);
|
||||
return season;
|
||||
} catch (error) {
|
||||
console.error('[SeasonService.createSeason] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt eine Saison nach ID
|
||||
* @param {number} seasonId - Die Saison-ID
|
||||
* @returns {Promise<Season|null>} Die Saison oder null
|
||||
*/
|
||||
static async getSeasonById(seasonId) {
|
||||
try {
|
||||
devLog('[SeasonService.getSeasonById] - Getting season:', seasonId);
|
||||
const season = await Season.findByPk(seasonId);
|
||||
devLog('[SeasonService.getSeasonById] - Found season:', season ? 'yes' : 'no');
|
||||
return season;
|
||||
} catch (error) {
|
||||
console.error('[SeasonService.getSeasonById] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Löscht eine Saison (nur wenn keine Teams/Ligen damit verknüpft sind)
|
||||
* @param {number} seasonId - Die Saison-ID
|
||||
* @returns {Promise<boolean>} True wenn gelöscht, false wenn nicht möglich
|
||||
*/
|
||||
static async deleteSeason(seasonId) {
|
||||
try {
|
||||
devLog('[SeasonService.deleteSeason] - Deleting season:', seasonId);
|
||||
|
||||
// Prüfe ob Saison verwendet wird
|
||||
const season = await Season.findByPk(seasonId, {
|
||||
include: [
|
||||
{ association: 'teams' },
|
||||
{ association: 'leagues' }
|
||||
]
|
||||
});
|
||||
|
||||
if (!season) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prüfe ob Saison verwendet wird
|
||||
if (season.teams && season.teams.length > 0) {
|
||||
throw new Error('Season is used by teams');
|
||||
}
|
||||
|
||||
if (season.leagues && season.leagues.length > 0) {
|
||||
throw new Error('Season is used by leagues');
|
||||
}
|
||||
|
||||
await Season.destroy({
|
||||
where: { id: seasonId }
|
||||
});
|
||||
|
||||
devLog('[SeasonService.deleteSeason] - Season deleted');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('[SeasonService.deleteSeason] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default SeasonService;
|
||||
144
backend/services/teamService.js
Normal file
144
backend/services/teamService.js
Normal file
@@ -0,0 +1,144 @@
|
||||
import Team from '../models/Team.js';
|
||||
import League from '../models/League.js';
|
||||
import Club from '../models/Club.js';
|
||||
import Season from '../models/Season.js';
|
||||
import SeasonService from './seasonService.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
class TeamService {
|
||||
static async getAllTeamsByClub(clubId, seasonId = null) {
|
||||
try {
|
||||
devLog('[TeamService.getAllTeamsByClub] - Getting teams for club:', clubId, 'season:', seasonId);
|
||||
|
||||
// Wenn keine Saison angegeben, verwende die aktuelle
|
||||
if (!seasonId) {
|
||||
const currentSeason = await SeasonService.getOrCreateCurrentSeason();
|
||||
seasonId = currentSeason.id;
|
||||
}
|
||||
|
||||
const teams = await Team.findAll({
|
||||
where: { clubId, seasonId },
|
||||
include: [
|
||||
{
|
||||
model: League,
|
||||
as: 'league',
|
||||
attributes: ['id', 'name']
|
||||
},
|
||||
{
|
||||
model: Season,
|
||||
as: 'season',
|
||||
attributes: ['id', 'season']
|
||||
}
|
||||
],
|
||||
order: [['name', 'ASC']]
|
||||
});
|
||||
devLog('[TeamService.getAllTeamsByClub] - Found teams:', teams.length);
|
||||
return teams;
|
||||
} catch (error) {
|
||||
console.error('[TeamService.getAllTeamsByClub] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async getTeamById(teamId) {
|
||||
try {
|
||||
devLog('[TeamService.getTeamById] - Getting team:', teamId);
|
||||
const team = await Team.findByPk(teamId, {
|
||||
include: [
|
||||
{
|
||||
model: League,
|
||||
as: 'league',
|
||||
attributes: ['id', 'name']
|
||||
},
|
||||
{
|
||||
model: Club,
|
||||
as: 'club',
|
||||
attributes: ['id', 'name']
|
||||
},
|
||||
{
|
||||
model: Season,
|
||||
as: 'season',
|
||||
attributes: ['id', 'season']
|
||||
}
|
||||
]
|
||||
});
|
||||
devLog('[TeamService.getTeamById] - Found team:', team ? 'yes' : 'no');
|
||||
return team;
|
||||
} catch (error) {
|
||||
console.error('[TeamService.getTeamById] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async createTeam(teamData) {
|
||||
try {
|
||||
devLog('[TeamService.createTeam] - Creating team:', teamData);
|
||||
|
||||
// Wenn keine Saison angegeben, verwende die aktuelle
|
||||
if (!teamData.seasonId) {
|
||||
const currentSeason = await SeasonService.getOrCreateCurrentSeason();
|
||||
teamData.seasonId = currentSeason.id;
|
||||
}
|
||||
|
||||
const team = await Team.create(teamData);
|
||||
devLog('[TeamService.createTeam] - Team created with ID:', team.id);
|
||||
return team;
|
||||
} catch (error) {
|
||||
console.error('[TeamService.createTeam] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async updateTeam(teamId, updateData) {
|
||||
try {
|
||||
devLog('[TeamService.updateTeam] - Updating team:', teamId, updateData);
|
||||
const [updatedRowsCount] = await Team.update(updateData, {
|
||||
where: { id: teamId }
|
||||
});
|
||||
devLog('[TeamService.updateTeam] - Updated rows:', updatedRowsCount);
|
||||
return updatedRowsCount > 0;
|
||||
} catch (error) {
|
||||
console.error('[TeamService.updateTeam] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async deleteTeam(teamId) {
|
||||
try {
|
||||
devLog('[TeamService.deleteTeam] - Deleting team:', teamId);
|
||||
const deletedRowsCount = await Team.destroy({
|
||||
where: { id: teamId }
|
||||
});
|
||||
devLog('[TeamService.deleteTeam] - Deleted rows:', deletedRowsCount);
|
||||
return deletedRowsCount > 0;
|
||||
} catch (error) {
|
||||
console.error('[TeamService.deleteTeam] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async getLeaguesByClub(clubId, seasonId = null) {
|
||||
try {
|
||||
devLog('[TeamService.getLeaguesByClub] - Getting leagues for club:', clubId, 'season:', seasonId);
|
||||
|
||||
// Wenn keine Saison angegeben, verwende die aktuelle
|
||||
if (!seasonId) {
|
||||
const currentSeason = await SeasonService.getOrCreateCurrentSeason();
|
||||
seasonId = currentSeason.id;
|
||||
}
|
||||
|
||||
const leagues = await League.findAll({
|
||||
where: { clubId, seasonId },
|
||||
attributes: ['id', 'name', 'seasonId'],
|
||||
order: [['name', 'ASC']]
|
||||
});
|
||||
devLog('[TeamService.getLeaguesByClub] - Found leagues:', leagues.length);
|
||||
return leagues;
|
||||
} catch (error) {
|
||||
console.error('[TeamService.getLeaguesByClub] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default TeamService;
|
||||
@@ -65,6 +65,10 @@
|
||||
<span class="nav-icon">⚙️</span>
|
||||
Vordefinierte Aktivitäten
|
||||
</a>
|
||||
<a href="/team-management" class="nav-link">
|
||||
<span class="nav-icon">👥</span>
|
||||
Team-Verwaltung
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -170,7 +174,7 @@ export default {
|
||||
},
|
||||
|
||||
loadClub() {
|
||||
this.setCurrentClub(this.currentClub);
|
||||
this.setCurrentClub(this.selectedClub);
|
||||
this.$router.push('/training-stats');
|
||||
},
|
||||
|
||||
|
||||
301
frontend/src/components/SeasonSelector.vue
Normal file
301
frontend/src/components/SeasonSelector.vue
Normal file
@@ -0,0 +1,301 @@
|
||||
<template>
|
||||
<div class="season-selector">
|
||||
<label>
|
||||
<span>Saison:</span>
|
||||
<div class="season-input-group">
|
||||
<select v-model="selectedSeasonId" @change="onSeasonChange" class="season-select" :disabled="loading">
|
||||
<option value="">{{ loading ? 'Lade...' : 'Saison wählen...' }}</option>
|
||||
<option v-for="season in seasons" :key="season.id" :value="season.id">
|
||||
{{ season.season }}
|
||||
</option>
|
||||
</select>
|
||||
<button @click="showNewSeasonForm = !showNewSeasonForm" class="btn-add-season" title="Neue Saison hinzufügen">
|
||||
{{ showNewSeasonForm ? '✕' : '+' }}
|
||||
</button>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<div v-if="error" class="error-message">
|
||||
{{ error }}
|
||||
</div>
|
||||
|
||||
<div v-if="showNewSeasonForm" class="new-season-form">
|
||||
<label>
|
||||
<span>Neue Saison:</span>
|
||||
<input
|
||||
type="text"
|
||||
v-model="newSeasonString"
|
||||
placeholder="z.B. 2023/2024"
|
||||
@keyup.enter="createSeason"
|
||||
class="season-input"
|
||||
>
|
||||
</label>
|
||||
<div class="form-actions">
|
||||
<button @click="createSeason" :disabled="!isValidSeasonFormat" class="btn-create">
|
||||
Erstellen
|
||||
</button>
|
||||
<button @click="cancelNewSeason" class="btn-cancel">
|
||||
Abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, computed, onMounted, watch } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
import apiClient from '../apiClient.js';
|
||||
|
||||
export default {
|
||||
name: 'SeasonSelector',
|
||||
props: {
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
default: null
|
||||
},
|
||||
showCurrentSeason: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'season-change'],
|
||||
setup(props, { emit }) {
|
||||
const store = useStore();
|
||||
|
||||
// Reactive data
|
||||
const seasons = ref([]);
|
||||
const selectedSeasonId = ref(props.modelValue);
|
||||
const showNewSeasonForm = ref(false);
|
||||
const newSeasonString = ref('');
|
||||
const loading = ref(false);
|
||||
const error = ref(null);
|
||||
|
||||
// Computed
|
||||
const isValidSeasonFormat = computed(() => {
|
||||
const seasonRegex = /^\d{4}\/\d{4}$/;
|
||||
return seasonRegex.test(newSeasonString.value);
|
||||
});
|
||||
|
||||
// Methods
|
||||
const loadSeasons = async () => {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
|
||||
try {
|
||||
console.log('SeasonSelector: Loading seasons');
|
||||
const response = await apiClient.get('/seasons');
|
||||
console.log('SeasonSelector: Loaded seasons:', response.data);
|
||||
seasons.value = response.data;
|
||||
|
||||
// Wenn showCurrentSeason true ist und keine Saison ausgewählt, wähle die aktuelle
|
||||
if (props.showCurrentSeason && !selectedSeasonId.value && seasons.value.length > 0) {
|
||||
// Die erste Saison ist die neueste (sortiert nach DESC)
|
||||
selectedSeasonId.value = seasons.value[0].id;
|
||||
emit('update:modelValue', selectedSeasonId.value);
|
||||
emit('season-change', seasons.value[0]);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Fehler beim Laden der Saisons:', err);
|
||||
error.value = 'Fehler beim Laden der Saisons';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onSeasonChange = () => {
|
||||
const selectedSeason = seasons.value.find(s => s.id == selectedSeasonId.value);
|
||||
emit('update:modelValue', selectedSeasonId.value);
|
||||
emit('season-change', selectedSeason);
|
||||
};
|
||||
|
||||
const createSeason = async () => {
|
||||
if (!isValidSeasonFormat.value) return;
|
||||
|
||||
try {
|
||||
const response = await apiClient.post('/seasons', {
|
||||
season: newSeasonString.value
|
||||
});
|
||||
|
||||
const newSeason = response.data;
|
||||
seasons.value.unshift(newSeason); // Am Anfang einfügen (neueste zuerst)
|
||||
selectedSeasonId.value = newSeason.id;
|
||||
emit('update:modelValue', selectedSeasonId.value);
|
||||
emit('season-change', newSeason);
|
||||
|
||||
// Formular zurücksetzen
|
||||
newSeasonString.value = '';
|
||||
showNewSeasonForm.value = false;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erstellen der Saison:', error);
|
||||
if (error.response?.data?.error === 'alreadyexists') {
|
||||
alert('Diese Saison existiert bereits!');
|
||||
} else {
|
||||
alert('Fehler beim Erstellen der Saison');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const cancelNewSeason = () => {
|
||||
newSeasonString.value = '';
|
||||
showNewSeasonForm.value = false;
|
||||
};
|
||||
|
||||
// Watch for prop changes
|
||||
watch(() => props.modelValue, (newValue) => {
|
||||
selectedSeasonId.value = newValue;
|
||||
});
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
loadSeasons();
|
||||
});
|
||||
|
||||
return {
|
||||
seasons,
|
||||
selectedSeasonId,
|
||||
showNewSeasonForm,
|
||||
newSeasonString,
|
||||
loading,
|
||||
error,
|
||||
isValidSeasonFormat,
|
||||
onSeasonChange,
|
||||
createSeason,
|
||||
cancelNewSeason
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.season-selector {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.season-selector label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.season-selector label span {
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.season-input-group {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.season-select {
|
||||
flex: 1;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius-small);
|
||||
font-size: 1rem;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.season-select:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 2px var(--primary-light);
|
||||
}
|
||||
|
||||
.btn-add-season {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: var(--border-radius-small);
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.btn-add-season:hover {
|
||||
background: var(--primary-dark);
|
||||
}
|
||||
|
||||
.new-season-form {
|
||||
background: var(--background-light);
|
||||
padding: 1rem;
|
||||
border-radius: var(--border-radius);
|
||||
border: 1px solid var(--border-color);
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.new-season-form label {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.season-input {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius-small);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.season-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 2px var(--primary-light);
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-create {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: var(--border-radius-small);
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.btn-create:hover:not(:disabled) {
|
||||
background: var(--primary-dark);
|
||||
}
|
||||
|
||||
.btn-create:disabled {
|
||||
background: var(--text-muted);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background: var(--background-light);
|
||||
color: var(--text-color);
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: var(--border-radius-small);
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.btn-cancel:hover {
|
||||
background: var(--border-color);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #dc3545;
|
||||
font-size: 0.875rem;
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
background: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
border-radius: var(--border-radius-small);
|
||||
}
|
||||
</style>
|
||||
@@ -14,6 +14,7 @@ import TrainingStatsView from './views/TrainingStatsView.vue';
|
||||
import PredefinedActivities from './views/PredefinedActivities.vue';
|
||||
import OfficialTournaments from './views/OfficialTournaments.vue';
|
||||
import MyTischtennisAccount from './views/MyTischtennisAccount.vue';
|
||||
import TeamManagementView from './views/TeamManagementView.vue';
|
||||
import Impressum from './views/Impressum.vue';
|
||||
import Datenschutz from './views/Datenschutz.vue';
|
||||
|
||||
@@ -33,6 +34,7 @@ const routes = [
|
||||
{ path: '/predefined-activities', component: PredefinedActivities },
|
||||
{ path: '/official-tournaments', component: OfficialTournaments },
|
||||
{ path: '/mytischtennis-account', component: MyTischtennisAccount },
|
||||
{ path: '/team-management', component: TeamManagementView },
|
||||
{ path: '/impressum', component: Impressum },
|
||||
{ path: '/datenschutz', component: Datenschutz },
|
||||
];
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>Spielpläne</h2>
|
||||
|
||||
<SeasonSelector
|
||||
v-model="selectedSeasonId"
|
||||
@season-change="onSeasonChange"
|
||||
:show-current-season="true"
|
||||
/>
|
||||
|
||||
<button @click="openImportModal">Spielplanimport</button>
|
||||
<div v-if="hoveredMatch && hoveredMatch.location" class="hover-info">
|
||||
<p><strong>{{ hoveredMatch.location.name || 'N/A' }}</strong></p>
|
||||
@@ -12,8 +19,9 @@
|
||||
<li class="special-link" @click="loadAllMatches">Gesamtspielplan</li>
|
||||
<li class="special-link" @click="loadAdultMatches">Spielplan Erwachsene</li>
|
||||
<li class="divider"></li>
|
||||
<li v-for="league in leagues" :key="league" @click="loadMatchesForLeague(league.id, league.name)">{{
|
||||
<li v-for="league in leagues" :key="league.id" @click="loadMatchesForLeague(league.id, league.name)">{{
|
||||
league.name }}</li>
|
||||
<li v-if="leagues.length === 0" class="no-leagues">Keine Ligen für diese Saison gefunden</li>
|
||||
</ul>
|
||||
<div class="flex-item">
|
||||
<button @click="generatePDF">Download PDF</button>
|
||||
@@ -65,9 +73,13 @@
|
||||
import { mapGetters } from 'vuex';
|
||||
import apiClient from '../apiClient.js';
|
||||
import PDFGenerator from '../components/PDFGenerator.js';
|
||||
import SeasonSelector from '../components/SeasonSelector.vue';
|
||||
|
||||
export default {
|
||||
name: 'ScheduleView',
|
||||
components: {
|
||||
SeasonSelector
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['isAuthenticated', 'currentClub', 'clubs', 'currentClubName']),
|
||||
},
|
||||
@@ -79,6 +91,8 @@ export default {
|
||||
matches: [],
|
||||
selectedLeague: '',
|
||||
hoveredMatch: null,
|
||||
selectedSeasonId: null,
|
||||
currentSeason: null,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
@@ -174,12 +188,25 @@ export default {
|
||||
async loadLeagues() {
|
||||
try {
|
||||
const clubId = this.currentClub;
|
||||
const response = await apiClient.get(`/matches/leagues/current/${clubId}`);
|
||||
const seasonParam = this.selectedSeasonId ? `?seasonid=${this.selectedSeasonId}` : '';
|
||||
console.log('ScheduleView: Loading leagues for club:', clubId, 'season:', this.selectedSeasonId);
|
||||
const response = await apiClient.get(`/matches/leagues/current/${clubId}${seasonParam}`);
|
||||
console.log('ScheduleView: Loaded leagues:', response.data);
|
||||
this.leagues = this.sortLeagues(response.data);
|
||||
console.log('ScheduleView: Sorted leagues:', this.leagues);
|
||||
} catch (error) {
|
||||
console.error('ScheduleView: Error loading leagues:', error);
|
||||
alert('Fehler beim Laden der Ligen');
|
||||
}
|
||||
},
|
||||
onSeasonChange(season) {
|
||||
console.log('ScheduleView: Season changed to:', season);
|
||||
this.currentSeason = season;
|
||||
this.loadLeagues();
|
||||
// Leere die aktuellen Matches, da sie für eine andere Saison sind
|
||||
this.matches = [];
|
||||
this.selectedLeague = '';
|
||||
},
|
||||
async loadMatchesForLeague(leagueId, leagueName) {
|
||||
this.selectedLeague = leagueName;
|
||||
try {
|
||||
@@ -193,7 +220,8 @@ export default {
|
||||
async loadAllMatches() {
|
||||
this.selectedLeague = 'Gesamtspielplan';
|
||||
try {
|
||||
const response = await apiClient.get(`/matches/leagues/${this.currentClub}/matches`);
|
||||
const seasonParam = this.selectedSeasonId ? `?seasonid=${this.selectedSeasonId}` : '';
|
||||
const response = await apiClient.get(`/matches/leagues/${this.currentClub}/matches${seasonParam}`);
|
||||
this.matches = response.data;
|
||||
} catch (error) {
|
||||
alert('Fehler beim Laden des Gesamtspielplans');
|
||||
@@ -203,7 +231,8 @@ export default {
|
||||
async loadAdultMatches() {
|
||||
this.selectedLeague = 'Spielplan Erwachsene';
|
||||
try {
|
||||
const response = await apiClient.get(`/matches/leagues/${this.currentClub}/matches`);
|
||||
const seasonParam = this.selectedSeasonId ? `?seasonid=${this.selectedSeasonId}` : '';
|
||||
const response = await apiClient.get(`/matches/leagues/${this.currentClub}/matches${seasonParam}`);
|
||||
// Filtere nur Erwachsenenligen (keine Jugendligen)
|
||||
const allMatches = response.data;
|
||||
this.matches = allMatches.filter(match => {
|
||||
@@ -303,7 +332,9 @@ export default {
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
await this.loadLeagues();
|
||||
// Ligen werden geladen, sobald eine Saison ausgewählt ist
|
||||
// Die SeasonSelector-Komponente wird automatisch die aktuelle Saison auswählen
|
||||
// und dann onSeasonChange aufrufen, was loadLeagues() triggert
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -428,6 +459,12 @@ li {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.no-leagues {
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background-color: #ddd;
|
||||
|
||||
612
frontend/src/views/TeamManagementView.vue
Normal file
612
frontend/src/views/TeamManagementView.vue
Normal file
@@ -0,0 +1,612 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>Team-Verwaltung</h2>
|
||||
|
||||
<SeasonSelector
|
||||
v-model="selectedSeasonId"
|
||||
@season-change="onSeasonChange"
|
||||
:show-current-season="true"
|
||||
/>
|
||||
|
||||
<div class="newteam">
|
||||
<div class="toggle-new-team">
|
||||
<span @click="toggleNewTeam">
|
||||
<span class="add">{{ teamFormIsOpen ? '-' : '+' }}</span>
|
||||
{{ teamToEdit === null ? "Neues Team" : "Team bearbeiten" }}
|
||||
</span>
|
||||
<button v-if="teamToEdit !== null" @click="resetToNewTeam">Neues Team anlegen</button>
|
||||
</div>
|
||||
|
||||
<div v-if="teamFormIsOpen" class="new-team-form">
|
||||
<label>
|
||||
<span>Team-Name:</span>
|
||||
<input type="text" v-model="newTeamName" placeholder="z.B. Herren 1, Damen 2">
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>Spielklasse:</span>
|
||||
<select v-model="newLeagueId">
|
||||
<option value="">Keine Spielklasse</option>
|
||||
<option v-for="league in filteredLeagues" :key="league.id" :value="league.id">
|
||||
{{ league.name }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<div class="form-actions">
|
||||
<button @click="addNewTeam" :disabled="!newTeamName.trim()">
|
||||
{{ teamToEdit ? 'Ändern' : 'Anlegen & Bearbeiten' }}
|
||||
</button>
|
||||
<button @click="resetNewTeam" v-if="teamToEdit === null" class="cancel-action">
|
||||
Felder leeren
|
||||
</button>
|
||||
<button @click="resetToNewTeam" v-if="teamToEdit !== null" class="cancel-action">
|
||||
Neues Team anlegen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Upload-Buttons nur beim Bearbeiten eines bestehenden Teams -->
|
||||
<div v-if="teamToEdit" class="upload-actions">
|
||||
<h4>Team-Dokumente hochladen</h4>
|
||||
<div class="upload-buttons">
|
||||
<button @click="uploadCodeList" class="upload-btn code-list-btn">
|
||||
📋 Code-Liste hochladen
|
||||
</button>
|
||||
<button @click="uploadPinList" class="upload-btn pin-list-btn">
|
||||
🔐 Pin-Liste hochladen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="teams-list">
|
||||
<h3>Teams ({{ teams.length }}) - Saison {{ currentSeason?.season || 'unbekannt' }}</h3>
|
||||
|
||||
<div v-if="teams.length === 0" class="no-teams">
|
||||
<p>Noch keine Teams vorhanden. Erstellen Sie Ihr erstes Team!</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="teams-grid">
|
||||
<div
|
||||
v-for="team in teams"
|
||||
:key="team.id"
|
||||
class="team-card"
|
||||
@click="editTeam(team)"
|
||||
>
|
||||
<div class="team-header">
|
||||
<h4>{{ team.name }}</h4>
|
||||
<div class="team-actions">
|
||||
<button @click.stop="editTeam(team)" class="btn-edit" title="Bearbeiten">
|
||||
✏️
|
||||
</button>
|
||||
<button @click.stop="deleteTeam(team)" class="btn-delete" title="Löschen">
|
||||
🗑️
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="team-info">
|
||||
<div class="info-row">
|
||||
<span class="label">Spielklasse:</span>
|
||||
<span class="value">
|
||||
{{ team.league ? team.league.name : 'Keine Zuordnung' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="label">Saison:</span>
|
||||
<span class="value">{{ team.season?.season || 'Unbekannt' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="label">Erstellt:</span>
|
||||
<span class="value">{{ formatDate(team.createdAt) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
import SeasonSelector from '../components/SeasonSelector.vue';
|
||||
import apiClient from '../apiClient.js';
|
||||
|
||||
export default {
|
||||
name: 'TeamManagementView',
|
||||
components: {
|
||||
SeasonSelector
|
||||
},
|
||||
setup() {
|
||||
const store = useStore();
|
||||
|
||||
// Reactive data
|
||||
const teams = ref([]);
|
||||
const leagues = ref([]);
|
||||
const teamFormIsOpen = ref(false);
|
||||
const teamToEdit = ref(null);
|
||||
const newTeamName = ref('');
|
||||
const newLeagueId = ref('');
|
||||
const selectedSeasonId = ref(null);
|
||||
const currentSeason = ref(null);
|
||||
|
||||
// Computed
|
||||
const selectedClub = computed(() => store.state.currentClub);
|
||||
const authToken = computed(() => store.state.token);
|
||||
const filteredLeagues = computed(() => {
|
||||
if (!selectedSeasonId.value) return [];
|
||||
return leagues.value.filter(league => league.seasonId == selectedSeasonId.value);
|
||||
});
|
||||
|
||||
// Methods
|
||||
const toggleNewTeam = () => {
|
||||
teamFormIsOpen.value = !teamFormIsOpen.value;
|
||||
if (!teamFormIsOpen.value) {
|
||||
resetNewTeam();
|
||||
}
|
||||
};
|
||||
|
||||
const resetToNewTeam = () => {
|
||||
teamToEdit.value = null;
|
||||
resetNewTeam();
|
||||
};
|
||||
|
||||
const resetNewTeam = () => {
|
||||
newTeamName.value = '';
|
||||
newLeagueId.value = '';
|
||||
};
|
||||
|
||||
const loadTeams = async () => {
|
||||
if (!selectedClub.value || !selectedSeasonId.value) {
|
||||
console.log('TeamManagementView: Skipping loadTeams - club:', selectedClub.value, 'season:', selectedSeasonId.value);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('TeamManagementView: Loading club teams for club:', selectedClub.value, 'season:', selectedSeasonId.value);
|
||||
const response = await apiClient.get(`/club-teams/club/${selectedClub.value}?seasonid=${selectedSeasonId.value}`);
|
||||
console.log('TeamManagementView: Loaded club teams:', response.data);
|
||||
teams.value = response.data;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Club-Teams:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const loadLeagues = async () => {
|
||||
if (!selectedClub.value) return;
|
||||
|
||||
try {
|
||||
console.log('TeamManagementView: Loading leagues for club:', selectedClub.value, 'season:', selectedSeasonId.value);
|
||||
const seasonParam = selectedSeasonId.value ? `?seasonid=${selectedSeasonId.value}` : '';
|
||||
const response = await apiClient.get(`/club-teams/leagues/${selectedClub.value}${seasonParam}`);
|
||||
console.log('TeamManagementView: Loaded leagues:', response.data);
|
||||
leagues.value = response.data;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Spielklassen:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const addNewTeam = async () => {
|
||||
if (!newTeamName.value.trim() || !selectedClub.value || !selectedSeasonId.value) return;
|
||||
|
||||
try {
|
||||
const teamData = {
|
||||
name: newTeamName.value.trim(),
|
||||
leagueId: newLeagueId.value || null,
|
||||
seasonId: selectedSeasonId.value
|
||||
};
|
||||
|
||||
if (teamToEdit.value) {
|
||||
// Bearbeitung eines bestehenden Teams
|
||||
await apiClient.put(`/club-teams/${teamToEdit.value.id}`, teamData);
|
||||
await loadTeams();
|
||||
resetNewTeam();
|
||||
teamFormIsOpen.value = false;
|
||||
teamToEdit.value = null;
|
||||
} else {
|
||||
// Erstellen eines neuen Teams
|
||||
const response = await apiClient.post(`/club-teams/club/${selectedClub.value}`, teamData);
|
||||
const newTeam = response.data;
|
||||
|
||||
await loadTeams();
|
||||
|
||||
// Neues Team automatisch auswählen und bearbeiten
|
||||
teamToEdit.value = newTeam;
|
||||
newTeamName.value = newTeam.name;
|
||||
newLeagueId.value = newTeam.leagueId || '';
|
||||
teamFormIsOpen.value = true; // Formular bleibt offen für weitere Bearbeitung
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Speichern des Club-Teams:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const editTeam = (team) => {
|
||||
teamToEdit.value = team;
|
||||
newTeamName.value = team.name;
|
||||
newLeagueId.value = team.leagueId || '';
|
||||
teamFormIsOpen.value = true;
|
||||
};
|
||||
|
||||
const deleteTeam = async (team) => {
|
||||
if (!confirm(`Möchten Sie das Club-Team "${team.name}" wirklich löschen?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await apiClient.delete(`/club-teams/${team.id}`);
|
||||
await loadTeams();
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Löschen des Club-Teams:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const uploadCodeList = () => {
|
||||
if (!teamToEdit.value) return;
|
||||
|
||||
// Erstelle ein verstecktes File-Input-Element
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.pdf,.doc,.docx,.txt,.csv';
|
||||
input.onchange = async (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
try {
|
||||
console.log('Code-Liste hochladen für Team:', teamToEdit.value.name, 'Datei:', file.name);
|
||||
// TODO: Implementiere Upload-Logik für Code-Liste
|
||||
alert(`Code-Liste "${file.name}" würde für Team "${teamToEdit.value.name}" hochgeladen werden.`);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Hochladen der Code-Liste:', error);
|
||||
alert('Fehler beim Hochladen der Code-Liste');
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
};
|
||||
|
||||
const uploadPinList = () => {
|
||||
if (!teamToEdit.value) return;
|
||||
|
||||
// Erstelle ein verstecktes File-Input-Element
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.pdf,.doc,.docx,.txt,.csv';
|
||||
input.onchange = async (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
try {
|
||||
console.log('Pin-Liste hochladen für Team:', teamToEdit.value.name, 'Datei:', file.name);
|
||||
// TODO: Implementiere Upload-Logik für Pin-Liste
|
||||
alert(`Pin-Liste "${file.name}" würde für Team "${teamToEdit.value.name}" hochgeladen werden.`);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Hochladen der Pin-Liste:', error);
|
||||
alert('Fehler beim Hochladen der Pin-Liste');
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
};
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
return new Date(dateString).toLocaleDateString('de-DE');
|
||||
};
|
||||
|
||||
const onSeasonChange = (season) => {
|
||||
currentSeason.value = season;
|
||||
loadTeams();
|
||||
loadLeagues();
|
||||
};
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
console.log('TeamManagementView: onMounted - Store state:', {
|
||||
currentClub: store.state.currentClub,
|
||||
token: store.state.token,
|
||||
username: store.state.username
|
||||
});
|
||||
// Lade Ligen beim ersten Laden der Seite (ohne Saison-Filter)
|
||||
loadLeagues();
|
||||
});
|
||||
|
||||
return {
|
||||
teams,
|
||||
leagues,
|
||||
teamFormIsOpen,
|
||||
teamToEdit,
|
||||
newTeamName,
|
||||
newLeagueId,
|
||||
selectedSeasonId,
|
||||
currentSeason,
|
||||
filteredLeagues,
|
||||
toggleNewTeam,
|
||||
resetToNewTeam,
|
||||
resetNewTeam,
|
||||
addNewTeam,
|
||||
editTeam,
|
||||
deleteTeam,
|
||||
uploadCodeList,
|
||||
uploadPinList,
|
||||
formatDate,
|
||||
onSeasonChange
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.newteam {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.toggle-new-team {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.toggle-new-team span {
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
color: var(--primary-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.add {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.new-team-form {
|
||||
background: var(--background-light);
|
||||
padding: 1.5rem;
|
||||
border-radius: var(--border-radius);
|
||||
border: 1px solid var(--border-color);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.new-team-form label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.new-team-form label span {
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.new-team-form input,
|
||||
.new-team-form select {
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius-small);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.new-team-form input:focus,
|
||||
.new-team-form select:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 2px var(--primary-light);
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.form-actions button {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: var(--border-radius-small);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.form-actions button:not(.cancel-action) {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.form-actions button:not(.cancel-action):hover {
|
||||
background: var(--primary-dark);
|
||||
}
|
||||
|
||||
.form-actions button:not(.cancel-action):disabled {
|
||||
background: var(--text-muted);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.cancel-action {
|
||||
background: var(--background-light);
|
||||
color: var(--text-color);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.cancel-action:hover {
|
||||
background: var(--border-color);
|
||||
}
|
||||
|
||||
.teams-list h3 {
|
||||
margin-bottom: 1rem;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.no-teams {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: var(--text-muted);
|
||||
background: var(--background-light);
|
||||
border-radius: var(--border-radius);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.teams-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.team-card {
|
||||
background: white;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 1.5rem;
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.team-card:hover {
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: var(--shadow-small);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.team-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.team-header h4 {
|
||||
margin: 0;
|
||||
color: var(--text-color);
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.team-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.team-actions button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0.25rem;
|
||||
border-radius: var(--border-radius-small);
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.btn-edit:hover {
|
||||
background: var(--primary-light);
|
||||
}
|
||||
|
||||
.btn-delete:hover {
|
||||
background: #fee;
|
||||
}
|
||||
|
||||
.team-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info-row .label {
|
||||
font-weight: 600;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.info-row .value {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.teams-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.team-header {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.team-actions {
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
/* Upload-Buttons Styles */
|
||||
.upload-actions {
|
||||
margin-top: 2rem;
|
||||
padding: 1.5rem;
|
||||
background: var(--background-light);
|
||||
border-radius: var(--border-radius-medium);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.upload-actions h4 {
|
||||
margin: 0 0 1rem 0;
|
||||
color: var(--text-color);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.upload-buttons {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.upload-btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: var(--border-radius-small);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.code-list-btn {
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.code-list-btn:hover {
|
||||
background: #45a049;
|
||||
}
|
||||
|
||||
.pin-list-btn {
|
||||
background: #FF9800;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.pin-list-btn:hover {
|
||||
background: #e68900;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.upload-buttons {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.upload-btn {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user