Add node-fetch dependency and implement meeting report submission endpoint

This commit introduces the node-fetch package to facilitate HTTP requests in the backend. Additionally, a new API endpoint for submitting meeting reports has been implemented in the nuscoreApiRoutes. The endpoint handles report data submission, cookie management, and error handling, enhancing the functionality of the meeting report feature. Frontend components have been updated to support this new functionality, improving the overall user experience.
This commit is contained in:
Torsten Schulz (local)
2025-11-12 15:29:04 +01:00
parent 3c64e2e92d
commit da351b40b2
7 changed files with 504 additions and 49 deletions

View File

@@ -1318,6 +1318,15 @@
"node": ">= 10"
}
},
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/date-fns": {
"version": "2.30.0",
"license": "MIT",
@@ -1886,6 +1895,29 @@
"reusify": "^1.0.4"
}
},
"node_modules/fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": "^12.20 || >= 14.13"
}
},
"node_modules/file-entry-cache": {
"version": "8.0.0",
"dev": true,
@@ -2004,6 +2036,18 @@
"node": ">= 6"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"license": "MIT",
"dependencies": {
"fetch-blob": "^3.1.2"
},
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/formidable": {
"version": "3.5.4",
"dev": true,
@@ -3155,10 +3199,48 @@
"node": ">=6.0.0"
}
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"deprecated": "Use your platform's native DOMException instead",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-ensure": {
"version": "0.0.0",
"license": "MIT"
},
"node_modules/node-fetch": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
"license": "MIT",
"dependencies": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/node-gyp": {
"version": "8.4.1",
"dev": true,
@@ -5032,6 +5114,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"license": "BSD-2-Clause"

View File

@@ -23,6 +23,7 @@
"multer": "^1.4.5-lts.1",
"mysql2": "^3.10.3",
"node-cron": "^4.2.1",
"node-fetch": "^3.3.2",
"nodemailer": "^7.0.9",
"pdf-parse": "^1.1.1",
"pdfjs-dist": "^5.4.394",
@@ -1352,6 +1353,15 @@
"node": ">= 10"
}
},
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/date-fns": {
"version": "2.30.0",
"license": "MIT",
@@ -1920,6 +1930,29 @@
"reusify": "^1.0.4"
}
},
"node_modules/fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": "^12.20 || >= 14.13"
}
},
"node_modules/file-entry-cache": {
"version": "8.0.0",
"dev": true,
@@ -2038,6 +2071,18 @@
"node": ">= 6"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"license": "MIT",
"dependencies": {
"fetch-blob": "^3.1.2"
},
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/formidable": {
"version": "3.5.4",
"dev": true,
@@ -3189,10 +3234,48 @@
"node": ">=6.0.0"
}
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"deprecated": "Use your platform's native DOMException instead",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-ensure": {
"version": "0.0.0",
"license": "MIT"
},
"node_modules/node-fetch": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
"license": "MIT",
"dependencies": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/node-gyp": {
"version": "8.4.1",
"dev": true,
@@ -5066,6 +5149,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"license": "BSD-2-Clause"

View File

@@ -28,6 +28,7 @@
"multer": "^1.4.5-lts.1",
"mysql2": "^3.10.3",
"node-cron": "^4.2.1",
"node-fetch": "^3.3.2",
"nodemailer": "^7.0.9",
"pdf-parse": "^1.1.1",
"pdfjs-dist": "^5.4.394",

View File

