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.
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -80,84 +80,86 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="matches.length > 0">
|
||||
<table id="schedule-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('schedule.locationInfo') }}</th>
|
||||
<th>{{ $t('schedule.date') }}</th>
|
||||
<th>{{ $t('schedule.time') }}</th>
|
||||
<th>{{ $t('schedule.homeTeam') }}</th>
|
||||
<th>{{ $t('schedule.guestTeam') }}</th>
|
||||
<th>{{ $t('schedule.result') }}</th>
|
||||
<th
|
||||
v-if="selectedLeague === $t('schedule.overallSchedule') || selectedLeague === $t('schedule.adultSchedule')">
|
||||
{{ $t('schedule.ageClass') }}</th>
|
||||
<th>{{ $t('schedule.code') }}</th>
|
||||
<th>{{ $t('schedule.homePin') }}</th>
|
||||
<th>{{ $t('schedule.guestPin') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="match in matches" :key="match.id"
|
||||
@click="openPlayerSelectionDialog(match)"
|
||||
:class="getRowClass(match.date)"
|
||||
style="cursor: pointer;">
|
||||
<td class="location-info-cell">
|
||||
<button
|
||||
v-if="match.location"
|
||||
type="button"
|
||||
class="location-info-button"
|
||||
:title="$t('schedule.showLocation')"
|
||||
@click.stop="openLocationDialog(match)"
|
||||
>
|
||||
📍
|
||||
</button>
|
||||
<span v-else class="no-data">-</span>
|
||||
</td>
|
||||
<td>{{ formatDate(match.date) }}</td>
|
||||
<td>{{ match.time ? match.time.toString().slice(0, 5) + ' ' + $t('common.time') : 'N/A' }}</td>
|
||||
<td :class="{ 'highlighted-club': isClubHighlighted(match.homeTeam?.name) }">
|
||||
{{ match.homeTeam?.name || 'N/A' }}
|
||||
</td>
|
||||
<td :class="{ 'highlighted-club': isClubHighlighted(match.guestTeam?.name) }">
|
||||
{{ match.guestTeam?.name || 'N/A' }}
|
||||
</td>
|
||||
<td class="result-cell" :class="getResultClass(match)">
|
||||
<span v-if="match.isCompleted" class="result-score">
|
||||
{{ match.homeMatchPoints }}:{{ match.guestMatchPoints }}
|
||||
</span>
|
||||
<span v-else class="result-pending">—</span>
|
||||
</td>
|
||||
<td
|
||||
v-if="selectedLeague === $t('schedule.overallSchedule') || selectedLeague === $t('schedule.adultSchedule')">
|
||||
{{ match.leagueDetails?.name || 'N/A' }}</td>
|
||||
<td class="code-cell match-report-cell" @click.stop="openMatchReport(match)">
|
||||
<span v-if="match.code && selectedLeague && selectedLeague !== ''">
|
||||
<button @click.stop="openMatchReport(match)" class="nuscore-link"
|
||||
:title="$t('schedule.openMatchReport')">📊</button>
|
||||
<span class="code-value clickable" @click.stop="copyToClipboard(match.code, $t('schedule.code'), $event)"
|
||||
<div class="schedule-table-wrapper">
|
||||
<table id="schedule-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('schedule.locationInfo') }}</th>
|
||||
<th>{{ $t('schedule.date') }}</th>
|
||||
<th>{{ $t('schedule.time') }}</th>
|
||||
<th>{{ $t('schedule.homeTeam') }}</th>
|
||||
<th>{{ $t('schedule.guestTeam') }}</th>
|
||||
<th>{{ $t('schedule.result') }}</th>
|
||||
<th
|
||||
v-if="selectedLeague === $t('schedule.overallSchedule') || selectedLeague === $t('schedule.adultSchedule')">
|
||||
{{ $t('schedule.ageClass') }}</th>
|
||||
<th>{{ $t('schedule.code') }}</th>
|
||||
<th>{{ $t('schedule.homePin') }}</th>
|
||||
<th>{{ $t('schedule.guestPin') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="match in matches" :key="match.id"
|
||||
@click="openPlayerSelectionDialog(match)"
|
||||
:class="getRowClass(match.date)"
|
||||
style="cursor: pointer;">
|
||||
<td class="location-info-cell">
|
||||
<button
|
||||
v-if="match.location"
|
||||
type="button"
|
||||
class="location-info-button"
|
||||
:title="$t('schedule.showLocation')"
|
||||
@click.stop="openLocationDialog(match)"
|
||||
>
|
||||
📍
|
||||
</button>
|
||||
<span v-else class="no-data">-</span>
|
||||
</td>
|
||||
<td>{{ formatDate(match.date) }}</td>
|
||||
<td>{{ match.time ? match.time.toString().slice(0, 5) + ' ' + $t('common.time') : 'N/A' }}</td>
|
||||
<td :class="{ 'highlighted-club': isClubHighlighted(match.homeTeam?.name) }">
|
||||
{{ match.homeTeam?.name || 'N/A' }}
|
||||
</td>
|
||||
<td :class="{ 'highlighted-club': isClubHighlighted(match.guestTeam?.name) }">
|
||||
{{ match.guestTeam?.name || 'N/A' }}
|
||||
</td>
|
||||
<td class="result-cell" :class="getResultClass(match)">
|
||||
<span v-if="match.isCompleted" class="result-score">
|
||||
{{ match.homeMatchPoints }}:{{ match.guestMatchPoints }}
|
||||
</span>
|
||||
<span v-else class="result-pending">—</span>
|
||||
</td>
|
||||
<td
|
||||
v-if="selectedLeague === $t('schedule.overallSchedule') || selectedLeague === $t('schedule.adultSchedule')">
|
||||
{{ match.leagueDetails?.name || 'N/A' }}</td>
|
||||
<td class="code-cell match-report-cell" @click.stop="openMatchReport(match)">
|
||||
<span v-if="match.code && selectedLeague && selectedLeague !== ''">
|
||||
<button @click.stop="openMatchReport(match)" class="nuscore-link"
|
||||
:title="$t('schedule.openMatchReport')">📊</button>
|
||||
<span class="code-value clickable" @click.stop="copyToClipboard(match.code, $t('schedule.code'), $event)"
|
||||
:title="$t('schedule.copyCode') + ': ' + match.code">{{ match.code }}</span>
|
||||
</span>
|
||||
<span v-else-if="match.code" class="code-value clickable"
|
||||
@click.stop="copyToClipboard(match.code, $t('schedule.code'), $event)"
|
||||
:title="$t('schedule.copyCode') + ': ' + match.code">{{ match.code }}</span>
|
||||
</span>
|
||||
<span v-else-if="match.code" class="code-value clickable"
|
||||
@click.stop="copyToClipboard(match.code, $t('schedule.code'), $event)"
|
||||
:title="$t('schedule.copyCode') + ': ' + match.code">{{ match.code }}</span>
|
||||
<span v-else class="no-data">-</span>
|
||||
</td>
|
||||
<td class="pin-cell">
|
||||
<span v-if="match.homePin" class="pin-value clickable"
|
||||
@click.stop="copyToClipboard(match.homePin, $t('schedule.homePin'), $event)"
|
||||
:title="$t('schedule.copyHomePin') + ': ' + match.homePin">{{ match.homePin }}</span>
|
||||
<span v-else class="no-data">-</span>
|
||||
</td>
|
||||
<td class="pin-cell">
|
||||
<span v-if="match.guestPin" class="pin-value clickable"
|
||||
@click.stop="copyToClipboard(match.guestPin, $t('schedule.guestPin'), $event)"
|
||||
:title="$t('schedule.copyGuestPin') + ': ' + match.guestPin">{{ match.guestPin }}</span>
|
||||
<span v-else class="no-data">-</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<span v-else class="no-data">-</span>
|
||||
</td>
|
||||
<td class="pin-cell">
|
||||
<span v-if="match.homePin" class="pin-value clickable"
|
||||
@click.stop="copyToClipboard(match.homePin, $t('schedule.homePin'), $event)"
|
||||
:title="$t('schedule.copyHomePin') + ': ' + match.homePin">{{ match.homePin }}</span>
|
||||
<span v-else class="no-data">-</span>
|
||||
</td>
|
||||
<td class="pin-cell">
|
||||
<span v-if="match.guestPin" class="pin-value clickable"
|
||||
@click.stop="copyToClipboard(match.guestPin, $t('schedule.guestPin'), $event)"
|
||||
:title="$t('schedule.copyGuestPin') + ': ' + match.guestPin">{{ match.guestPin }}</span>
|
||||
<span v-else class="no-data">-</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p>{{ $t('schedule.noGames') }}</p>
|
||||
@@ -169,28 +171,30 @@
|
||||
<h3>{{ $t('schedule.leagueTable') }}</h3>
|
||||
</div>
|
||||
<div v-if="leagueTable.length > 0">
|
||||
<table id="league-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('schedule.position') }}</th>
|
||||
<th>{{ $t('schedule.team') }}</th>
|
||||
<th>{{ $t('schedule.matches') }}</th>
|
||||
<th>{{ $t('schedule.sets') }}</th>
|
||||
<th>{{ $t('schedule.points') }}</th>
|
||||
<th>{{ $t('schedule.balls') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(team, index) in leagueTable" :key="team.teamId" :class="{ 'our-team': isOurTeam(team.teamName) }">
|
||||
<td>{{ index + 1 }}</td>
|
||||
<td>{{ team.teamName }}</td>
|
||||
<td>{{ team.matchPoints }}</td>
|
||||
<td>{{ team.setsWon }}:{{ team.setsLost }}</td>
|
||||
<td>{{ team.tablePoints }}</td>
|
||||
<td>{{ team.pointRatio }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="schedule-table-wrapper">
|
||||
<table id="league-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('schedule.position') }}</th>
|
||||
<th>{{ $t('schedule.team') }}</th>
|
||||
<th>{{ $t('schedule.matches') }}</th>
|
||||
<th>{{ $t('schedule.sets') }}</th>
|
||||
<th>{{ $t('schedule.points') }}</th>
|
||||
<th>{{ $t('schedule.balls') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(team, index) in leagueTable" :key="team.teamId" :class="{ 'our-team': isOurTeam(team.teamName) }">
|
||||
<td>{{ index + 1 }}</td>
|
||||
<td>{{ team.teamName }}</td>
|
||||
<td>{{ team.matchPoints }}</td>
|
||||
<td>{{ team.setsWon }}:{{ team.setsLost }}</td>
|
||||
<td>{{ team.tablePoints }}</td>
|
||||
<td>{{ team.pointRatio }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p>{{ $t('schedule.noTableData') }}</p>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user