From 5eff1d63aa436191d73b2cc37fe3b5b0bd5129e2 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Tue, 31 Mar 2026 13:44:28 +0200 Subject: [PATCH] feat(ClubTeam): enhance club team management with lineup features and member eligibility - Added teamGender and teamAgeGroup fields to ClubTeam model for better categorization. - Updated create and update club team endpoints to handle new fields and default values. - Implemented getClubTeamLineup and updateClubTeamLineup functions for managing team lineups. - Enhanced member management with adultReleaseApproved and adultReserveApproved fields in Member model. - Updated frontend views to support new lineup features and member eligibility flags. - Improved localization for new terms related to team management and member eligibility across multiple languages. --- .gitea/workflows/deploy.sh | 52 ++ backend/controllers/clubTeamController.js | 54 +- backend/controllers/memberController.js | 4 +- backend/models/ClubTeam.js | 12 + backend/models/ClubTeamMember.js | 47 ++ backend/models/Member.js | 14 + backend/models/index.js | 7 + backend/routes/clubTeamRoutes.js | 8 +- backend/server.js | 2 +- backend/services/clubTeamService.js | 65 ++ backend/services/memberService.js | 6 +- frontend/src/i18n/locales/de-CH.json | 23 +- frontend/src/i18n/locales/de-extended.json | 22 +- frontend/src/i18n/locales/de.json | 24 +- frontend/src/i18n/locales/en-AU.json | 23 +- frontend/src/i18n/locales/en-GB.json | 22 +- frontend/src/i18n/locales/en-US.json | 23 +- frontend/src/i18n/locales/es.json | 20 +- frontend/src/i18n/locales/fil.json | 20 +- frontend/src/i18n/locales/fr.json | 20 +- frontend/src/i18n/locales/it.json | 20 +- frontend/src/i18n/locales/ja.json | 20 +- frontend/src/i18n/locales/pl.json | 20 +- frontend/src/i18n/locales/th.json | 20 +- frontend/src/i18n/locales/tl.json | 20 +- frontend/src/i18n/locales/zh.json | 20 +- frontend/src/views/MembersView.vue | 46 ++ frontend/src/views/TeamManagementView.vue | 763 +++++++++++++++++++-- 28 files changed, 1325 insertions(+), 72 deletions(-) create mode 100644 .gitea/workflows/deploy.sh create mode 100644 backend/models/ClubTeamMember.js diff --git a/.gitea/workflows/deploy.sh b/.gitea/workflows/deploy.sh new file mode 100644 index 00000000..4acfed87 --- /dev/null +++ b/.gitea/workflows/deploy.sh @@ -0,0 +1,52 @@ +name: Deploy tt-tagebuch + +on: + push: + branches: + - main + +jobs: + deploy: + runs-on: ubuntu-latest + + env: + SSH_HOST: ${{ vars.PROD_HOST }} + SSH_PORT: ${{ vars.PROD_PORT }} + SSH_USER: ${{ vars.PROD_USER }} + + steps: + - name: Show resolved non-secret config + run: | + echo "SSH_HOST=$SSH_HOST" + echo "SSH_PORT=$SSH_PORT" + echo "SSH_USER=$SSH_USER" + + - name: Prepare SSH + run: | + set -e + mkdir -p ~/.ssh + printf "%s" "${{ secrets.PROD_SSH_KEY }}" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + ssh-keyscan -p "$SSH_PORT" "$SSH_HOST" >> ~/.ssh/known_hosts + + - name: Test SSH connection + run: | + set -e + ssh -i ~/.ssh/id_ed25519 \ + -o StrictHostKeyChecking=no \ + -o BatchMode=yes \ + -o ConnectTimeout=10 \ + -p "$SSH_PORT" \ + "$SSH_USER@$SSH_HOST" \ + "echo SSH OK" + + - name: Run deployment script + run: | + set -e + ssh -i ~/.ssh/id_ed25519 \ + -o StrictHostKeyChecking=no \ + -o BatchMode=yes \ + -o ConnectTimeout=10 \ + -p "$SSH_PORT" \ + "$SSH_USER@$SSH_HOST" \ + "/usr/local/bin/actualize-tagebuch.sh" \ No newline at end of file diff --git a/backend/controllers/clubTeamController.js b/backend/controllers/clubTeamController.js index 680af1cd..eb406415 100644 --- a/backend/controllers/clubTeamController.js +++ b/backend/controllers/clubTeamController.js @@ -42,7 +42,7 @@ export const createClubTeam = async (req, res) => { try { const { authcode: token } = req.headers; const { clubid: clubId } = req.params; - const { name, leagueId, seasonId } = req.body; + const { name, leagueId, seasonId, teamGender, teamAgeGroup } = req.body; const user = await getUserByToken(token); @@ -54,7 +54,9 @@ export const createClubTeam = async (req, res) => { name, clubId: parseInt(clubId), leagueId: leagueId ? parseInt(leagueId) : null, - seasonId: seasonId ? parseInt(seasonId) : null + seasonId: seasonId ? parseInt(seasonId) : null, + teamGender: teamGender || 'open', + teamAgeGroup: teamAgeGroup || 'adult' }; const newClubTeam = await ClubTeamService.createClubTeam(clubTeamData); @@ -70,7 +72,7 @@ export const updateClubTeam = async (req, res) => { try { const { authcode: token } = req.headers; const { clubteamid: clubTeamId } = req.params; - const { name, leagueId, seasonId } = req.body; + const { name, leagueId, seasonId, teamGender, teamAgeGroup } = req.body; const user = await getUserByToken(token); @@ -78,6 +80,8 @@ export const updateClubTeam = async (req, res) => { if (name !== undefined) updateData.name = name; if (leagueId !== undefined) updateData.leagueId = leagueId ? parseInt(leagueId) : null; if (seasonId !== undefined) updateData.seasonId = seasonId ? parseInt(seasonId) : null; + if (teamGender !== undefined) updateData.teamGender = teamGender || 'open'; + if (teamAgeGroup !== undefined) updateData.teamAgeGroup = teamAgeGroup || 'adult'; const success = await ClubTeamService.updateClubTeam(clubTeamId, updateData); if (!success) { @@ -126,3 +130,47 @@ export const getLeagues = async (req, res) => { res.status(500).json({ error: "internalerror" }); } }; + +export const getClubTeamLineup = async (req, res) => { + try { + const { authcode: token } = req.headers; + const { clubteamid: clubTeamId } = req.params; + const lineupHalf = req.query.half === 'second_half' ? 'second_half' : 'first_half'; + await getUserByToken(token); + + const clubTeam = await ClubTeamService.getClubTeamById(clubTeamId); + if (!clubTeam) { + return res.status(404).json({ error: "notfound" }); + } + + const lineup = await ClubTeamService.getTeamLineup(clubTeamId, lineupHalf); + res.status(200).json(lineup); + } catch (error) { + console.error('[getClubTeamLineup] - Error:', error); + res.status(500).json({ error: "internalerror" }); + } +}; + +export const updateClubTeamLineup = async (req, res) => { + try { + const { authcode: token } = req.headers; + const { clubteamid: clubTeamId } = req.params; + const { assignments, lineupHalf: requestLineupHalf } = req.body; + const lineupHalf = requestLineupHalf === 'second_half' || req.query.half === 'second_half' ? 'second_half' : 'first_half'; + await getUserByToken(token); + + const clubTeam = await ClubTeamService.getClubTeamById(clubTeamId); + if (!clubTeam) { + return res.status(404).json({ error: "notfound" }); + } + + const lineup = await ClubTeamService.replaceTeamLineup(clubTeamId, assignments, lineupHalf); + res.status(200).json(lineup); + } catch (error) { + console.error('[updateClubTeamLineup] - Error:', error); + if (error?.code === 'TEAM_LINEUP_TABLE_MISSING') { + return res.status(500).json({ error: 'teamlineuptablemissing' }); + } + res.status(500).json({ error: "internalerror" }); + } +}; diff --git a/backend/controllers/memberController.js b/backend/controllers/memberController.js index cddce0da..7b9222b7 100644 --- a/backend/controllers/memberController.js +++ b/backend/controllers/memberController.js @@ -29,11 +29,11 @@ const getWaitingApprovals = async(req, res) => { const setClubMembers = async (req, res) => { try { const { id: memberId, firstname: firstName, lastname: lastName, street, city, postalCode, birthdate, phone, email, active, - testMembership, picsInInternetAllowed, gender, ttr, qttr, memberFormHandedOver, contacts } = req.body; + testMembership, picsInInternetAllowed, gender, ttr, qttr, memberFormHandedOver, adultReleaseApproved, adultReserveApproved, contacts } = req.body; const { id: clubId } = req.params; const { authcode: userToken } = req.headers; const addResult = await MemberService.setClubMember(userToken, clubId, memberId, firstName, lastName, street, city, postalCode, birthdate, - phone, email, active, testMembership, picsInInternetAllowed, gender, ttr, qttr, memberFormHandedOver, contacts); + phone, email, active, testMembership, picsInInternetAllowed, gender, ttr, qttr, memberFormHandedOver, adultReleaseApproved, adultReserveApproved, contacts); // Emit Socket-Event wenn Member erfolgreich erstellt/aktualisiert wurde if (addResult.status === 200) { diff --git a/backend/models/ClubTeam.js b/backend/models/ClubTeam.js index 412268e9..089daf63 100644 --- a/backend/models/ClubTeam.js +++ b/backend/models/ClubTeam.js @@ -51,6 +51,18 @@ const ClubTeam = sequelize.define('ClubTeam', { comment: 'Team ID from myTischtennis (e.g. 2995094)', field: 'my_tischtennis_team_id' }, + teamGender: { + type: DataTypes.ENUM('open', 'female'), + allowNull: false, + defaultValue: 'open', + field: 'team_gender' + }, + teamAgeGroup: { + type: DataTypes.ENUM('adult', 'J19', 'J17', 'J15', 'J13', 'J11'), + allowNull: false, + defaultValue: 'adult', + field: 'team_age_group' + }, }, { underscored: true, tableName: 'club_team', diff --git a/backend/models/ClubTeamMember.js b/backend/models/ClubTeamMember.js new file mode 100644 index 00000000..fa86f41f --- /dev/null +++ b/backend/models/ClubTeamMember.js @@ -0,0 +1,47 @@ +import { DataTypes } from 'sequelize'; +import sequelize from '../database.js'; + +const ClubTeamMember = sequelize.define('ClubTeamMember', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false, + }, + clubTeamId: { + type: DataTypes.INTEGER, + allowNull: false, + field: 'club_team_id', + }, + memberId: { + type: DataTypes.INTEGER, + allowNull: false, + field: 'member_id', + }, + lineupHalf: { + type: DataTypes.ENUM('first_half', 'second_half'), + allowNull: false, + defaultValue: 'first_half', + field: 'lineup_half', + }, + position: { + type: DataTypes.INTEGER, + allowNull: false, + }, +}, { + underscored: true, + tableName: 'club_team_member', + timestamps: true, + indexes: [ + { + unique: true, + fields: ['club_team_id', 'lineup_half', 'member_id'], + }, + { + unique: true, + fields: ['club_team_id', 'lineup_half', 'position'], + } + ] +}); + +export default ClubTeamMember; diff --git a/backend/models/Member.js b/backend/models/Member.js index 3207a00a..0a4799da 100644 --- a/backend/models/Member.js +++ b/backend/models/Member.js @@ -161,6 +161,20 @@ const Member = sequelize.define('Member', { field: 'member_form_handed_over', comment: 'Mitgliedsformular ausgehändigt' }, + adultReleaseApproved: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + field: 'adult_release_approved', + comment: 'Jugendspieler mit Freigabe fuer Erwachsene' + }, + adultReserveApproved: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + field: 'adult_reserve_approved', + comment: 'Jugendspieler als Ersatz bei Erwachsenen zugelassen' + }, myTischtennisPlayerId: { type: DataTypes.STRING, allowNull: true, diff --git a/backend/models/index.js b/backend/models/index.js index 54609ff3..0b8748d3 100644 --- a/backend/models/index.js +++ b/backend/models/index.js @@ -20,6 +20,7 @@ import Match from './Match.js'; import League from './League.js'; import Team from './Team.js'; import ClubTeam from './ClubTeam.js'; +import ClubTeamMember from './ClubTeamMember.js'; import TeamDocument from './TeamDocument.js'; import Season from './Season.js'; import Location from './Location.js'; @@ -168,6 +169,11 @@ ClubTeam.belongsTo(League, { foreignKey: 'leagueId', as: 'league' }); Season.hasMany(ClubTeam, { foreignKey: 'seasonId', as: 'clubTeams' }); ClubTeam.belongsTo(Season, { foreignKey: 'seasonId', as: 'season' }); +ClubTeam.hasMany(ClubTeamMember, { foreignKey: 'clubTeamId', as: 'lineupEntries' }); +ClubTeamMember.belongsTo(ClubTeam, { foreignKey: 'clubTeamId', as: 'clubTeam' }); +Member.hasMany(ClubTeamMember, { foreignKey: 'memberId', as: 'clubTeamAssignments' }); +ClubTeamMember.belongsTo(Member, { foreignKey: 'memberId', as: 'member' }); + // TeamDocument relationships ClubTeam.hasMany(TeamDocument, { foreignKey: 'clubTeamId', as: 'documents' }); TeamDocument.belongsTo(ClubTeam, { foreignKey: 'clubTeamId', as: 'clubTeam' }); @@ -406,6 +412,7 @@ export { League, Team, ClubTeam, + ClubTeamMember, TeamDocument, Group, GroupActivity, diff --git a/backend/routes/clubTeamRoutes.js b/backend/routes/clubTeamRoutes.js index cee1b6ab..4aaf3f48 100644 --- a/backend/routes/clubTeamRoutes.js +++ b/backend/routes/clubTeamRoutes.js @@ -6,7 +6,9 @@ import { createClubTeam, updateClubTeam, deleteClubTeam, - getLeagues + getLeagues, + getClubTeamLineup, + updateClubTeamLineup } from '../controllers/clubTeamController.js'; const router = express.Router(); @@ -23,6 +25,10 @@ router.get('/leagues/:clubid', authenticate, getLeagues); // Get a specific club team router.get('/:clubteamid', authenticate, getClubTeam); +// Get/save lineup for a specific club team +router.get('/:clubteamid/lineup', authenticate, getClubTeamLineup); +router.put('/:clubteamid/lineup', authenticate, updateClubTeamLineup); + // Update a club team router.put('/:clubteamid', authenticate, updateClubTeam); diff --git a/backend/server.js b/backend/server.js index 357a4a11..93926681 100644 --- a/backend/server.js +++ b/backend/server.js @@ -11,7 +11,7 @@ import { initializeSocketIO } from './services/socketService.js'; import { User, Log, Club, UserClub, Member, DiaryDate, Participant, Activity, MemberNote, DiaryNote, DiaryTag, MemberDiaryTag, DiaryDateTag, DiaryMemberNote, DiaryMemberTag, - PredefinedActivity, PredefinedActivityImage, DiaryDateActivity, DiaryMemberActivity, Match, League, Team, ClubTeam, TeamDocument, Group, + PredefinedActivity, PredefinedActivityImage, DiaryDateActivity, DiaryMemberActivity, Match, League, Team, ClubTeam, ClubTeamMember, TeamDocument, Group, GroupActivity, Tournament, TournamentGroup, TournamentMatch, TournamentResult, TournamentMember, Accident, UserToken, OfficialTournament, OfficialCompetition, OfficialCompetitionMember, MyTischtennis, ClickTtAccount, MyTischtennisUpdateHistory, MyTischtennisFetchLog, ApiLog, MemberTransferConfig, MemberContact, MemberTtrHistory , MemberOrder, MemberOrderHistory diff --git a/backend/services/clubTeamService.js b/backend/services/clubTeamService.js index 0cb92ebc..12f4ff3c 100644 --- a/backend/services/clubTeamService.js +++ b/backend/services/clubTeamService.js @@ -1,10 +1,16 @@ import ClubTeam from '../models/ClubTeam.js'; +import ClubTeamMember from '../models/ClubTeamMember.js'; import League from '../models/League.js'; +import Member from '../models/Member.js'; import Season from '../models/Season.js'; import SeasonService from './seasonService.js'; import { devLog } from '../utils/logger.js'; class ClubTeamService { + static isMissingTeamLineupTable(error) { + return error?.original?.code === 'ER_NO_SUCH_TABLE' + && String(error?.original?.sqlMessage || '').includes('club_team_member'); + } /** * Holt alle ClubTeams für einen Verein, optional gefiltert nach Saison. * Wenn keine Saison-ID angegeben ist, wird die aktuelle Saison verwendet. @@ -36,6 +42,8 @@ class ClubTeamService { leagueId: clubTeam.leagueId, seasonId: clubTeam.seasonId, myTischtennisTeamId: clubTeam.myTischtennisTeamId, + teamGender: clubTeam.teamGender, + teamAgeGroup: clubTeam.teamAgeGroup, createdAt: clubTeam.createdAt, updatedAt: clubTeam.updatedAt, league: { name: 'Unbekannt' }, @@ -178,6 +186,63 @@ class ClubTeamService { throw error; } } + + static async getTeamLineup(clubTeamId, lineupHalf = 'first_half') { + try { + return await ClubTeamMember.findAll({ + where: { clubTeamId, lineupHalf }, + include: [ + { + model: Member, + as: 'member' + } + ], + order: [['position', 'ASC']] + }); + } catch (error) { + if (this.isMissingTeamLineupTable(error)) { + return []; + } + console.error('[ClubTeamService.getTeamLineup] - Error:', error); + throw error; + } + } + + static async replaceTeamLineup(clubTeamId, assignments, lineupHalf = 'first_half') { + try { + await ClubTeamMember.destroy({ + where: { clubTeamId, lineupHalf } + }); + + if (!Array.isArray(assignments) || assignments.length === 0) { + return []; + } + + const normalizedAssignments = assignments + .filter((entry) => entry && entry.memberId) + .map((entry, index) => ({ + clubTeamId, + lineupHalf, + memberId: Number(entry.memberId), + position: Number(entry.position) || (index + 1) + })); + + if (normalizedAssignments.length === 0) { + return []; + } + + await ClubTeamMember.bulkCreate(normalizedAssignments); + return await this.getTeamLineup(clubTeamId, lineupHalf); + } catch (error) { + if (this.isMissingTeamLineupTable(error)) { + const tableMissingError = new Error('teamlineuptablemissing'); + tableMissingError.code = 'TEAM_LINEUP_TABLE_MISSING'; + throw tableMissingError; + } + console.error('[ClubTeamService.replaceTeamLineup] - Error:', error); + throw error; + } + } } export default ClubTeamService; diff --git a/backend/services/memberService.js b/backend/services/memberService.js index e9e80987..ace87402 100644 --- a/backend/services/memberService.js +++ b/backend/services/memberService.js @@ -106,7 +106,7 @@ class MemberService { } async setClubMember(userToken, clubId, memberId, firstName, lastName, street, city, postalCode, birthdate, phone, email, active = true, testMembership = false, - picsInInternetAllowed = false, gender = 'unknown', ttr = null, qttr = null, memberFormHandedOver = false, contacts = []) { + picsInInternetAllowed = false, gender = 'unknown', ttr = null, qttr = null, memberFormHandedOver = false, adultReleaseApproved = false, adultReserveApproved = false, contacts = []) { try { await checkAccess(userToken, clubId); let member = null; @@ -130,6 +130,8 @@ class MemberService { if (ttr !== undefined) member.ttr = ttr; if (qttr !== undefined) member.qttr = qttr; member.memberFormHandedOver = !!memberFormHandedOver; + member.adultReleaseApproved = !!adultReleaseApproved; + member.adultReserveApproved = !!adultReserveApproved; await member.save(); // Update contacts if provided @@ -173,6 +175,8 @@ class MemberService { ttr: ttr, qttr: qttr, memberFormHandedOver: !!memberFormHandedOver, + adultReleaseApproved: !!adultReleaseApproved, + adultReserveApproved: !!adultReserveApproved, }); // Create contacts if provided diff --git a/frontend/src/i18n/locales/de-CH.json b/frontend/src/i18n/locales/de-CH.json index 5365c4fe..9d36b0f2 100644 --- a/frontend/src/i18n/locales/de-CH.json +++ b/frontend/src/i18n/locales/de-CH.json @@ -4,6 +4,7 @@ "title": "Trainingstagebuch" }, "common": { + "period": "Zeitraum", "loading": "Lade...", "save": "Speichere", "saved": "Gespeichert", @@ -28,6 +29,7 @@ "time": "Zyt", "new": "Neu", "update": "Aktualisiere", + "move": "Verschiebe", "refresh": "Neu lade", "create": "Erstelle", "remove": "Entferne", @@ -449,6 +451,8 @@ "picsInInternetAllowed": "Pics in Internet erlaubt", "testMembership": "Testmitgliedschaft", "memberFormHandedOver": "Mitgliedsformular ausgehändigt", + "adultReleaseApproved": "Freigabe Erwachsene", + "adultReserveApproved": "Ersatz bei Erwachsenen", "trainingGroups": "Trainingsgruppen", "noGroupsAssigned": "Keine Gruppen zugeordnet", "noGroupsAvailable": "Keine Gruppen verfügbar", @@ -463,7 +467,7 @@ "clearFields": "Felder leeren", "showInactiveMembers": "Inaktive Mitglieder anzeigen", "ageGroup": "Altersklasse", - "adults": "Erwachsene (20+)", + "adults": "Erwachsene (18+)", "j19": "J19 (19 und jünger)", "j17": "J17 (17 und jünger)", "j15": "J15 (15 und jünger)", @@ -868,5 +872,22 @@ "targetMiddleShort": "Mitte kurz", "targetBackhandShort": "Rückhand kurz", "toTarget": "nach" + }, + "teamManagement": { + "lineupProposal": "Mannschaftsmeldung nach QTTR", + "eligibility": "Einsatz", + "eligibilityRegular": "Regulär", + "eligibilityAdultRelease": "Freigabe Erwachsene", + "eligibilityAdultReserve": "Ersatz Erwachsene", + "eligibilityAdultReleaseAndReserve": "Freigabe + Ersatz Erwachsene", + "selectedLineup": "Gemeldete Spieler", + "availableLineupMembers": "Verfügbare Spieler", + "lineupEmpty": "Noch keine Spieler für diese Mannschaft gemeldet.", + "lineupSaveError": "Mannschaftsmeldung konnte nicht gespeichert werden.", + "lineupValidationTooLargeGap": "{higher} hat mehr als 30 QTTR Punkte Vorsprung vor {lower}. Diese Reihenfolge bitte korrigieren.", + "firstHalf": "Vorrunde", + "firstHalfFull": "Vorrunde (Juli - Dezember)", + "secondHalf": "Rückrunde", + "secondHalfFull": "Rückrunde (ab 1. Januar)" } } diff --git a/frontend/src/i18n/locales/de-extended.json b/frontend/src/i18n/locales/de-extended.json index 381474bb..a15f7389 100644 --- a/frontend/src/i18n/locales/de-extended.json +++ b/frontend/src/i18n/locales/de-extended.json @@ -53,7 +53,8 @@ "days": "Tage", "weeks": "Wochen", "months": "Monate", - "years": "Jahre" + "years": "Jahre", + "period": "Zeitraum" }, "navigation": { "home": "Startseite", @@ -203,6 +204,8 @@ "picsInInternetAllowed": "Pics in Internet erlaubt", "testMembership": "Testmitgliedschaft", "memberFormHandedOver": "Mitgliedsformular ausgehändigt", + "adultReleaseApproved": "Freigabe Erwachsene", + "adultReserveApproved": "Ersatz bei Erwachsenen", "trainingGroups": "Trainingsgruppen", "subtitle": "Mitglieder suchen, filtern und direkt bearbeiten.", "closeEditor": "Editor schließen", @@ -574,5 +577,22 @@ "cookies": "Cookies/Local Storage", "logData": "Logdaten", "recipients": "Empfänger" + }, + "teamManagement": { + "lineupProposal": "Mannschaftsmeldung nach QTTR", + "eligibility": "Einsatz", + "eligibilityRegular": "Regulär", + "eligibilityAdultRelease": "Freigabe Erwachsene", + "eligibilityAdultReserve": "Ersatz Erwachsene", + "eligibilityAdultReleaseAndReserve": "Freigabe + Ersatz Erwachsene", + "selectedLineup": "Gemeldete Spieler", + "availableLineupMembers": "Verfügbare Spieler", + "lineupEmpty": "Noch keine Spieler für diese Mannschaft gemeldet.", + "lineupSaveError": "Mannschaftsmeldung konnte nicht gespeichert werden.", + "lineupValidationTooLargeGap": "{higher} hat mehr als 30 QTTR Punkte Vorsprung vor {lower}. Diese Reihenfolge bitte korrigieren.", + "firstHalf": "Vorrunde", + "firstHalfFull": "Vorrunde (Juli - Dezember)", + "secondHalf": "Rückrunde", + "secondHalfFull": "Rückrunde (ab 1. Januar)" } } diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json index 995d0f22..fcfa0ad4 100644 --- a/frontend/src/i18n/locales/de.json +++ b/frontend/src/i18n/locales/de.json @@ -28,6 +28,7 @@ "time": "Zeit", "new": "Neu", "update": "Aktualisieren", + "move": "Verschieben", "refresh": "Neu laden", "create": "Erstellen", "remove": "Entfernen", @@ -57,7 +58,8 @@ "weeks": "Wochen", "months": "Monate", "years": "Jahre", - "ok": "OK" + "ok": "OK", + "period": "Zeitraum" }, "navigation": { "home": "Startseite", @@ -224,6 +226,8 @@ "picsInInternetAllowed": "Pics in Internet erlaubt", "testMembership": "Testmitgliedschaft", "memberFormHandedOver": "Mitgliedsformular ausgehändigt", + "adultReleaseApproved": "Freigabe Erwachsene", + "adultReserveApproved": "Ersatz bei Erwachsenen", "trainingGroups": "Trainingsgruppen", "noGroupsAssigned": "Keine Gruppen zugeordnet", "noGroupsAvailable": "Keine Gruppen verfügbar", @@ -238,7 +242,7 @@ "clearFields": "Felder leeren", "showInactiveMembers": "Inaktive Mitglieder anzeigen", "ageGroup": "Altersklasse", - "adults": "Erwachsene (20+)", + "adults": "Erwachsene (18+)", "j19": "J19 (19 und jünger)", "j17": "J17 (17 und jünger)", "j15": "J15 (15 und jünger)", @@ -1323,6 +1327,22 @@ "playerStats": "Spieleinsätze", "playerStatsIntro": "Schneller Überblick über Einsätze der Mannschaft in dieser Saison.", "lineupProposal": "Mannschaftsmeldung nach QTTR", + "teamGender": "Geschlecht", + "teamAgeGroup": "Altersklasse", + "teamGenderOpen": "Offen", + "teamGenderFemale": "Weiblich", + "eligibility": "Einsatz", + "eligibilityRegular": "Regulär", + "eligibilityAdultRelease": "Freigabe Erwachsene", + "eligibilityAdultReserve": "Ersatz Erwachsene", + "eligibilityAdultReleaseAndReserve": "Freigabe + Ersatz Erwachsene", + "selectedLineup": "Gemeldete Spieler", + "availableLineupMembers": "Verfügbare Spieler", + "lineupEmpty": "Noch keine Spieler für diese Mannschaft gemeldet.", + "lineupUnsavedChanges": "Ungespeicherte Änderungen in der Mannschaftsmeldung.", + "lineupSaved": "Mannschaftsmeldung gespeichert.", + "lineupSaveError": "Mannschaftsmeldung konnte nicht gespeichert werden.", + "lineupValidationTooLargeGap": "{higher} hat mehr als 30 QTTR Punkte Vorsprung vor {lower}. Diese Reihenfolge bitte korrigieren.", "refreshStats": "Aktualisieren", "loadingStats": "Lade Statistiken...", "noPlayerStats": "Keine Spieleinsätze erfasst.", diff --git a/frontend/src/i18n/locales/en-AU.json b/frontend/src/i18n/locales/en-AU.json index 62e2f0fc..d9a179a7 100644 --- a/frontend/src/i18n/locales/en-AU.json +++ b/frontend/src/i18n/locales/en-AU.json @@ -4,6 +4,7 @@ "title": "Training Diary" }, "common": { + "period": "Period", "loading": "Loading...", "save": "Save", "saved": "Saved", @@ -29,6 +30,7 @@ "time": "Time", "new": "New", "update": "Update", + "move": "Move", "refresh": "Reload", "create": "Create", "remove": "Remove", @@ -449,6 +451,8 @@ "picsInInternetAllowed": "Pictures allowed online", "testMembership": "Trial membership", "memberFormHandedOver": "Membership form handed over", + "adultReleaseApproved": "Approved for adults", + "adultReserveApproved": "Adult reserve", "trainingGroups": "Training groups", "noGroupsAssigned": "No groups assigned", "noGroupsAvailable": "No groups available", @@ -463,7 +467,7 @@ "clearFields": "Clear fields", "showInactiveMembers": "Show inactive members", "ageGroup": "Age group", - "adults": "Adults (20+)", + "adults": "Adults (18+)", "j19": "U19 (19 and younger)", "j17": "U17 (17 and younger)", "j15": "U15 (15 and younger)", @@ -868,5 +872,22 @@ "targetMiddleShort": "Middle short", "targetBackhandShort": "Backhand short", "toTarget": "to" + }, + "teamManagement": { + "lineupProposal": "Line-up by QTTR", + "eligibility": "Eligibility", + "eligibilityRegular": "Regular", + "eligibilityAdultRelease": "Approved for adults", + "eligibilityAdultReserve": "Adult reserve", + "eligibilityAdultReleaseAndReserve": "Approved + adult reserve", + "selectedLineup": "Selected players", + "availableLineupMembers": "Available players", + "lineupEmpty": "No players have been assigned to this team yet.", + "lineupSaveError": "Team line-up could not be saved.", + "lineupValidationTooLargeGap": "{higher} is more than 30 QTTR points ahead of {lower}. Please correct this order.", + "firstHalf": "First half", + "firstHalfFull": "First half (July - December)", + "secondHalf": "Second half", + "secondHalfFull": "Second half (from 1 January)" } } diff --git a/frontend/src/i18n/locales/en-GB.json b/frontend/src/i18n/locales/en-GB.json index d555242c..bb8f7370 100644 --- a/frontend/src/i18n/locales/en-GB.json +++ b/frontend/src/i18n/locales/en-GB.json @@ -4,6 +4,7 @@ "title": "Training Diary" }, "common": { + "period": "Period", "loading": "Loading...", "save": "Save", "saved": "Saved", @@ -29,6 +30,7 @@ "time": "Time", "new": "New", "update": "Update", + "move": "Move", "refresh": "Reload", "create": "Create", "remove": "Remove", @@ -724,6 +726,8 @@ "picsInInternetAllowed": "Pictures allowed online", "testMembership": "Trial membership", "memberFormHandedOver": "Membership form handed over", + "adultReleaseApproved": "Approved for adults", + "adultReserveApproved": "Adult reserve", "trainingGroups": "Training groups", "noGroupsAssigned": "No groups assigned", "noGroupsAvailable": "No groups available", @@ -738,7 +742,7 @@ "clearFields": "Clear fields", "showInactiveMembers": "Show inactive members", "ageGroup": "Age group", - "adults": "Adults (20+)", + "adults": "Adults (18+)", "j19": "U19 (19 and younger)", "j17": "U17 (17 and younger)", "j15": "U15 (15 and younger)", @@ -1014,6 +1018,22 @@ "playerStats": "Appearances", "playerStatsIntro": "Quick overview of this team's appearances in the current season.", "lineupProposal": "Line-up by QTTR", + "teamGender": "Gender", + "teamAgeGroup": "Age group", + "teamGenderOpen": "Open", + "teamGenderFemale": "Female", + "eligibility": "Eligibility", + "eligibilityRegular": "Regular", + "eligibilityAdultRelease": "Approved for adults", + "eligibilityAdultReserve": "Adult reserve", + "eligibilityAdultReleaseAndReserve": "Approved + adult reserve", + "selectedLineup": "Selected players", + "availableLineupMembers": "Available players", + "lineupEmpty": "No players have been assigned to this team yet.", + "lineupUnsavedChanges": "There are unsaved changes in the team line-up.", + "lineupSaved": "Team line-up saved.", + "lineupSaveError": "Team line-up could not be saved.", + "lineupValidationTooLargeGap": "{higher} is more than 30 QTTR points ahead of {lower}. Please correct this order.", "refreshStats": "Refresh", "loadingStats": "Loading statistics...", "noPlayerStats": "No appearances recorded.", diff --git a/frontend/src/i18n/locales/en-US.json b/frontend/src/i18n/locales/en-US.json index 926826bc..bb2c7d93 100644 --- a/frontend/src/i18n/locales/en-US.json +++ b/frontend/src/i18n/locales/en-US.json @@ -4,6 +4,7 @@ "title": "Training Diary" }, "common": { + "period": "Period", "loading": "Loading...", "save": "Save", "saved": "Saved", @@ -29,6 +30,7 @@ "time": "Time", "new": "New", "update": "Update", + "move": "Move", "refresh": "Reload", "create": "Create", "remove": "Remove", @@ -449,6 +451,8 @@ "picsInInternetAllowed": "Pictures allowed online", "testMembership": "Trial membership", "memberFormHandedOver": "Membership form handed over", + "adultReleaseApproved": "Approved for adults", + "adultReserveApproved": "Adult reserve", "trainingGroups": "Training groups", "noGroupsAssigned": "No groups assigned", "noGroupsAvailable": "No groups available", @@ -463,7 +467,7 @@ "clearFields": "Clear fields", "showInactiveMembers": "Show inactive members", "ageGroup": "Age group", - "adults": "Adults (20+)", + "adults": "Adults (18+)", "j19": "U19 (19 and younger)", "j17": "U17 (17 and younger)", "j15": "U15 (15 and younger)", @@ -868,5 +872,22 @@ "targetMiddleShort": "Middle short", "targetBackhandShort": "Backhand short", "toTarget": "to" + }, + "teamManagement": { + "lineupProposal": "Line-up by QTTR", + "eligibility": "Eligibility", + "eligibilityRegular": "Regular", + "eligibilityAdultRelease": "Approved for adults", + "eligibilityAdultReserve": "Adult reserve", + "eligibilityAdultReleaseAndReserve": "Approved + adult reserve", + "selectedLineup": "Selected players", + "availableLineupMembers": "Available players", + "lineupEmpty": "No players have been assigned to this team yet.", + "lineupSaveError": "Team line-up could not be saved.", + "lineupValidationTooLargeGap": "{higher} is more than 30 QTTR points ahead of {lower}. Please correct this order.", + "firstHalf": "First half", + "firstHalfFull": "First half (July - December)", + "secondHalf": "Second half", + "secondHalfFull": "Second half (from 1 January)" } } diff --git a/frontend/src/i18n/locales/es.json b/frontend/src/i18n/locales/es.json index bdb6f4da..f2bfd6b9 100644 --- a/frontend/src/i18n/locales/es.json +++ b/frontend/src/i18n/locales/es.json @@ -57,7 +57,8 @@ "weeks": "semanas", "months": "meses", "years": "años", - "ok": "OK" + "ok": "OK", + "period": "Periodo" }, "navigation": { "home": "Inicio", @@ -838,5 +839,22 @@ "targetMiddleShort": "Centro corto", "targetBackhandShort": "Revés corto", "toTarget": "a" + }, + "teamManagement": { + "lineupProposal": "Alineación por QTTR", + "eligibility": "Elegibilidad", + "eligibilityRegular": "Regular", + "eligibilityAdultRelease": "Autorizado para adultos", + "eligibilityAdultReserve": "Reserva en adultos", + "eligibilityAdultReleaseAndReserve": "Autorizado + reserva en adultos", + "selectedLineup": "Jugadores inscritos", + "availableLineupMembers": "Jugadores disponibles", + "lineupEmpty": "Todavía no se ha inscrito ningún jugador para este equipo.", + "lineupSaveError": "No se pudo guardar la alineación del equipo.", + "lineupValidationTooLargeGap": "{higher} tiene más de 30 puntos QTTR de ventaja sobre {lower}. Corrige este orden.", + "firstHalf": "Primera vuelta", + "firstHalfFull": "Primera vuelta (julio - diciembre)", + "secondHalf": "Segunda vuelta", + "secondHalfFull": "Segunda vuelta (desde el 1 de enero)" } } diff --git a/frontend/src/i18n/locales/fil.json b/frontend/src/i18n/locales/fil.json index 9fb18ce0..395f6e7c 100644 --- a/frontend/src/i18n/locales/fil.json +++ b/frontend/src/i18n/locales/fil.json @@ -57,7 +57,8 @@ "weeks": "Linggo", "months": "Buwan", "years": "Taon", - "ok": "OK" + "ok": "OK", + "period": "Panahon" }, "navigation": { "home": "Home", @@ -838,5 +839,22 @@ "targetMiddleShort": "middle short", "targetBackhandShort": "backhand short", "toTarget": "papunta sa" + }, + "teamManagement": { + "lineupProposal": "Line-up ayon sa QTTR", + "eligibility": "Pagiging karapat-dapat", + "eligibilityRegular": "Karaniwan", + "eligibilityAdultRelease": "Pinayagan sa adults", + "eligibilityAdultReserve": "Reserve sa adults", + "eligibilityAdultReleaseAndReserve": "Pinayagan + reserve sa adults", + "selectedLineup": "Napiling manlalaro", + "availableLineupMembers": "Magagamit na manlalaro", + "lineupEmpty": "Wala pang manlalarong itinalaga sa koponang ito.", + "lineupSaveError": "Hindi mai-save ang line-up ng koponan.", + "lineupValidationTooLargeGap": "Mahigit 30 QTTR points ang lamang ni {higher} kay {lower}. Pakitama ang ayos na ito.", + "firstHalf": "Unang yugto", + "firstHalfFull": "Unang yugto (Hulyo - Disyembre)", + "secondHalf": "Ikalawang yugto", + "secondHalfFull": "Ikalawang yugto (mula Enero 1)" } } diff --git a/frontend/src/i18n/locales/fr.json b/frontend/src/i18n/locales/fr.json index e872a7db..baf9328e 100644 --- a/frontend/src/i18n/locales/fr.json +++ b/frontend/src/i18n/locales/fr.json @@ -57,7 +57,8 @@ "weeks": "semaines", "months": "mois", "years": "années", - "ok": "OK" + "ok": "OK", + "period": "Période" }, "navigation": { "home": "Accueil", @@ -838,5 +839,22 @@ "targetMiddleShort": "Milieu court", "targetBackhandShort": "Revers court", "toTarget": "vers" + }, + "teamManagement": { + "lineupProposal": "Composition selon le QTTR", + "eligibility": "Éligibilité", + "eligibilityRegular": "Régulier", + "eligibilityAdultRelease": "Autorisé chez les adultes", + "eligibilityAdultReserve": "Remplaçant chez les adultes", + "eligibilityAdultReleaseAndReserve": "Autorisé + remplaçant chez les adultes", + "selectedLineup": "Joueurs inscrits", + "availableLineupMembers": "Joueurs disponibles", + "lineupEmpty": "Aucun joueur n’a encore été inscrit pour cette équipe.", + "lineupSaveError": "La composition de l’équipe n’a pas pu être enregistrée.", + "lineupValidationTooLargeGap": "{higher} a plus de 30 points QTTR d’avance sur {lower}. Veuillez corriger cet ordre.", + "firstHalf": "Phase aller", + "firstHalfFull": "Phase aller (juillet - décembre)", + "secondHalf": "Phase retour", + "secondHalfFull": "Phase retour (à partir du 1er janvier)" } } diff --git a/frontend/src/i18n/locales/it.json b/frontend/src/i18n/locales/it.json index 8d2933fc..0482d6ec 100644 --- a/frontend/src/i18n/locales/it.json +++ b/frontend/src/i18n/locales/it.json @@ -57,7 +57,8 @@ "weeks": "settimane", "months": "mesi", "years": "anni", - "ok": "OK" + "ok": "OK", + "period": "Periodo" }, "navigation": { "home": "Home", @@ -838,5 +839,22 @@ "targetMiddleShort": "Centro corto", "targetBackhandShort": "Rovescio corto", "toTarget": "verso" + }, + "teamManagement": { + "lineupProposal": "Formazione in base al QTTR", + "eligibility": "Idoneità", + "eligibilityRegular": "Regolare", + "eligibilityAdultRelease": "Autorizzato per adulti", + "eligibilityAdultReserve": "Riserva negli adulti", + "eligibilityAdultReleaseAndReserve": "Autorizzato + riserva negli adulti", + "selectedLineup": "Giocatori schierati", + "availableLineupMembers": "Giocatori disponibili", + "lineupEmpty": "Nessun giocatore è ancora stato assegnato a questa squadra.", + "lineupSaveError": "Impossibile salvare la formazione della squadra.", + "lineupValidationTooLargeGap": "{higher} ha più di 30 punti QTTR di vantaggio su {lower}. Correggi questo ordine.", + "firstHalf": "Girone d’andata", + "firstHalfFull": "Girone d’andata (luglio - dicembre)", + "secondHalf": "Girone di ritorno", + "secondHalfFull": "Girone di ritorno (dal 1° gennaio)" } } diff --git a/frontend/src/i18n/locales/ja.json b/frontend/src/i18n/locales/ja.json index c108e2ef..1bf12633 100644 --- a/frontend/src/i18n/locales/ja.json +++ b/frontend/src/i18n/locales/ja.json @@ -57,7 +57,8 @@ "weeks": "週間", "months": "か月", "years": "年", - "ok": "OK" + "ok": "OK", + "period": "期間" }, "navigation": { "home": "ホーム", @@ -838,5 +839,22 @@ "targetMiddleShort": "ミドル短", "targetBackhandShort": "バック短", "toTarget": "へ" + }, + "teamManagement": { + "lineupProposal": "QTTRによるチーム登録", + "eligibility": "出場資格", + "eligibilityRegular": "通常", + "eligibilityAdultRelease": "一般出場許可", + "eligibilityAdultReserve": "一般の補欠", + "eligibilityAdultReleaseAndReserve": "一般出場許可 + 補欠", + "selectedLineup": "登録済み選手", + "availableLineupMembers": "利用可能な選手", + "lineupEmpty": "このチームにはまだ選手が登録されていません。", + "lineupSaveError": "チーム登録を保存できませんでした。", + "lineupValidationTooLargeGap": "{higher} は {lower} より 30 QTTR ポイント以上高いです。この順序を修正してください。", + "firstHalf": "前期", + "firstHalfFull": "前期(7月 - 12月)", + "secondHalf": "後期", + "secondHalfFull": "後期(1月1日以降)" } } diff --git a/frontend/src/i18n/locales/pl.json b/frontend/src/i18n/locales/pl.json index 8cd20f6c..3dde26e5 100644 --- a/frontend/src/i18n/locales/pl.json +++ b/frontend/src/i18n/locales/pl.json @@ -57,7 +57,8 @@ "weeks": "tygodnie", "months": "miesiące", "years": "lata", - "ok": "OK" + "ok": "OK", + "period": "Okres" }, "navigation": { "home": "Strona główna", @@ -838,5 +839,22 @@ "targetMiddleShort": "Środek krótki", "targetBackhandShort": "Backhand krótki", "toTarget": "do" + }, + "teamManagement": { + "lineupProposal": "Zgłoszenie według QTTR", + "eligibility": "Uprawnienie", + "eligibilityRegular": "Standardowe", + "eligibilityAdultRelease": "Dopuszczony do dorosłych", + "eligibilityAdultReserve": "Rezerwowy u dorosłych", + "eligibilityAdultReleaseAndReserve": "Dopuszczony + rezerwowy u dorosłych", + "selectedLineup": "Zgłoszeni zawodnicy", + "availableLineupMembers": "Dostępni zawodnicy", + "lineupEmpty": "Do tej drużyny nie zgłoszono jeszcze żadnych zawodników.", + "lineupSaveError": "Nie udało się zapisać zgłoszenia drużyny.", + "lineupValidationTooLargeGap": "{higher} ma ponad 30 punktów QTTR przewagi nad {lower}. Popraw tę kolejność.", + "firstHalf": "Pierwsza runda", + "firstHalfFull": "Pierwsza runda (lipiec - grudzień)", + "secondHalf": "Druga runda", + "secondHalfFull": "Druga runda (od 1 stycznia)" } } diff --git a/frontend/src/i18n/locales/th.json b/frontend/src/i18n/locales/th.json index 6fea9ac4..3edcb513 100644 --- a/frontend/src/i18n/locales/th.json +++ b/frontend/src/i18n/locales/th.json @@ -57,7 +57,8 @@ "weeks": "สัปดาห์", "months": "เดือน", "years": "ปี", - "ok": "ตกลง" + "ok": "ตกลง", + "period": "ช่วงเวลา" }, "navigation": { "home": "หน้าแรก", @@ -838,5 +839,22 @@ "targetMiddleShort": "กลางสั้น", "targetBackhandShort": "แบ็กแฮนด์สั้น", "toTarget": "ไปยัง" + }, + "teamManagement": { + "lineupProposal": "จัดทีมตาม QTTR", + "eligibility": "สิทธิ์ลงเล่น", + "eligibilityRegular": "ปกติ", + "eligibilityAdultRelease": "อนุมัติให้เล่นผู้ใหญ่", + "eligibilityAdultReserve": "ตัวสำรองผู้ใหญ่", + "eligibilityAdultReleaseAndReserve": "อนุมัติ + ตัวสำรองผู้ใหญ่", + "selectedLineup": "ผู้เล่นที่ขึ้นทะเบียน", + "availableLineupMembers": "ผู้เล่นที่พร้อมใช้งาน", + "lineupEmpty": "ยังไม่มีการกำหนดผู้เล่นให้ทีมนี้", + "lineupSaveError": "ไม่สามารถบันทึกรายชื่อทีมได้", + "lineupValidationTooLargeGap": "{higher} มีคะแนน QTTR มากกว่า {lower} เกิน 30 คะแนน กรุณาแก้ไขลำดับนี้", + "firstHalf": "ครึ่งแรก", + "firstHalfFull": "ครึ่งแรก (กรกฎาคม - ธันวาคม)", + "secondHalf": "ครึ่งหลัง", + "secondHalfFull": "ครึ่งหลัง (ตั้งแต่ 1 มกราคม)" } } diff --git a/frontend/src/i18n/locales/tl.json b/frontend/src/i18n/locales/tl.json index 6609750b..d49ce1e5 100644 --- a/frontend/src/i18n/locales/tl.json +++ b/frontend/src/i18n/locales/tl.json @@ -57,7 +57,8 @@ "weeks": "Linggo", "months": "Buwan", "years": "Taon", - "ok": "OK" + "ok": "OK", + "period": "Panahon" }, "navigation": { "home": "Home", @@ -838,5 +839,22 @@ "targetMiddleShort": "middle short", "targetBackhandShort": "backhand short", "toTarget": "papunta sa" + }, + "teamManagement": { + "lineupProposal": "Line-up ayon sa QTTR", + "eligibility": "Pagiging karapat-dapat", + "eligibilityRegular": "Karaniwan", + "eligibilityAdultRelease": "Pinayagan sa adults", + "eligibilityAdultReserve": "Reserve sa adults", + "eligibilityAdultReleaseAndReserve": "Pinayagan + reserve sa adults", + "selectedLineup": "Napiling manlalaro", + "availableLineupMembers": "Magagamit na manlalaro", + "lineupEmpty": "Wala pang manlalarong itinalaga sa koponang ito.", + "lineupSaveError": "Hindi mai-save ang line-up ng koponan.", + "lineupValidationTooLargeGap": "Mahigit 30 QTTR points ang lamang ni {higher} kay {lower}. Pakitama ang ayos na ito.", + "firstHalf": "Unang yugto", + "firstHalfFull": "Unang yugto (Hulyo - Disyembre)", + "secondHalf": "Ikalawang yugto", + "secondHalfFull": "Ikalawang yugto (mula Enero 1)" } } diff --git a/frontend/src/i18n/locales/zh.json b/frontend/src/i18n/locales/zh.json index 4ff678a1..8df02eb1 100644 --- a/frontend/src/i18n/locales/zh.json +++ b/frontend/src/i18n/locales/zh.json @@ -57,7 +57,8 @@ "weeks": "周", "months": "个月", "years": "年", - "ok": "确定" + "ok": "确定", + "period": "期间" }, "navigation": { "home": "首页", @@ -838,5 +839,22 @@ "targetMiddleShort": "中路短", "targetBackhandShort": "反手短", "toTarget": "到" + }, + "teamManagement": { + "lineupProposal": "按 QTTR 的队伍报名", + "eligibility": "参赛资格", + "eligibilityRegular": "常规", + "eligibilityAdultRelease": "允许参加成人组", + "eligibilityAdultReserve": "成人组替补", + "eligibilityAdultReleaseAndReserve": "允许参加成人组 + 替补", + "selectedLineup": "已报名球员", + "availableLineupMembers": "可用球员", + "lineupEmpty": "这支队伍还没有报名任何球员。", + "lineupSaveError": "无法保存队伍报名。", + "lineupValidationTooLargeGap": "{higher} 比 {lower} 高出超过 30 个 QTTR 积分。请更正这个顺序。", + "firstHalf": "上半程", + "firstHalfFull": "上半程(7月 - 12月)", + "secondHalf": "下半程", + "secondHalfFull": "下半程(1月1日起)" } } diff --git a/frontend/src/views/MembersView.vue b/frontend/src/views/MembersView.vue index f5b8acb8..83b5228d 100644 --- a/frontend/src/views/MembersView.vue +++ b/frontend/src/views/MembersView.vue @@ -257,6 +257,8 @@ + +
@@ -364,6 +366,18 @@ ⚠️ {{ labelGender(member.gender) }}
+
+ FE + EE +
@@ -911,6 +925,8 @@ export default { showInactiveMembers: false, newPicsInInternetAllowed: false, newMemberFormHandedOver: false, + newAdultReleaseApproved: false, + newAdultReserveApproved: false, isUpdatingRatings: false, showMemberInfo: false, showActivitiesModal: false, @@ -1370,6 +1386,8 @@ export default { this.memberImage = null; this.memberImagePreview = null; this.newMemberFormHandedOver = false; + this.newAdultReleaseApproved = false; + this.newAdultReserveApproved = false; this.memberContacts = { phones: [{ value: '', isParent: false, parentName: '', isPrimary: false }], emails: [{ value: '', isParent: false, parentName: '', isPrimary: false }] @@ -1589,6 +1607,8 @@ export default { testMembership: this.testMembership, picsInInternetAllowed: this.newPicsInInternetAllowed, memberFormHandedOver: this.newMemberFormHandedOver, + adultReleaseApproved: this.newAdultReleaseApproved, + adultReserveApproved: this.newAdultReserveApproved, contacts: contacts }; @@ -1637,6 +1657,8 @@ export default { this.testMembership = member.testMembership; this.newPicsInInternetAllowed = member.picsInInternetAllowed; this.newMemberFormHandedOver = !!member.memberFormHandedOver; + this.newAdultReleaseApproved = !!member.adultReleaseApproved; + this.newAdultReserveApproved = !!member.adultReserveApproved; // Load contacts if (member.contacts && Array.isArray(member.contacts)) { @@ -3574,6 +3596,30 @@ table td { flex-wrap: wrap; } +.member-name-flags { + display: flex; + align-items: center; + gap: 0.35rem; + flex-wrap: wrap; + margin-top: 0.18rem; +} + +.member-eligibility-flag { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 2.1rem; + padding: 0.1rem 0.4rem; + border-radius: 999px; + background: #eef6ff; + color: #12427a; + border: 1px solid #bfd7f3; + font-size: 0.72rem; + font-weight: 700; + letter-spacing: 0.02em; + line-height: 1.1; +} + .member-meta-text { color: #66788a; font-size: 0.88rem; diff --git a/frontend/src/views/TeamManagementView.vue b/frontend/src/views/TeamManagementView.vue index 96779f6a..39785156 100644 --- a/frontend/src/views/TeamManagementView.vue +++ b/frontend/src/views/TeamManagementView.vue @@ -66,6 +66,9 @@ + @@ -97,6 +100,14 @@ {{ t('teamManagement.season') }} {{ teamToEdit.season?.season || t('teamManagement.seasonUnknown') }} +
+ {{ t('teamManagement.teamGender') }} + {{ labelTeamGender(effectiveTeamGender) }} +
+
+ {{ t('teamManagement.teamAgeGroup') }} + {{ labelAgeGroup(effectiveTeamAgeGroup) }} +
+ + + +