Update PDF parsing and document upload handling in team management
Enhanced the PDFParserService to support layout-based text extraction from PDFs using pdfjs-dist, improving parsing accuracy. Updated the team management view to streamline document uploads and parsing processes, removing unnecessary UI elements and consolidating upload logic. Improved error handling and user feedback during document processing, ensuring better user experience and clarity in case of issues.
This commit is contained in:
@@ -213,7 +213,7 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -241,26 +241,26 @@
|
||||
@delete-note="deleteNote"
|
||||
@close="closeNotesModal"
|
||||
/>
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
|
||||
<!-- Member Activities Dialog -->
|
||||
<MemberActivitiesDialog
|
||||
|
||||
@@ -139,67 +139,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upload-Bestätigung -->
|
||||
<div v-if="showLeagueSelection" class="upload-confirmation">
|
||||
<div class="selected-file-info">
|
||||
<strong>Ausgewählte Datei:</strong> {{ pendingUploadFile?.name }}
|
||||
<br>
|
||||
<strong>Typ:</strong> {{ pendingUploadType === 'code_list' ? 'Code-Liste' : 'Pin-Liste' }}
|
||||
<br>
|
||||
<strong>Team:</strong> {{ teamToEdit?.name }}
|
||||
<br>
|
||||
<strong>Liga:</strong> {{ getTeamLeagueName() }}
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button
|
||||
@click="confirmUploadAndParse"
|
||||
:disabled="parsingInProgress"
|
||||
class="confirm-parse-btn"
|
||||
>
|
||||
{{ parsingInProgress ? '⏳ Parse läuft...' : '🚀 Hochladen & Parsen' }}
|
||||
</button>
|
||||
<button @click="cancelUpload" class="cancel-parse-btn">
|
||||
Abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PDF-Parsing Bereich für bereits hochgeladene Dokumente -->
|
||||
<div v-if="teamDocuments.length > 0" class="pdf-parsing-section compact">
|
||||
<span class="section-title">📄 Hochgeladene Dokumente</span>
|
||||
<table class="document-table compact">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Dateiname</th>
|
||||
<th>Typ</th>
|
||||
<th>Größe</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="document in teamDocuments" :key="document.id" class="document-row">
|
||||
<td class="document-name">{{ document.originalFileName }}</td>
|
||||
<td class="document-type">
|
||||
<span class="type-badge" :class="document.documentType">
|
||||
{{ document.documentType === 'code_list' ? 'Code-Liste' : 'Pin-Liste' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="document-size">{{ formatFileSize(document.fileSize) }}</td>
|
||||
<td class="document-actions">
|
||||
<button
|
||||
@click="parsePDF(document)"
|
||||
:disabled="document.mimeType !== 'application/pdf'"
|
||||
class="parse-btn"
|
||||
:title="document.mimeType !== 'application/pdf' ? 'Nur PDF-Dateien können geparst werden' : 'PDF parsen und Matches extrahieren'"
|
||||
>
|
||||
🔍 Parsen
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Automatische Jobs Info für dieses Team -->
|
||||
<div v-if="getTeamJobInfo(teamToEdit)" class="team-job-info compact">
|
||||
<span class="section-title">🔄 Automatische Jobs</span>
|
||||
@@ -443,10 +382,8 @@ export default {
|
||||
const selectedSeasonId = ref(null);
|
||||
const currentSeason = ref(null);
|
||||
const teamDocuments = ref([]);
|
||||
const pendingUploadFile = ref(null);
|
||||
const pendingUploadType = ref(null);
|
||||
const showLeagueSelection = ref(false);
|
||||
const parsingInProgress = ref(false);
|
||||
const parsingDocuments = ref({});
|
||||
|
||||
// PDF-Dialog Variablen
|
||||
const showPDFViewer = ref(false);
|
||||
@@ -594,42 +531,34 @@ export default {
|
||||
|
||||
const uploadCodeList = () => {
|
||||
if (!teamToEdit.value) return;
|
||||
if (parsingInProgress.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Erstelle ein verstecktes File-Input-Element
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.pdf,.doc,.docx,.txt,.csv';
|
||||
input.onchange = async (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
// Speichere die Datei und den Typ für späteres Parsing
|
||||
pendingUploadFile.value = file;
|
||||
pendingUploadType.value = 'code_list';
|
||||
|
||||
// Zeige Liga-Auswahl für Parsing
|
||||
showLeagueSelection.value = true;
|
||||
await uploadAndParseDocument(file, 'code_list');
|
||||
};
|
||||
input.click();
|
||||
};
|
||||
|
||||
const uploadPinList = () => {
|
||||
if (!teamToEdit.value) return;
|
||||
if (parsingInProgress.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Erstelle ein verstecktes File-Input-Element
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.pdf,.doc,.docx,.txt,.csv';
|
||||
input.onchange = async (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
// Speichere die Datei und den Typ für späteres Parsing
|
||||
pendingUploadFile.value = file;
|
||||
pendingUploadType.value = 'pin_list';
|
||||
|
||||
// Zeige Liga-Auswahl für Parsing
|
||||
showLeagueSelection.value = true;
|
||||
await uploadAndParseDocument(file, 'pin_list');
|
||||
};
|
||||
input.click();
|
||||
};
|
||||
@@ -702,136 +631,153 @@ export default {
|
||||
}
|
||||
};
|
||||
|
||||
const confirmUploadAndParse = async () => {
|
||||
if (!pendingUploadFile.value || !teamToEdit.value?.leagueId) {
|
||||
alert('Team ist keiner Liga zugeordnet!');
|
||||
const uploadAndParseDocument = async (file, documentType) => {
|
||||
if (!teamToEdit.value?.leagueId) {
|
||||
await showInfo(
|
||||
'Hinweis',
|
||||
'Dieses Team ist keiner Liga zugeordnet.',
|
||||
'Bitte ordnen Sie dem Team zuerst eine Liga zu, damit Dokumente verarbeitet werden können.',
|
||||
'warning'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (parsingInProgress.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
parsingInProgress.value = true;
|
||||
|
||||
|
||||
try {
|
||||
|
||||
// Schritt 1: Datei als Team-Dokument hochladen
|
||||
const formData = new FormData();
|
||||
formData.append('document', pendingUploadFile.value);
|
||||
formData.append('documentType', pendingUploadType.value);
|
||||
|
||||
formData.append('document', file);
|
||||
formData.append('documentType', documentType);
|
||||
|
||||
const uploadResponse = await apiClient.post(`/team-documents/club-team/${teamToEdit.value.id}/upload`, formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Schritt 2: Datei parsen (nur für PDF/TXT-Dateien)
|
||||
const fileExtension = pendingUploadFile.value.name.toLowerCase().split('.').pop();
|
||||
|
||||
const fileExtension = file.name.toLowerCase().split('.').pop();
|
||||
const documentLabel = documentType === 'code_list' ? 'Code-Liste' : 'Pin-Liste';
|
||||
|
||||
if (fileExtension === 'pdf' || fileExtension === 'txt') {
|
||||
const parseResponse = await apiClient.post(`/team-documents/${uploadResponse.data.id}/parse?leagueid=${teamToEdit.value.leagueId}`);
|
||||
|
||||
|
||||
const { parseResult, saveResult } = parseResponse.data;
|
||||
|
||||
let message = `${pendingUploadType.value === 'code_list' ? 'Code-Liste' : 'Pin-Liste'} erfolgreich hochgeladen und geparst!\n\n`;
|
||||
|
||||
let message = `${documentLabel} erfolgreich hochgeladen und geparst!\n\n`;
|
||||
message += `Gefundene Spiele: ${parseResult.matchesFound}\n`;
|
||||
message += `Neue Spiele erstellt: ${saveResult.created}\n`;
|
||||
message += `Spiele aktualisiert: ${saveResult.updated}\n`;
|
||||
|
||||
message += `Spiele aktualisiert: ${saveResult.updated}`;
|
||||
|
||||
if (saveResult.errors.length > 0) {
|
||||
message += `\nFehler: ${saveResult.errors.length}\n`;
|
||||
message += `\n\nFehler: ${saveResult.errors.length}\n`;
|
||||
message += saveResult.errors.slice(0, 3).join('\n');
|
||||
if (saveResult.errors.length > 3) {
|
||||
message += `\n... und ${saveResult.errors.length - 3} weitere`;
|
||||
}
|
||||
}
|
||||
|
||||
// Debug-Informationen anzeigen wenn keine Matches gefunden wurden
|
||||
|
||||
let dialogTitle = 'Erfolg';
|
||||
let dialogType = 'success';
|
||||
|
||||
if (parseResult.matchesFound === 0) {
|
||||
message += `\n\n--- DEBUG-INFORMATIONEN ---\n`;
|
||||
message += `Text-Länge: ${parseResult.debugInfo.totalTextLength} Zeichen\n`;
|
||||
message += `Zeilen: ${parseResult.debugInfo.totalLines}\n`;
|
||||
message += `Erste Zeilen:\n`;
|
||||
parseResult.debugInfo.firstFewLines.forEach((line, index) => {
|
||||
message += `${index + 1}: "${line}"\n`;
|
||||
});
|
||||
message += `\nLetzte Zeilen:\n`;
|
||||
parseResult.debugInfo.lastFewLines.forEach((line, index) => {
|
||||
message += `${parseResult.debugInfo.totalLines - 5 + index + 1}: "${line}"\n`;
|
||||
});
|
||||
// Fehler-Dialog wenn nichts gefunden wurde
|
||||
await showInfo('Fehler', message, '', 'error');
|
||||
dialogTitle = 'Keine Spiele gefunden';
|
||||
dialogType = 'warning';
|
||||
if (parseResult.debugInfo) {
|
||||
message += `\n\nHinweis: Keine Spiele erkannt.\nZeilen im Dokument: ${parseResult.debugInfo.totalLines}`;
|
||||
}
|
||||
} else if (saveResult.errors.length > 0) {
|
||||
// Warnung wenn Spiele gefunden wurden, aber Fehler auftraten
|
||||
await showInfo('Warnung', message, '', 'warning');
|
||||
} else {
|
||||
// Erfolg wenn alles geklappt hat
|
||||
await showInfo('Erfolg', message, '', 'success');
|
||||
dialogTitle = 'Warnung';
|
||||
dialogType = 'warning';
|
||||
}
|
||||
|
||||
await showInfo(dialogTitle, message, '', dialogType);
|
||||
} else {
|
||||
// Für andere Dateitypen nur Upload-Bestätigung
|
||||
await showInfo('Information', `${pendingUploadType.value === 'code_list' ? 'Code-Liste' : 'Pin-Liste'} "${pendingUploadFile.value.name}" wurde erfolgreich hochgeladen!`, '', 'info');
|
||||
await showInfo('Information', `${documentLabel} "${file.name}" wurde erfolgreich hochgeladen!`, '', 'info');
|
||||
}
|
||||
|
||||
// Dokumente neu laden
|
||||
|
||||
await loadTeamDocuments();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Hochladen und Parsen der Datei:', error);
|
||||
await showInfo('Fehler', 'Fehler beim Hochladen und Parsen der Datei', '', 'error');
|
||||
const responseData = error?.response?.data || {};
|
||||
const errorMessage = responseData.message || responseData.error || error.message || 'Fehler beim Hochladen und Parsen der Datei';
|
||||
await showInfo('Fehler', errorMessage, '', 'error');
|
||||
} finally {
|
||||
parsingInProgress.value = false;
|
||||
pendingUploadFile.value = null;
|
||||
pendingUploadType.value = null;
|
||||
showLeagueSelection.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const cancelUpload = () => {
|
||||
pendingUploadFile.value = null;
|
||||
pendingUploadType.value = null;
|
||||
showLeagueSelection.value = false;
|
||||
};
|
||||
|
||||
const getTeamLeagueName = () => {
|
||||
if (!teamToEdit.value?.leagueId) return 'Keine Liga zugeordnet';
|
||||
const league = leagues.value.find(l => l.id === teamToEdit.value.leagueId);
|
||||
return league ? league.name : 'Unbekannte Liga';
|
||||
};
|
||||
|
||||
|
||||
|
||||
const parsePDF = async (document) => {
|
||||
// Finde das Team für dieses Dokument
|
||||
const parsePDF = async (document) => {
|
||||
const team = teams.value.find(t => t.id === document.clubTeamId);
|
||||
if (!team || !team.leagueId) {
|
||||
alert('Team ist keiner Liga zugeordnet!');
|
||||
await showInfo(
|
||||
'Hinweis',
|
||||
'Dieses Team ist keiner Liga zugeordnet.',
|
||||
'Bitte ordnen Sie dem Team zuerst eine Liga zu, um PDF-Dateien zu parsen.',
|
||||
'warning'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsingDocuments.value[document.id]) {
|
||||
return;
|
||||
}
|
||||
|
||||
parsingDocuments.value = {
|
||||
...parsingDocuments.value,
|
||||
[document.id]: true
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
const response = await apiClient.post(`/team-documents/${document.id}/parse?leagueid=${team.leagueId}`);
|
||||
|
||||
|
||||
const { parseResult, saveResult } = response.data;
|
||||
|
||||
let message = `PDF erfolgreich geparst!\n\n`;
|
||||
message += `Gefundene Spiele: ${parseResult.matchesFound}\n`;
|
||||
|
||||
let message = `Gefundene Spiele: ${parseResult.matchesFound}\n`;
|
||||
message += `Neue Spiele erstellt: ${saveResult.created}\n`;
|
||||
message += `Spiele aktualisiert: ${saveResult.updated}\n`;
|
||||
|
||||
if (saveResult.errors.length > 0) {
|
||||
message += `\nFehler: ${saveResult.errors.length}\n`;
|
||||
message += `Spiele aktualisiert: ${saveResult.updated}`;
|
||||
|
||||
let dialogTitle = 'Erfolg';
|
||||
let dialogType = 'success';
|
||||
|
||||
if (parseResult.matchesFound === 0) {
|
||||
dialogTitle = 'Keine Spiele gefunden';
|
||||
dialogType = 'warning';
|
||||
if (parseResult.debugInfo) {
|
||||
message += `\n\nHinweis: Keine Spiele erkannt.\nZeilen im Dokument: ${parseResult.debugInfo.totalLines}`;
|
||||
}
|
||||
} else if (saveResult.errors.length > 0) {
|
||||
dialogTitle = 'Warnung';
|
||||
dialogType = 'warning';
|
||||
message += `\n\nFehler: ${saveResult.errors.length}\n`;
|
||||
message += saveResult.errors.slice(0, 3).join('\n');
|
||||
if (saveResult.errors.length > 3) {
|
||||
message += `\n... und ${saveResult.errors.length - 3} weitere`;
|
||||
}
|
||||
}
|
||||
|
||||
this.showInfo('Fehler', message, '', 'error');
|
||||
|
||||
await showInfo(dialogTitle, message, '', dialogType);
|
||||
await loadTeamDocuments();
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Parsen der PDF:', error);
|
||||
this.showInfo('Fehler', 'Fehler beim Parsen der PDF-Datei', '', 'error');
|
||||
const responseData = error?.response?.data || {};
|
||||
const status = error?.response?.status;
|
||||
let errorMessage = responseData.message || responseData.error || error.message || 'Fehler beim Parsen der PDF-Datei';
|
||||
let details = '';
|
||||
|
||||
if (status === 404 && responseData.error === 'documentnotfound') {
|
||||
errorMessage = 'Das ausgewählte Dokument wurde nicht gefunden.';
|
||||
} else if (status === 400 && responseData.error === 'missingleagueid') {
|
||||
errorMessage = 'Für das ausgewählte Team wurde keine Liga übermittelt.';
|
||||
} else if (error.code === 'ENOENT' || errorMessage.includes('ENOENT')) {
|
||||
errorMessage = 'Die PDF-Datei konnte nicht gefunden werden.';
|
||||
details = 'Bitte laden Sie die Datei erneut hoch und versuchen Sie es noch einmal.';
|
||||
}
|
||||
|
||||
await showInfo('Fehler', errorMessage, details, 'error');
|
||||
} finally {
|
||||
const { [document.id]: _ignored, ...rest } = parsingDocuments.value;
|
||||
parsingDocuments.value = rest;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1289,10 +1235,8 @@ export default {
|
||||
selectedSeasonId,
|
||||
currentSeason,
|
||||
teamDocuments,
|
||||
pendingUploadFile,
|
||||
pendingUploadType,
|
||||
showLeagueSelection,
|
||||
parsingInProgress,
|
||||
parsingDocuments,
|
||||
showPDFViewer,
|
||||
pdfUrl,
|
||||
pdfDialogTitle,
|
||||
@@ -1317,9 +1261,6 @@ export default {
|
||||
uploadPinList,
|
||||
loadTeamDocuments,
|
||||
loadAllTeamDocuments,
|
||||
confirmUploadAndParse,
|
||||
cancelUpload,
|
||||
getTeamLeagueName,
|
||||
parsePDF,
|
||||
getTeamDocuments,
|
||||
showPDFDialog,
|
||||
|
||||
Reference in New Issue
Block a user