From 32ba4330084701348ce334d99d4caa15a2d08a31 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Thu, 26 Mar 2026 19:34:46 +0100 Subject: [PATCH] feat(AutoFetchMatchResultsService, ScheduleView): enhance date and time parsing for match results - Implemented a new method in AutoFetchMatchResultsService to parse match date and time from various formats, improving robustness in handling date inputs. - Updated storeMatchResult method to utilize the new date parsing logic, ensuring accurate match date handling. - Enhanced ScheduleView to maintain consistent display of match dates and times, improving user experience in the schedule table. --- .../services/autoFetchMatchResultsService.js | 51 +++- frontend/src/views/ScheduleView.vue | 233 ++++++++++-------- 2 files changed, 177 insertions(+), 107 deletions(-) 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; }