@@ -182,4 +182,93 @@ router.get('/meetingdetails/:uuid', async (req, res) => {
}
});
// Submit Meeting Report API-Endpunkt
router.put('/submit/:uuid', async (req, res) => {
const { uuid } = req.params;
const reportData = req.body;
try {
// Hole Cookies für diese UUID (falls vorhanden)
// Versuche zuerst UUID, dann Code als Fallback
let cookies = cookieStore.get(uuid) || {};
// Falls keine Cookies für UUID vorhanden, versuche Code zu finden
if (Object.keys(cookies).length === 0 && reportData.gameCode) {
cookies = cookieStore.get(reportData.gameCode) || {};
}
const url = `https://ttde-apps.liga.nu/nuliga/rs/tt/2022/meetingentry/reports/${uuid}/submit`;
const response = await fetch(url, {
method: 'PUT',
headers: {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:141.0) Gecko/20100101 Firefox/141.0',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'de,en-US;q=0.7,en;q=0.3',
'Accept-Encoding': 'gzip, deflate, br, zstd',
'Content-Type': 'application/json',
'Referer': `https://ttde-apps.liga.nu/nuliga/nuscore-tt/meeting/${uuid}/report-clearance/review`,
'Origin': 'https://ttde-apps.liga.nu',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'Connection': 'keep-alive',
...(Object.keys(cookies).length > 0 && { 'Cookie': formatCookies(cookies) })
},
body: JSON.stringify(reportData)
});
const responseText = await response.text();
let responseData;
try {
responseData = JSON.parse(responseText);
} catch (e) {
// Falls keine JSON-Antwort, verwende Text
responseData = { message: responseText };
}
if (!response.ok) {
console.error(`❌ Submit fehlgeschlagen: HTTP ${response.status}`, responseData);
return res.status(response.status).json({
error: 'Fehler beim Absenden des Spielberichts',
details: responseData,
status: response.status,
statusText: response.statusText
});
}
// Speichere neue Cookies falls vorhanden
const newCookies = extractCookies(response.headers.raw()['set-cookie']);
if (Object.keys(newCookies).length > 0) {
cookieStore.set(uuid, { ...cookies, ...newCookies });
if (reportData.gameCode) {
cookieStore.set(reportData.gameCode, { ...cookies, ...newCookies });
}
}
// CORS-Header setzen
res.set({
'Content-Type': 'application/json; charset=utf-8',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'Cache-Control': 'no-cache, no-store, must-revalidate'
});
res.json({
success: true,
data: responseData,
message: 'Spielbericht erfolgreich abgesendet'
});
} catch (error) {
console.error(`❌ Fehler beim Absenden des Spielberichts für UUID ${uuid}:`, error);
res.status(500).json({
error: 'Fehler beim Absenden des Spielberichts',
details: error.message
});
}
});
export default router;

View File

