feat(member-notes): enhance member note functionality with structured data and observation tracking
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 58s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 58s
This commit is contained in:
@@ -33,15 +33,26 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>{{ $t('memberNotes.newNote') }}</label>
|
||||
<div v-if="structuredNotes" class="note-kind-picker" aria-label="Art der Notiz">
|
||||
<button v-for="option in noteKinds" :key="option.value" type="button" :class="['note-kind-chip', `is-${option.value}`, { active: localNoteKind === option.value }]" @click="localNoteKind = option.value">{{ option.label }}</button>
|
||||
</div>
|
||||
<textarea v-model="localNoteContent" :placeholder="$t('memberNotes.newNote')" rows="4" class="note-textarea"></textarea>
|
||||
<label v-if="structuredNotes" class="training-focus-label">Trainingsziel / nächster Fokus
|
||||
<input v-model.trim="localTrainingFocus" type="text" placeholder="z. B. Rückschlag kurz ablegen" class="training-focus-input" />
|
||||
</label>
|
||||
<button @click="handleAddNote" class="btn-primary">{{ $t('memberNotes.add') }}</button>
|
||||
</div>
|
||||
<div class="notes-list">
|
||||
<h4>{{ $t('memberNotes.notes') }}</h4>
|
||||
<ul>
|
||||
<li v-for="note in notes" :key="note.id" class="note-item">
|
||||
<li v-for="note in notes" :key="note.id" :class="['note-item', { 'observation-note': isObservation(note) }]">
|
||||
<button @click="$emit('delete-note', note.id)" class="trash-btn">🗑️</button>
|
||||
<span class="note-content">{{ note.content }}</span>
|
||||
<div class="note-copy">
|
||||
<span v-if="isObservation(note)" :class="['note-kind-badge', `is-${note.kind}`]">{{ kindLabel(note.kind) }}</span>
|
||||
<span class="note-content">{{ note.content }}</span>
|
||||
<span v-if="note.trainingFocus" class="note-training-focus"><strong>Trainingsziel:</strong> {{ note.trainingFocus }}</span>
|
||||
<span v-if="note.sourceLabel" class="note-source">{{ sourcePrefix(note.sourceType) }}{{ note.sourceLabel }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -85,13 +96,23 @@ export default {
|
||||
noteContent: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
structuredNotes: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
sourceDefaults: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'close', 'add-note', 'delete-note', 'add-tag', 'remove-tag', 'update:noteContent', 'update:selectedTags'],
|
||||
data() {
|
||||
return {
|
||||
localNoteContent: this.noteContent,
|
||||
localSelectedTags: this.selectedTags
|
||||
localSelectedTags: this.selectedTags,
|
||||
localNoteKind: 'general',
|
||||
localTrainingFocus: ''
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
@@ -108,14 +129,40 @@ export default {
|
||||
this.$emit('update:selectedTags', newVal);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
noteKinds() {
|
||||
return [
|
||||
{ value: 'general', label: 'Allgemeine Notiz' },
|
||||
{ value: 'strength', label: 'Stärke' },
|
||||
{ value: 'development', label: 'Entwicklungsfeld' },
|
||||
{ value: 'training_focus', label: 'Trainingsfokus' }
|
||||
];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
this.$emit('update:modelValue', false);
|
||||
this.$emit('close');
|
||||
},
|
||||
handleAddNote() {
|
||||
this.$emit('add-note', this.localNoteContent);
|
||||
const content = this.localNoteContent.trim();
|
||||
if (!content) return;
|
||||
const payload = this.structuredNotes
|
||||
? { content, kind: this.localNoteKind, trainingFocus: this.localTrainingFocus.trim() || null, ...this.sourceDefaults }
|
||||
: content;
|
||||
this.$emit('add-note', payload);
|
||||
this.localNoteContent = '';
|
||||
this.localTrainingFocus = '';
|
||||
this.localNoteKind = 'general';
|
||||
},
|
||||
isObservation(note) {
|
||||
return note && note.kind && note.kind !== 'general';
|
||||
},
|
||||
kindLabel(kind) {
|
||||
return ({ strength: 'Stärke', development: 'Entwicklungsfeld', training_focus: 'Trainingsfokus' })[kind] || 'Allgemeine Notiz';
|
||||
},
|
||||
sourcePrefix(type) {
|
||||
return type === 'tournament' ? 'Turnier · ' : type === 'match' ? 'Mannschaftsspiel · ' : '';
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -183,6 +230,16 @@ export default {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.note-kind-picker { display:flex; flex-wrap:wrap; gap:.4rem; }
|
||||
.note-kind-chip,.note-kind-badge { border:1px solid var(--border-color); background:var(--surface-muted); color:var(--text-color); font:inherit; font-size:.8rem; font-weight:600; padding:.28rem .55rem; border-radius:7px; }
|
||||
.note-kind-chip { cursor:pointer; }
|
||||
.note-kind-chip.active { outline:2px solid currentColor; outline-offset:1px; }
|
||||
.is-strength { color:#17764a; background:#edf8f1; border-color:#9bd5b5; }
|
||||
.is-development { color:#a85d14; background:#fff4e8; border-color:#edbd7e; }
|
||||
.is-training_focus { color:#2563a7; background:#edf5ff; border-color:#a8c9ec; }
|
||||
.training-focus-label { display:flex; flex-direction:column; gap:.35rem; font-size:.9rem; }
|
||||
.training-focus-input { width:100%; box-sizing:border-box; padding:.6rem .75rem; border:1px solid var(--border-color); border-radius:8px; font:inherit; background:var(--surface-muted); color:var(--text-color); }
|
||||
|
||||
.notes-list h4 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 1rem;
|
||||
@@ -196,7 +253,7 @@ export default {
|
||||
|
||||
.note-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
@@ -205,6 +262,11 @@ export default {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.observation-note { border-left:4px solid var(--primary-color); }
|
||||
.note-copy { flex:1; display:flex; flex-direction:column; align-items:flex-start; gap:.3rem; min-width:0; }
|
||||
.note-training-focus { color:var(--text-muted); font-size:.88rem; }
|
||||
.note-source { color:var(--text-muted); font-size:.78rem; }
|
||||
|
||||
.note-content {
|
||||
flex: 1;
|
||||
}
|
||||
@@ -249,5 +311,7 @@ export default {
|
||||
height: auto;
|
||||
max-height: 300px;
|
||||
}
|
||||
.note-kind-picker { gap:.3rem; }
|
||||
.note-kind-chip { flex:1 1 130px; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -673,6 +673,7 @@
|
||||
v-model="showNotesModal"
|
||||
:member="memberToEdit"
|
||||
:notes="notes"
|
||||
:structured-notes="true"
|
||||
v-model:note-content="newNoteContent"
|
||||
:selected-tags="[]"
|
||||
:available-tags="[]"
|
||||
@@ -2486,10 +2487,15 @@ export default {
|
||||
this.notes = response.data;
|
||||
this.showNotesModal = true;
|
||||
},
|
||||
async addNote() {
|
||||
async addNote(note = null) {
|
||||
const payload = typeof note === 'object' && note !== null ? note : { content: this.newNoteContent };
|
||||
const response = await apiClient.post('/membernotes', {
|
||||
memberId: this.selectedMember.id,
|
||||
content: this.newNoteContent,
|
||||
content: payload.content,
|
||||
kind: payload.kind,
|
||||
trainingFocus: payload.trainingFocus,
|
||||
sourceType: payload.sourceType,
|
||||
sourceLabel: payload.sourceLabel,
|
||||
clubId: this.currentClub
|
||||
});
|
||||
this.notes = response.data;
|
||||
@@ -2501,9 +2507,9 @@ export default {
|
||||
});
|
||||
this.notes = response.data;
|
||||
},
|
||||
openNotesModal(member) {
|
||||
async openNotesModal(member) {
|
||||
this.memberToEdit = member;
|
||||
this.showNotesModal = true;
|
||||
await this.loadNotes(member);
|
||||
},
|
||||
closeNotesModal() {
|
||||
this.showNotesModal = false;
|
||||
|
||||
@@ -508,6 +508,10 @@
|
||||
<label class="opponent-note">Stärken Gegner/in<textarea v-model="tacticPlan.opponentStrengths" placeholder="Gefährliche Aufschläge, Muster, Lieblingsseiten …"></textarea></label>
|
||||
<label class="opponent-note">Schwächen Gegner/in<textarea v-model="tacticPlan.opponentWeaknesses" placeholder="Wo lässt sich Druck erzeugen?"></textarea></label>
|
||||
</div>
|
||||
<div class="tactic-observation-action">
|
||||
<div><strong>Spielerbeobachtung</strong><span>Wird in den gemeinsamen Mitgliedsnotizen für das gezielte Training gespeichert.</span><span v-if="!tacticPlan.memberId" id="tournament-observation-hint" class="tactic-observation-hint">Bitte zuerst eine eigene Spielerin bzw. einen eigenen Spieler auswählen.</span></div>
|
||||
<button type="button" class="btn-secondary" :disabled="!tacticPlan.memberId" :aria-describedby="!tacticPlan.memberId ? 'tournament-observation-hint' : null" :title="!tacticPlan.memberId ? 'Bitte zuerst eine eigene Spielerin bzw. einen eigenen Spieler auswählen.' : 'Beobachtung in Mitgliedsnotizen festhalten'" @click="openTournamentObservation">Beobachtung festhalten</button>
|
||||
</div>
|
||||
<TacticBoard :key="tacticPlan.id || 'new-tactic-plan'" v-model="tacticPlan.drawingData" />
|
||||
<div class="editor-actions"><span v-if="tacticPlanDirty" class="dirty-note">Ungespeicherte Änderungen</span><button class="btn-primary" :disabled="tacticSaving" @click="saveTacticPlan">{{ tacticSaving ? 'Speichert …' : 'Speichern' }}</button></div>
|
||||
</section>
|
||||
@@ -556,6 +560,18 @@
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
<MemberNotesDialog
|
||||
v-model="tournamentObservationDialog.isOpen"
|
||||
:member="tournamentObservationDialog.member"
|
||||
:notes="tournamentObservationDialog.notes"
|
||||
:structured-notes="true"
|
||||
:source-defaults="tournamentObservationSource"
|
||||
:selected-tags="[]"
|
||||
:available-tags="[]"
|
||||
@add-note="addTournamentObservation"
|
||||
@delete-note="deleteTournamentObservation"
|
||||
@close="closeTournamentObservation"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -571,6 +587,7 @@ import PDFGenerator from '../components/PDFGenerator.js';
|
||||
import BaseDialog from '../components/BaseDialog.vue';
|
||||
import MemberSelectionDialog from '../components/MemberSelectionDialog.vue';
|
||||
import TacticBoard from '../components/tournament/TacticBoard.vue';
|
||||
import MemberNotesDialog from '../components/MemberNotesDialog.vue';
|
||||
export default {
|
||||
name: 'OfficialTournaments',
|
||||
components: {
|
||||
@@ -578,7 +595,8 @@ export default {
|
||||
ConfirmDialog,
|
||||
BaseDialog,
|
||||
MemberSelectionDialog,
|
||||
TacticBoard
|
||||
TacticBoard,
|
||||
MemberNotesDialog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -624,6 +642,7 @@ export default {
|
||||
autoRegistering: false,
|
||||
suggestions: [], fetchingSuggestions: false, suggestionError: '',
|
||||
tacticPlans: [], tacticPlan: null, tacticPlansSnapshot: '', tacticsLoading: false, tacticSaving: false, tacticsError: '', tacticTournamentId: null, tacticLoadToken: 0, reloadToken: 0,
|
||||
tournamentObservationDialog: { isOpen: false, member: null, notes: [] },
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -1015,6 +1034,12 @@ export default {
|
||||
},
|
||||
tacticPlanDirty() {
|
||||
return !!this.tacticPlan && JSON.stringify(this.tacticPlan) !== this.tacticPlansSnapshot;
|
||||
},
|
||||
tournamentObservationSource() {
|
||||
if (!this.tacticPlan) return {};
|
||||
const tournament = (this.list || []).find((item) => String(item.id) === String(this.uploadedId));
|
||||
const competition = this.tacticEligibleOptions.find((option) => option.value === this.tacticPlan.selection)?.label?.split(' – ')[1] || 'Konkurrenz';
|
||||
return { sourceType: 'tournament', sourceLabel: [tournament?.title || 'Öffentliches Turnier', competition, this.tacticPlan.round, this.tacticPlan.opponentName ? `gegen ${this.tacticPlan.opponentName}` : ''].filter(Boolean).join(' · ') };
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -1456,6 +1481,32 @@ export default {
|
||||
},
|
||||
rememberTacticSnapshot() { this.tacticPlansSnapshot = JSON.stringify(this.tacticPlan); },
|
||||
async openTactics() { this.activeTab = 'tactics'; await this.loadTacticPlans(); },
|
||||
async openTournamentObservation() {
|
||||
const member = (this.members || []).find((item) => String(item.id) === String(this.tacticPlan?.memberId));
|
||||
if (!member) { this.tacticsError = 'Bitte zuerst eine eigene Spielerin bzw. einen eigenen Spieler auswählen.'; return; }
|
||||
this.tournamentObservationDialog.member = member;
|
||||
this.tournamentObservationDialog.notes = [];
|
||||
this.tournamentObservationDialog.isOpen = true;
|
||||
try {
|
||||
const response = await apiClient.get(`/membernotes/${member.id}`, { params: { clubId: this.currentClub } });
|
||||
if (String(this.tournamentObservationDialog.member?.id) === String(member.id)) this.tournamentObservationDialog.notes = response.data || [];
|
||||
} catch (error) { this.tacticsError = getSafeErrorMessage(error, 'Mitgliedsnotizen konnten nicht geladen werden.'); }
|
||||
},
|
||||
async addTournamentObservation(note) {
|
||||
const member = this.tournamentObservationDialog.member;
|
||||
if (!member || !note?.content) return;
|
||||
try {
|
||||
const response = await apiClient.post('/membernotes', { memberId: member.id, clubId: this.currentClub, ...note });
|
||||
this.tournamentObservationDialog.notes = response.data || [];
|
||||
} catch (error) { this.tacticsError = getSafeErrorMessage(error, 'Beobachtung konnte nicht gespeichert werden.'); }
|
||||
},
|
||||
async deleteTournamentObservation(noteId) {
|
||||
try {
|
||||
const response = await apiClient.delete(`/membernotes/${noteId}`, { data: { clubId: this.currentClub } });
|
||||
this.tournamentObservationDialog.notes = response.data || [];
|
||||
} catch (error) { this.tacticsError = getSafeErrorMessage(error, 'Notiz konnte nicht gelöscht werden.'); }
|
||||
},
|
||||
closeTournamentObservation() { this.tournamentObservationDialog = { isOpen: false, member: null, notes: [] }; },
|
||||
async loadTacticPlans(requestedTournamentId = String(this.uploadedId || '')) {
|
||||
if (!requestedTournamentId) return;
|
||||
if (this.tacticTournamentId !== requestedTournamentId) this.resetTacticsForTournament(requestedTournamentId);
|
||||
@@ -2052,6 +2103,9 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
.official-tournaments { display: flex; flex-direction: column; gap: 0.75rem; }
|
||||
.tactic-observation-action { display:flex; justify-content:space-between; align-items:center; gap:.8rem; margin:-.1rem 0 .85rem; padding:.65rem .75rem; border:1px solid #cbd9e8; background:#f7fafc; }
|
||||
.tactic-observation-action strong,.tactic-observation-action span { display:block; }.tactic-observation-action span { margin-top:.15rem; color:#64748b; font-size:.82rem; }
|
||||
.tactic-observation-action .tactic-observation-hint { color:#9a6511; font-weight:600; }
|
||||
.tactics-header { display:flex; align-items:flex-start; justify-content:space-between; gap:1rem; margin:.4rem 0 1rem; }
|
||||
.tactics-header h3,.editor-heading h4 { margin:0; color:#17366d; }.tactics-header p { margin:.3rem 0 0; color:#64748b; max-width:720px; }
|
||||
.tactics-workspace { display:grid; grid-template-columns:minmax(205px, .38fr) minmax(0, 1fr); border:1px solid #cbd9e8; background:#fff; min-height:420px; }.tactic-list { padding:.55rem; background:#eef3f8; border-right:1px solid #cbd9e8; display:flex; flex-direction:column; gap:.35rem; }.tactic-list-item { appearance:none; text-align:left; background:#fff; border:1px solid #d5dfe9; padding:.6rem; cursor:pointer; color:#203247; }.tactic-list-item strong,.tactic-list-item span { display:block; }.tactic-list-item span { color:#667789; font-size:.82rem; margin-top:.2rem; }.tactic-list-item.active { border-left:4px solid #d79727; background:#fff9ed; }.tactic-editor { padding:1rem; }.editor-heading,.editor-actions { display:flex; justify-content:space-between; align-items:center; gap:.75rem; margin-bottom:.8rem; }.tactic-form-grid,.tactic-notes-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:.75rem; margin-bottom:.85rem; }.tactic-form-grid label,.tactic-notes-grid label { color:#26394a; font-weight:600; font-size:.88rem; }.tactic-form-grid input,.tactic-form-grid select,.tactic-notes-grid textarea { display:block; box-sizing:border-box; width:100%; margin-top:.28rem; border:1px solid #b9c8d7; padding:.52rem; font:inherit; background:#fff; }.tactic-notes-grid textarea { min-height:82px; resize:vertical; }.own-note textarea { border-left:4px solid #d79727; }.opponent-note textarea { border-left:4px solid #dc7865; }.tactics-error { color:#a43d30; background:#fff0eb; border-left:3px solid #dc7865; padding:.6rem; }.dirty-note { color:#9a6511; font-size:.88rem; }.danger-button { color:#9b3326; }.tactic-list-empty { color:#65758a; font-size:.88rem; padding:.3rem; }.tactic-empty { display:flex; flex-direction:column; gap:.35rem; text-align:left; }
|
||||
|
||||
@@ -338,7 +338,8 @@
|
||||
<p><strong>{{ $t('schedule.time') }}:</strong> {{ playerSelectionDialog.match?.time ? playerSelectionDialog.match.time.toString().slice(0, 5) + ' ' + $t('common.time') : 'N/A' }}</p>
|
||||
</div>
|
||||
|
||||
<div class="player-list">
|
||||
<div class="player-list" tabindex="0" aria-label="Spielerliste; auf kleinen Bildschirmen horizontal scrollbar">
|
||||
<p class="player-list-scroll-hint">Für weitere Spalten seitlich wischen.</p>
|
||||
<table class="player-selection-table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -346,6 +347,7 @@
|
||||
<th>{{ $t('schedule.ready') }}</th>
|
||||
<th>{{ $t('schedule.planned') }}</th>
|
||||
<th>{{ $t('schedule.played') }}</th>
|
||||
<th>Beobachtung</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -377,6 +379,7 @@
|
||||
@change="togglePlayerPlayed(member)"
|
||||
/>
|
||||
</td>
|
||||
<td class="observation-cell"><button type="button" class="btn-observation" @click="openMatchObservation(member)">Beobachtung</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -393,6 +396,19 @@
|
||||
</div>
|
||||
</BaseDialog>
|
||||
|
||||
<MemberNotesDialog
|
||||
v-model="matchObservationDialog.isOpen"
|
||||
:member="matchObservationDialog.member"
|
||||
:notes="matchObservationDialog.notes"
|
||||
:structured-notes="true"
|
||||
:source-defaults="matchObservationSource"
|
||||
:selected-tags="[]"
|
||||
:available-tags="[]"
|
||||
@add-note="addMatchObservation"
|
||||
@delete-note="deleteMatchObservation"
|
||||
@close="closeMatchObservation"
|
||||
/>
|
||||
|
||||
<BaseDialog
|
||||
v-model="friendlyResultDialog.isOpen"
|
||||
:title="`${friendlyResultReadonly ? 'Ergebnis' : 'Ergebniseingabe'} - ${friendlyResultDialog.match?.homeTeam?.name || ''} vs ${friendlyResultDialog.match?.guestTeam?.name || ''}`"
|
||||
@@ -662,6 +678,7 @@ import BaseDialog from '../components/BaseDialog.vue';
|
||||
import CsvImportDialog from '../components/CsvImportDialog.vue';
|
||||
import ScheduleLayoutShell from '../components/schedule/ScheduleLayoutShell.vue';
|
||||
import FriendlyParticipantsColumn from '../components/schedule/FriendlyParticipantsColumn.vue';
|
||||
import MemberNotesDialog from '../components/MemberNotesDialog.vue';
|
||||
import {
|
||||
connectSocket,
|
||||
disconnectSocket,
|
||||
@@ -695,7 +712,8 @@ export default {
|
||||
BaseDialog,
|
||||
CsvImportDialog,
|
||||
ScheduleLayoutShell,
|
||||
FriendlyParticipantsColumn
|
||||
FriendlyParticipantsColumn,
|
||||
MemberNotesDialog
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['isAuthenticated', 'currentClub', 'clubs', 'currentClubName']),
|
||||
@@ -784,6 +802,12 @@ export default {
|
||||
friendlyMatchesLabel() {
|
||||
return 'Freundschaftsspiele';
|
||||
},
|
||||
matchObservationSource() {
|
||||
const match = this.playerSelectionDialog.match;
|
||||
if (!match) return {};
|
||||
const matchup = `${match.homeTeam?.name || 'Heim'} – ${match.guestTeam?.name || 'Gast'}`;
|
||||
return { sourceType: 'match', sourceLabel: [matchup, match.date ? this.formatDate(match.date) : ''].filter(Boolean).join(' · ') };
|
||||
},
|
||||
friendlyInvitationTargetClubs() {
|
||||
return (this.clubs || []).filter((club) => Number(club.id) !== Number(this.currentClub));
|
||||
},
|
||||
@@ -862,6 +886,7 @@ export default {
|
||||
loading: false,
|
||||
readonly: false
|
||||
},
|
||||
matchObservationDialog: { isOpen: false, member: null, notes: [] },
|
||||
locationDialog: {
|
||||
isOpen: false,
|
||||
match: null
|
||||
@@ -1342,6 +1367,41 @@ export default {
|
||||
this.playerSelectionDialog.match = null;
|
||||
this.playerSelectionDialog.members = [];
|
||||
},
|
||||
async openMatchObservation(member) {
|
||||
if (!member?.id) return;
|
||||
this.matchObservationDialog.member = member;
|
||||
this.matchObservationDialog.notes = [];
|
||||
this.matchObservationDialog.isOpen = true;
|
||||
try {
|
||||
const response = await apiClient.get(`/membernotes/${member.id}`, { params: { clubId: this.currentClub } });
|
||||
if (this.matchObservationDialog.member?.id === member.id) this.matchObservationDialog.notes = response.data || [];
|
||||
} catch (error) {
|
||||
await this.showInfo(this.$t('messages.error'), 'Mitgliedsnotizen konnten nicht geladen werden.', getSafeErrorMessage(error), 'error');
|
||||
}
|
||||
},
|
||||
async addMatchObservation(note) {
|
||||
const member = this.matchObservationDialog.member;
|
||||
if (!member || !note?.content) return;
|
||||
try {
|
||||
const response = await apiClient.post('/membernotes', { memberId: member.id, clubId: this.currentClub, ...note });
|
||||
this.matchObservationDialog.notes = response.data || [];
|
||||
} catch (error) {
|
||||
await this.showInfo(this.$t('messages.error'), 'Beobachtung konnte nicht gespeichert werden.', getSafeErrorMessage(error), 'error');
|
||||
}
|
||||
},
|
||||
async deleteMatchObservation(noteId) {
|
||||
try {
|
||||
const response = await apiClient.delete(`/membernotes/${noteId}`, { data: { clubId: this.currentClub } });
|
||||
this.matchObservationDialog.notes = response.data || [];
|
||||
} catch (error) {
|
||||
await this.showInfo(this.$t('messages.error'), 'Notiz konnte nicht gelöscht werden.', getSafeErrorMessage(error), 'error');
|
||||
}
|
||||
},
|
||||
closeMatchObservation() {
|
||||
this.matchObservationDialog.isOpen = false;
|
||||
this.matchObservationDialog.member = null;
|
||||
this.matchObservationDialog.notes = [];
|
||||
},
|
||||
openLocationDialog(match) {
|
||||
if (!match?.location) {
|
||||
return;
|
||||
@@ -3629,6 +3689,8 @@ li {
|
||||
.player-selection-table tbody tr:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.btn-observation { border:1px solid var(--border-color); background:var(--surface-muted); color:var(--primary-color); border-radius:7px; padding:.35rem .55rem; font:inherit; font-size:.8rem; font-weight:600; cursor:pointer; }
|
||||
.btn-observation:hover { background:var(--primary-color); color:var(--text-on-primary); }
|
||||
|
||||
.player-name {
|
||||
font-weight: 500;
|
||||
@@ -3637,7 +3699,12 @@ li {
|
||||
|
||||
.player-list {
|
||||
overflow-x: auto;
|
||||
max-width: 100%;
|
||||
overscroll-behavior-inline: contain;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
.player-list:focus-visible { outline: 2px solid var(--primary-color); outline-offset: 2px; }
|
||||
.player-list-scroll-hint { display:none; }
|
||||
|
||||
/* Gallery Styles */
|
||||
.gallery-dialog-content {
|
||||
@@ -4113,6 +4180,8 @@ li {
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.player-list { border: 1px solid var(--border-color); border-radius: 8px; }
|
||||
.player-list-scroll-hint { display:block; position:sticky; left:0; margin:0; padding:7px 10px; background:var(--surface-muted); color:var(--text-muted); font-size:.78rem; }
|
||||
.player-selection-table {
|
||||
min-width: 520px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user