diff --git a/backend/services/autoFetchMatchResultsService.js b/backend/services/autoFetchMatchResultsService.js
index f315ec02..209b092d 100644
--- a/backend/services/autoFetchMatchResultsService.js
+++ b/backend/services/autoFetchMatchResultsService.js
@@ -14,6 +14,33 @@ import { devLog } from '../utils/logger.js';
import SeasonService from './seasonService.js';
class AutoFetchMatchResultsService {
+ parseMatchDateTime(rawValue) {
+ if (!rawValue) {
+ return { date: null, time: null };
+ }
+
+ if (typeof rawValue === 'string') {
+ const match = rawValue.match(/^(\d{4})-(\d{2})-(\d{2})(?:[T\s](\d{2}):(\d{2})(?::(\d{2}))?)?/);
+ if (match) {
+ const [, year, month, day, hour = '00', minute = '00', second = '00'] = match;
+ return {
+ date: new Date(Number(year), Number(month) - 1, Number(day), 0, 0, 0, 0),
+ time: `${hour}:${minute}:${second}`
+ };
+ }
+ }
+
+ const parsed = new Date(rawValue);
+ if (Number.isNaN(parsed.getTime())) {
+ return { date: null, time: null };
+ }
+
+ return {
+ date: new Date(parsed.getFullYear(), parsed.getMonth(), parsed.getDate(), 0, 0, 0, 0),
+ time: `${String(parsed.getHours()).padStart(2, '0')}:${String(parsed.getMinutes()).padStart(2, '0')}:${String(parsed.getSeconds()).padStart(2, '0')}`
+ };
+ }
+
/**
* Execute automatic match results fetching for all users with enabled auto-updates
*/
@@ -699,6 +726,8 @@ class AutoFetchMatchResultsService {
* Store or update match result in database
*/
async storeMatchResult(ourClubTeam, matchData, isHomeTeam) {
+ const parsedMatchDateTime = this.parseMatchDateTime(matchData.date);
+
// Parse match points from myTischtennis data
// matchData.matches_won/lost are ALWAYS from the perspective of team_home in myTischtennis
// So we need to assign them correctly based on whether WE are home or guest
@@ -725,9 +754,12 @@ class AutoFetchMatchResultsService {
// If not found by meeting ID, try to find by date and teams
if (!match) {
devLog(`No match found by meeting ID, searching by date and teams...`);
- const matchDate = new Date(matchData.date);
- const startOfDay = new Date(matchDate.setHours(0, 0, 0, 0));
- const endOfDay = new Date(matchDate.setHours(23, 59, 59, 999));
+ const matchDate = parsedMatchDateTime.date;
+ if (!matchDate) {
+ throw new Error(`Ungültiges Match-Datum von myTischtennis: ${matchData.date}`);
+ }
+ const startOfDay = new Date(matchDate.getFullYear(), matchDate.getMonth(), matchDate.getDate(), 0, 0, 0, 0);
+ const endOfDay = new Date(matchDate.getFullYear(), matchDate.getMonth(), matchDate.getDate(), 23, 59, 59, 999);
devLog(`Searching matches on ${matchData.date} in league ${ourClubTeam.leagueId}`);
@@ -812,8 +844,8 @@ class AutoFetchMatchResultsService {
}
const updateData = {
- date: matchData.date,
- time: matchData.date ? `${String(new Date(matchData.date).getHours()).padStart(2, '0')}:${String(new Date(matchData.date).getMinutes()).padStart(2, '0')}:00` : match.time,
+ date: parsedMatchDateTime.date || match.date,
+ time: parsedMatchDateTime.time || match.time,
homeMatchPoints: finalHomePoints,
guestMatchPoints: finalGuestPoints,
isCompleted: matchData.is_meeting_complete,
@@ -842,12 +874,15 @@ class AutoFetchMatchResultsService {
);
// Extract time from date
- const matchDate = new Date(matchData.date);
- const time = `${String(matchDate.getHours()).padStart(2, '0')}:${String(matchDate.getMinutes()).padStart(2, '0')}:00`;
+ const matchDate = parsedMatchDateTime.date;
+ const time = parsedMatchDateTime.time;
+ if (!matchDate) {
+ throw new Error(`Ungültiges Match-Datum von myTischtennis: ${matchData.date}`);
+ }
// Create match (points are already correctly set from matchData)
match = await Match.create({
- date: matchData.date,
+ date: matchDate,
time: time,
locationId: null, // Location is not provided by myTischtennis
homeTeamId: homeTeam.id,
diff --git a/frontend/src/views/ScheduleView.vue b/frontend/src/views/ScheduleView.vue
index b92fa7ee..0bad7da7 100644
--- a/frontend/src/views/ScheduleView.vue
+++ b/frontend/src/views/ScheduleView.vue
@@ -80,84 +80,86 @@
-
-
-
- | {{ $t('schedule.locationInfo') }} |
- {{ $t('schedule.date') }} |
- {{ $t('schedule.time') }} |
- {{ $t('schedule.homeTeam') }} |
- {{ $t('schedule.guestTeam') }} |
- {{ $t('schedule.result') }} |
-
- {{ $t('schedule.ageClass') }} |
- {{ $t('schedule.code') }} |
- {{ $t('schedule.homePin') }} |
- {{ $t('schedule.guestPin') }} |
-
-
-
-
- |
-
- -
- |
- {{ formatDate(match.date) }} |
- {{ match.time ? match.time.toString().slice(0, 5) + ' ' + $t('common.time') : 'N/A' }} |
-
- {{ match.homeTeam?.name || 'N/A' }}
- |
-
- {{ match.guestTeam?.name || 'N/A' }}
- |
-
-
- {{ match.homeMatchPoints }}:{{ match.guestMatchPoints }}
-
- —
- |
-
- {{ match.leagueDetails?.name || 'N/A' }} |
-
-
-
-
+
+
+
+ | {{ $t('schedule.locationInfo') }} |
+ {{ $t('schedule.date') }} |
+ {{ $t('schedule.time') }} |
+ {{ $t('schedule.homeTeam') }} |
+ {{ $t('schedule.guestTeam') }} |
+ {{ $t('schedule.result') }} |
+
+ {{ $t('schedule.ageClass') }} |
+ {{ $t('schedule.code') }} |
+ {{ $t('schedule.homePin') }} |
+ {{ $t('schedule.guestPin') }} |
+
+
+
+
+ |
+
+ -
+ |
+ {{ formatDate(match.date) }} |
+ {{ match.time ? match.time.toString().slice(0, 5) + ' ' + $t('common.time') : 'N/A' }} |
+
+ {{ match.homeTeam?.name || 'N/A' }}
+ |
+
+ {{ match.guestTeam?.name || 'N/A' }}
+ |
+
+
+ {{ match.homeMatchPoints }}:{{ match.guestMatchPoints }}
+
+ —
+ |
+
+ {{ match.leagueDetails?.name || 'N/A' }} |
+
+
+
+ {{ match.code }}
+
+ {{ match.code }}
-
- {{ match.code }}
- -
- |
-
- {{ match.homePin }}
- -
- |
-
- {{ match.guestPin }}
- -
- |
-
-
-
+ -
+ |
+
+ {{ match.homePin }}
+ -
+ |
+
+ {{ match.guestPin }}
+ -
+ |
+
+
+
+
{{ $t('schedule.noGames') }}
@@ -169,28 +171,30 @@
{{ $t('schedule.leagueTable') }}
-
-
-
- | {{ $t('schedule.position') }} |
- {{ $t('schedule.team') }} |
- {{ $t('schedule.matches') }} |
- {{ $t('schedule.sets') }} |
- {{ $t('schedule.points') }} |
- {{ $t('schedule.balls') }} |
-
-
-
-
- | {{ index + 1 }} |
- {{ team.teamName }} |
- {{ team.matchPoints }} |
- {{ team.setsWon }}:{{ team.setsLost }} |
- {{ team.tablePoints }} |
- {{ team.pointRatio }} |
-
-
-
+
+
+
+
+ | {{ $t('schedule.position') }} |
+ {{ $t('schedule.team') }} |
+ {{ $t('schedule.matches') }} |
+ {{ $t('schedule.sets') }} |
+ {{ $t('schedule.points') }} |
+ {{ $t('schedule.balls') }} |
+
+
+
+
+ | {{ index + 1 }} |
+ {{ team.teamName }} |
+ {{ team.matchPoints }} |
+ {{ team.setsWon }}:{{ team.setsLost }} |
+ {{ team.tablePoints }} |
+ {{ team.pointRatio }} |
+
+
+
+
{{ $t('schedule.noTableData') }}
@@ -1728,6 +1732,36 @@ li {
margin: 0;
}
+.schedule-table-wrapper {
+ width: 100%;
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+ overscroll-behavior-x: contain;
+}
+
+#schedule-table {
+ width: 100%;
+ min-width: 980px;
+ border-collapse: collapse;
+}
+
+#schedule-table th,
+#schedule-table td {
+ padding: 12px 8px;
+ text-align: left;
+ border: 1px solid #ddd;
+ white-space: nowrap;
+}
+
+#schedule-table th {
+ background-color: #f8f9fa;
+ font-weight: 600;
+}
+
+#schedule-table tr:hover {
+ background-color: #f5f5f5;
+}
+
.fetch-table-btn {
background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
color: var(--text-on-primary);
@@ -1752,6 +1786,7 @@ li {
#league-table {
width: 100%;
+ min-width: 640px;
border-collapse: collapse;
}