@@ -176,6 +176,14 @@ app.get('*', (req, res) => {
await renameColumnIfExists('official_tournaments', 'meldeschluesse', 'registration_deadlines', 'TEXT NULL');
const isDev = process.env.STAGE === 'dev';
// Foreign Keys temporär deaktivieren für MySQL
try {
await sequelize.query('SET FOREIGN_KEY_CHECKS = 0');
} catch (e) {
console.warn('[sync] Could not disable foreign key checks:', e?.message);
}
const safeSync = async (model) => {
try {
if (isDev) {
@@ -235,6 +243,15 @@ app.get('*', (req, res) => {
await safeSync(ApiLog);
await safeSync(MemberTransferConfig);
await safeSync(MemberContact);
await safeSync(ClubTeam);
await safeSync(TeamDocument);
// Foreign Keys wieder aktivieren
try {
await sequelize.query('SET FOREIGN_KEY_CHECKS = 1');
} catch (e) {
console.warn('[sync] Could not enable foreign key checks:', e?.message);
}
// Start scheduler service
schedulerService.start();

View File

@@ -113,6 +113,19 @@
</span>
</div>
<div class="info-item">
<label>Nicht angetreten:</label>
<select
v-model="teamNotAppeared"
class="not-appeared-select"
:class="{ 'not-appeared': teamNotAppeared !== null }"
>
<option :value="null">Beide Mannschaften angetreten</option>
<option value="home">Heimmannschaft ({{ meetingData.homeTeamname || meetingData.homeClub }}) nicht angetreten</option>
<option value="guest">Gastmannschaft ({{ meetingData.guestTeamname || meetingData.guestClub }}) nicht angetreten</option>
</select>
</div>
<div class="info-item" v-if="(meetingDetails && meetingDetails.playMode) || (meetingData && (meetingData.playMode || meetingData.matchSystem || meetingData.system))">
<label>Spielsystem:</label>
<span>{{ (meetingDetails && meetingDetails.playMode) || meetingData.playMode || meetingData.matchSystem || meetingData.system }}</span>
@@ -526,8 +539,9 @@
type="password"
class="pin-input"
:placeholder="match.homePin || 'PIN eingeben'"
:disabled="isMatchCompleted"
:disabled="isMatchCompleted || teamNotAppeared === 'home'"
/>
<span v-if="teamNotAppeared === 'home'" class="pin-not-required">(nicht erforderlich - Mannschaft nicht angetreten)</span>
</div>
<div class="pin-group">
@@ -538,8 +552,9 @@
type="password"
class="pin-input"
:placeholder="match.guestPin || 'PIN eingeben'"
:disabled="isMatchCompleted"
:disabled="isMatchCompleted || teamNotAppeared === 'guest'"
/>
<span v-if="teamNotAppeared === 'guest'" class="pin-not-required">(nicht erforderlich - Mannschaft nicht angetreten)</span>
</div>
</div>
@@ -652,7 +667,7 @@
<script>
import CryptoJS from 'crypto-js';
import apiClient from '../apiClient';
import apiClient, { backendBaseUrl } from '../apiClient';
export default {
name: 'MatchReportDialog',
@@ -674,6 +689,8 @@ export default {
isHomeLineupCertified: false,
isGuestLineupCertified: false,
isGreetingCompleted: false,
// Nicht angetreten: 'both', 'home', 'guest', null
teamNotAppeared: null,
originalHomePin: '',
originalGuestPin: '',
showPinModal: false,
@@ -765,6 +782,16 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
if (this.isMatchCompleted) {
return false;
}
// Wenn eine Mannschaft nicht angetreten ist, muss die andere Mannschaft ihre Aufstellung signieren
if (this.teamNotAppeared === 'home') {
// Heim nicht angetreten -> Gast muss Aufstellung signieren
return this.isGuestLineupCertified;
}
if (this.teamNotAppeared === 'guest') {
// Gast nicht angetreten -> Heim muss Aufstellung signieren
return this.isHomeLineupCertified;
}
// Beide Mannschaften angetreten -> beide müssen Aufstellung signieren
return this.isHomeLineupCertified && this.isGuestLineupCertified;
},
currentPlayMode() {
@@ -802,6 +829,19 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
this.initializeResults();
this.initializeFinalPins();
},
watch: {
teamNotAppeared(newValue, oldValue) {
if (newValue !== oldValue && this.meetingData) {
if (newValue === null) {
// Entferne das wo-Flag beim Zurücksetzen
this.meetingData.wo = null;
} else {
// Wende die "Nicht angetreten"-Logik an
this.applyTeamNotAppeared();
}
}
}
},
methods: {
// Effektive Spieleranzahl (berücksichtigt Braunschweiger-Regel)
getEffectivePlayerCount(team) {
@@ -1304,52 +1344,78 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
try {
console.log('🚀 Starte Spielbericht-Absendung...');
// Validiere zuerst alle Match-Ergebnisse
if (!this.areAllMatchResultsValid()) {
const playThrough = this.getPlayThrough();
if (playThrough && playThrough.toUpperCase().includes('ALLGAMES') && this.canBothTeamsPlayAllGames()) {
const unfinishedMatches = this.results.filter(match => !match.completed);
alert(`❌ Der Spielbericht kann nicht abgesendet werden. Es müssen alle Spiele abgeschlossen sein.\n\n` +
`📊 Aktueller Status:\n` +
`${this.results.length} Spiele insgesamt\n` +
`${this.results.length - unfinishedMatches.length} Spiele abgeschlossen\n` +
`${unfinishedMatches.length} Spiele noch offen\n\n` +
`Bitte schließen Sie alle offenen Spiele ab, bevor Sie den Spielbericht absenden.`);
return;
} else {
alert('❌ Der Spielbericht kann nicht abgesendet werden, da noch Fehler in den Match-Ergebnissen vorhanden sind. Bitte korrigieren Sie:\n\n' +
'• Lücken in der Satze-Reihenfolge\n' +
'• Ungültige Satzergebnisse\n' +
'• Ungenerische Match-Abschlüsse\n\n' +
'Überprüfen Sie die Ergebniserfassung.');
// Wenn eine Mannschaft nicht angetreten ist, keine Match-Ergebnisse oder Zeiten validieren
const isTeamNotAppeared = this.teamNotAppeared !== null;
if (!isTeamNotAppeared) {
// Validiere zuerst alle Match-Ergebnisse
if (!this.areAllMatchResultsValid()) {
const playThrough = this.getPlayThrough();
if (playThrough && playThrough.toUpperCase().includes('ALLGAMES') && this.canBothTeamsPlayAllGames()) {
const unfinishedMatches = this.results.filter(match => !match.completed);
alert(`❌ Der Spielbericht kann nicht abgesendet werden. Es müssen alle Spiele abgeschlossen sein.\n\n` +
`📊 Aktueller Status:\n` +
`${this.results.length} Spiele insgesamt\n` +
`${this.results.length - unfinishedMatches.length} Spiele abgeschlossen\n` +
`${unfinishedMatches.length} Spiele noch offen\n\n` +
`Bitte schließen Sie alle offenen Spiele ab, bevor Sie den Spielbericht absenden.`);
return;
} else {
alert('❌ Der Spielbericht kann nicht abgesendet werden, da noch Fehler in den Match-Ergebnissen vorhanden sind. Bitte korrigieren Sie:\n\n' +
'• Lücken in der Satze-Reihenfolge\n' +
'• Ungültige Satzergebnisse\n' +
'• Ungenerische Match-Abschlüsse\n\n' +
'Überprüfen Sie die Ergebniserfassung.');
return;
}
}
console.log('✅ Alle Match-Ergebnisse sind gültig - fahre fort...');
// Validiere Start- und Endzeiten
if (!this.areStartAndEndTimesValid()) {
alert('❌ Der Spielbericht kann nicht abgesendet werden, da Start- oder Endzeit nicht festgelegt wurden.\n\n' +
'Bitte legen Sie sowohl Startzeit als auch Endzeit fest.');
return;
}
console.log('✅ Alle Zeiten sind gültig - fahre fort...');
} else {
console.log('⚠️ Mannschaft nicht angetreten - überspringe Match-Ergebnis- und Zeit-Validierung');
}
console.log('✅ Alle Match-Ergebnisse sind gültig - fahre fort...');
// Validiere Aufstellungen (nur für angetretene Mannschaften)
const isHomeNotAppeared = this.teamNotAppeared === 'home';
const isGuestNotAppeared = this.teamNotAppeared === 'guest';
// Validiere Start- und Endzeiten
if (!this.areStartAndEndTimesValid()) {
alert('❌ Der Spielbericht kann nicht abgesendet werden, da Start- oder Endzeit nicht festgelegt wurden.\n\n' +
'Bitte legen Sie sowohl Startzeit als auch Endzeit fest.');
if (!isHomeNotAppeared && !this.isHomeLineupCertified) {
alert('❌ Der Spielbericht kann nicht abgesendet werden, da die Heimmannschaft ihre Aufstellung noch nicht signiert hat.\n\n' +
'Bitte signieren Sie die Aufstellung der Heimmannschaft, bevor Sie den Spielbericht absenden.');
return;
}
console.log('✅ Alle Zeiten sind gültig - fahre fort...');
if (!isGuestNotAppeared && !this.isGuestLineupCertified) {
alert('❌ Der Spielbericht kann nicht abgesendet werden, da die Gastmannschaft ihre Aufstellung noch nicht signiert hat.\n\n' +
'Bitte signieren Sie die Aufstellung der Gastmannschaft, bevor Sie den Spielbericht absenden.');
return;
}
// Validiere PINs (für Test: Gast-PIN "1234" akzeptieren)
if (!this.finalHomePin || this.finalHomePin.trim() === '') {
console.log('✅ Alle Aufstellungen sind signiert - fahre fort...');
// Validiere PINs (nur für angetretene Mannschaften)
if (!isHomeNotAppeared && (!this.finalHomePin || this.finalHomePin.trim() === '')) {
alert('Bitte geben Sie die PIN des Heimvereins ein.');
return;
}
if (!this.finalGuestPin || this.finalGuestPin.trim() === '') {
if (!isGuestNotAppeared && (!this.finalGuestPin || this.finalGuestPin.trim() === '')) {
alert('Bitte geben Sie die PIN des Gastvereins ein.');
return;
}
// Test-Validierung: Gast-PIN "1234" akzeptieren
if (this.finalGuestPin !== '1234') {
// Test-Validierung: Gast-PIN "1234" akzeptieren (nur wenn Gast angetreten ist)
if (!isGuestNotAppeared && this.finalGuestPin !== '1234') {
alert('Für den Test wird nur die Gast-PIN "1234" akzeptiert.');
return;
}
@@ -1366,10 +1432,33 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
this.updateMatchData(matchData);
console.log('✅ Match-Daten aktualisiert');
// Zeige das vollständige Objekt in einem Dialog an
console.log('📊 Zeige Match-Daten Dialog...');
this.showMatchDataDialog(matchData);
console.log('✅ Dialog angezeigt');
// Sende die Daten an den Backend-Endpunkt
console.log('📤 Sende Spielbericht an Backend...');
const uuid = this.meetingData.nuLigaMeetingUuid;
if (!uuid) {
throw new Error('nuLigaMeetingUuid nicht gefunden');
}
const response = await fetch(`${backendBaseUrl}/api/nuscore/submit/${uuid}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(matchData)
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.error || `HTTP ${response.status}: ${response.statusText}`);
}
console.log('✅ Spielbericht erfolgreich abgesendet:', result);
alert('✅ Spielbericht erfolgreich abgesendet!');
// Dialog schließen
this.$emit('close');
} catch (error) {
console.error('❌ Fehler beim Absenden:', error);
@@ -1472,6 +1561,16 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
// NUR unsere spezifischen Änderungen eintragen:
// Wenn eine Mannschaft nicht angetreten ist, nur wo-Flag setzen und nichts anderes ändern
if (this.teamNotAppeared !== null) {
// Nur das wo-Flag ist bereits gesetzt (durch applyTeamNotAppeared)
// Alle anderen Daten bleiben unverändert (Original aus baseData)
// isCompleted bleibt false, PINs bleiben unverändert, keine Match-Ergebnisse ändern
// Keine Zeitangaben, Spieleranzahl, Positionen oder Protest-Informationen ändern
console.log('⚠️ Mannschaft nicht angetreten - nur wo-Flag wird gesetzt, alle anderen Daten bleiben unverändert');
return; // Frühzeitiger Return - keine weiteren Änderungen
}
// 1. Spieleranzahl aktualisieren (aus Aufstellung)
matchData.playerCountHome = this.getSelectedPlayerCount('home');
matchData.playerCountGuest = this.getSelectedPlayerCount('guest');
@@ -1497,13 +1596,13 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
matchData.remarks = null;
}
// 4. PINs - Original-Hashes beibehalten (werden vom Backend beim Senden aktualisiert)
// 5. PINs - Original-Hashes beibehalten (werden vom Backend beim Senden aktualisiert)
// matchData.homePin und matchData.guestPin bleiben die ursprünglichen Hashes aus baseData
// 5. Match-Status auf abgeschlossen setzen
// 6. Match-Status auf abgeschlossen setzen
matchData.isCompleted = true;
// 6. Gesamtstatistik berechnen und eintragen
// 7. Gesamtstatistik berechnen und eintragen
const overallScore = this.getOverallMatchScore();
// Gesamtpunkte berechnen und eintragen
@@ -1522,7 +1621,7 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
matchData.guestSets = parseInt(setScoreMatch[2]);
}
// 7. Gesamtpunkte (Games) berechnen und eintragen
// 8. Gesamtpunkte (Games) berechnen und eintragen
let totalHomeGames = 0;
let totalGuestGames = 0;
@@ -1581,7 +1680,7 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
});
}
// 8. Gesamtpunkte (Games) eintragen
// 9. Gesamtpunkte (Games) eintragen
matchData.homeGames = totalHomeGames;
matchData.guestGames = totalGuestGames;
@@ -1735,12 +1834,12 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
try {
// Zuerst Cookies initialisieren
await fetch(`http://localhost:3000/api/nuscore/init-cookies/${this.match.code}`, {
await fetch(`${backendBaseUrl}/api/nuscore/init-cookies/${this.match.code}`, {
method: 'POST'
});
// Dann Meeting-Daten laden
const response = await fetch(`http://localhost:3000/api/nuscore/meetinginfo/${this.match.code}`);
const response = await fetch(`${backendBaseUrl}/api/nuscore/meetinginfo/${this.match.code}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
@@ -1749,10 +1848,27 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
this.meetingData = await response.json();
console.log('[nuscore] /meetinginfo response:', this.meetingData);
// Initialisiere "Nicht angetreten"-Wert aus meetingData
if (this.meetingData.teamNotAppeared !== undefined) {
this.teamNotAppeared = this.meetingData.teamNotAppeared;
} else if (this.meetingData.homeTeamNotAppeared !== undefined || this.meetingData.guestTeamNotAppeared !== undefined) {
// Migration: Alte boolean-Werte in neuen Wert umwandeln
if (this.meetingData.homeTeamNotAppeared) {
this.teamNotAppeared = 'home';
} else if (this.meetingData.guestTeamNotAppeared) {
this.teamNotAppeared = 'guest';
} else {
this.teamNotAppeared = null;
}
} else if (this.meetingData.wo) {
// Wenn wo bereits gesetzt ist, leite daraus teamNotAppeared ab
this.teamNotAppeared = this.meetingData.wo === 'A' ? 'home' : (this.meetingData.wo === 'B' ? 'guest' : null);
}
// Lade detaillierte Meeting-Daten falls UUID verfügbar
if (this.meetingData.nuLigaMeetingUuid) {
try {
const detailsResponse = await fetch(`http://localhost:3000/api/nuscore/meetingdetails/${this.meetingData.nuLigaMeetingUuid}`);
const detailsResponse = await fetch(`${backendBaseUrl}/api/nuscore/meetingdetails/${this.meetingData.nuLigaMeetingUuid}`);
if (detailsResponse.ok) {
this.meetingDetails = await detailsResponse.json();
console.log('[nuscore] /meetingdetails response:', this.meetingDetails);
@@ -1771,6 +1887,11 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
// Ergebnisse initial auf Basis der Matrix vorbereiten
this.prepareResults();
// Wenn teamNotAppeared gesetzt ist und die Daten noch nicht angepasst wurden, wende die Anpassung an
if (this.teamNotAppeared !== null && !this.meetingData.wo) {
this.applyTeamNotAppeared();
}
} catch (error) {
console.error('❌ Fehler beim Laden der Meeting-Daten:', error);
this.error = error.message;
@@ -1789,6 +1910,18 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
result: '1:0'
}));
},
applyTeamNotAppeared() {
if (!this.meetingData || this.teamNotAppeared === null) {
return;
}
const woValue = this.teamNotAppeared === 'home' ? 'A' : 'B'; // A = Home, B = Guest
// Setze nur das wo-Flag im meetingData - nichts anderes ändern
this.meetingData.wo = woValue;
console.log('✅ Team "Nicht angetreten" angewendet:', this.teamNotAppeared, 'wo:', woValue);
},
resolveSide(label, side) {
// label z.B. "A1 B2" oder "DA1 DB1"
try {
@@ -2968,11 +3101,11 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
// Braunschweiger System
if (mode.includes('braunschweiger')) {
if (playerCount === 2) {
return 2; // Alle 2 müssen Doppel spielen
return 2; // Beide müssen Doppel spielen
} else if (playerCount === 3) {
return 2; // 2 von 3 müssen Doppel spielen
} else if (playerCount >= 4) {
return playerCount; // Alle müssen Doppel spielen
return 4; // 4 Spieler müssen Doppel spielen (2 Doppel-Paare)
}
return 0;
}
@@ -3119,8 +3252,8 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
explanation = 'beide Spieler müssen Doppel spielen';
} else if (selectedPlayers === 3) {
explanation = '2 von 3 Spielern müssen Doppel spielen';
} else if (requiredDoubles === selectedPlayers) {
explanation = `alle ${selectedPlayers} Spieler müssen Doppel spielen`;
} else if (selectedPlayers >= 4 && requiredDoubles === 4) {
explanation = '4 Spieler müssen Doppel spielen (2 Doppel-Paare)';
}
} else if (mode.includes('4') || mode.includes('paarkreuz') || mode.includes('bundes') || mode.includes('werner')) {
if (selectedPlayers === 3 && requiredDoubles === 2) {
@@ -3792,6 +3925,13 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
font-size: 14px;
}
.pin-not-required {
font-size: 12px;
color: #6c757d;
font-style: italic;
margin-top: 4px;
}
.submit-section {
text-align: center;
padding: 16px;
@@ -3842,6 +3982,32 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
color: var(--primary-color);
}
.not-appeared-select {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
background-color: white;
cursor: pointer;
transition: all 0.3s ease;
}
.not-appeared-select.not-appeared {
background-color: #fff3cd;
border-color: #ffc107;
color: #856404;
font-weight: 600;
}
.not-appeared-select:hover {
border-color: #999;
}
.not-appeared-select.not-appeared:hover {
border-color: #ff9800;
background-color: #ffe082;
}
.code-display {
display: flex;
align-items: center;

View File

@@ -308,7 +308,6 @@ export default {
components: {
SeasonSelector,
MatchReportApiDialog,
MatchReportDialog,
InfoDialog,
ConfirmDialog,
BaseDialog,