feat(DiaryView, i18n): enhance diary view with new training day features and localization updates
- Added new sections in the DiaryView for displaying active training day details, including training window, participants, and free activities. - Implemented toggle functionality for displaying training day and group details, improving user interaction. - Expanded German localization file with new keys and translations related to training days, groups, and activities, enhancing the user experience for German-speaking users.
This commit is contained in:
87
frontend/src/components/DiaryActivitiesPanel.vue
Normal file
87
frontend/src/components/DiaryActivitiesPanel.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<section class="diary-sidebar-section">
|
||||
<h3 class="clickable" @click="$emit('toggle-box')">
|
||||
{{ $t('diary.activities') }} ({{ activities.length }}) <span>{{ showActivitiesBox ? '-' : '+' }}</span>
|
||||
</h3>
|
||||
<div v-if="showActivitiesBox" class="collapsible-box">
|
||||
<textarea :value="newActivity" @input="$emit('update:newActivity', $event.target.value)"></textarea>
|
||||
<button @click="$emit('add-activity')">{{ $t('diary.addActivity') }}</button>
|
||||
<ul v-if="activities.length">
|
||||
<li v-for="activity in activities" :key="activity.id">
|
||||
{{ activity.description }}
|
||||
</li>
|
||||
</ul>
|
||||
<div v-else class="activity-empty-state">{{ $t('diary.noFreeActivitiesYet') }}</div>
|
||||
<multiselect
|
||||
:model-value="selectedActivityTags"
|
||||
:options="availableTags"
|
||||
:placeholder="$t('diary.selectTags')"
|
||||
label="name"
|
||||
track-by="id"
|
||||
multiple
|
||||
:close-on-select="true"
|
||||
:allow-empty="false"
|
||||
@update:model-value="$emit('update:selectedActivityTags', $event)"
|
||||
@tag="$emit('add-tag', $event)"
|
||||
@remove="$emit('remove-tag', $event)"
|
||||
@keydown.enter.prevent="$emit('add-tag-from-input')"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Multiselect from 'vue-multiselect';
|
||||
|
||||
export default {
|
||||
name: 'DiaryActivitiesPanel',
|
||||
components: {
|
||||
Multiselect
|
||||
},
|
||||
props: {
|
||||
showActivitiesBox: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
newActivity: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
activities: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
availableTags: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
selectedActivityTags: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
'toggle-box',
|
||||
'update:newActivity',
|
||||
'add-activity',
|
||||
'update:selectedActivityTags',
|
||||
'add-tag',
|
||||
'remove-tag',
|
||||
'add-tag-from-input'
|
||||
]
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.diary-sidebar-section {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.activity-empty-state {
|
||||
padding: 0.7rem 0.75rem;
|
||||
border-radius: 8px;
|
||||
background: #f3f7fa;
|
||||
color: #516978;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
</style>
|
||||
166
frontend/src/components/DiaryParticipantsPanel.vue
Normal file
166
frontend/src/components/DiaryParticipantsPanel.vue
Normal file
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<section class="diary-sidebar-section">
|
||||
<h3>{{ $t('diary.participants') }} ({{ participants.length }})</h3>
|
||||
<div class="participant-toolbar">
|
||||
<input
|
||||
:value="participantSearchQuery"
|
||||
type="search"
|
||||
class="participant-search-input"
|
||||
:placeholder="$t('diary.searchParticipants')"
|
||||
@input="$emit('update:participantSearchQuery', $event.target.value)"
|
||||
>
|
||||
<div class="participant-filter-chips">
|
||||
<button type="button" class="participant-filter-chip" :class="{ active: participantFilter === 'all' }" @click="$emit('update:participantFilter', 'all')">{{ $t('diary.filterAll') }}</button>
|
||||
<button type="button" class="participant-filter-chip" :class="{ active: participantFilter === 'present' }" @click="$emit('update:participantFilter', 'present')">{{ $t('diary.filterPresent') }}</button>
|
||||
<button type="button" class="participant-filter-chip" :class="{ active: participantFilter === 'absent' }" @click="$emit('update:participantFilter', 'absent')">{{ $t('diary.filterAbsent') }}</button>
|
||||
<button type="button" class="participant-filter-chip" :class="{ active: participantFilter === 'test' }" @click="$emit('update:participantFilter', 'test')">{{ $t('diary.filterTest') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<ul>
|
||||
<li v-for="member in members" :key="member.id" class="checkbox-item participant-row"
|
||||
:class="{
|
||||
'row-inactive': !member.active,
|
||||
'row-test': member.testMembership && !member.memberFormHandedOver,
|
||||
'row-test-form': member.testMembership && member.memberFormHandedOver
|
||||
}">
|
||||
<label class="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
:value="member.id"
|
||||
:checked="participants.includes(member.id)"
|
||||
@change="$emit('toggle-participant', member.id)"
|
||||
>
|
||||
</label>
|
||||
<span class="clickable participant-name" @click.stop="$emit('open-notes', member)">
|
||||
<span v-if="member.testMembership && member.trainingParticipations >= 6" class="warning-icon warning-icon-severe" :title="$t('members.sixOrMoreParticipations')">🛑</span>
|
||||
<span v-else-if="member.testMembership && member.trainingParticipations >= 3" class="warning-icon" :title="$t('members.threeOrMoreParticipations')">⚠️</span>
|
||||
{{ member.firstName }} {{ member.lastName }}
|
||||
</span>
|
||||
<div class="participant-actions">
|
||||
<select
|
||||
v-if="participants.includes(member.id) && groups.length > 0"
|
||||
:value="memberGroupsMap[member.id] ?? ''"
|
||||
class="member-group-select"
|
||||
@change="$emit('update-member-group', { memberId: member.id, groupId: $event.target.value })"
|
||||
>
|
||||
<option value="">-</option>
|
||||
<option v-for="group in groups" :key="group.id" :value="String(group.id)">
|
||||
{{ group.name }}
|
||||
</option>
|
||||
</select>
|
||||
<span @click="$emit('show-pic', member)" class="img-icon" v-if="member.hasImage">🖼</span>
|
||||
<span
|
||||
v-if="member.testMembership === true && member.memberFormHandedOver !== true"
|
||||
@click.stop="$emit('mark-form', member)"
|
||||
class="pointer form-handover-icon"
|
||||
:title="$t('diary.formHandedOver')"
|
||||
>
|
||||
📄
|
||||
</span>
|
||||
<span class="pointer" @click="$emit('open-tags', member)">ℹ️</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="add-participant">
|
||||
<button @click="$emit('open-quick-add')" class="quick-add-btn">{{ $t('diary.quickAdd') }}</button>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'DiaryParticipantsPanel',
|
||||
props: {
|
||||
members: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
participants: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
participantSearchQuery: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
participantFilter: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
groups: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
memberGroupsMap: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
'update:participantSearchQuery',
|
||||
'update:participantFilter',
|
||||
'toggle-participant',
|
||||
'update-member-group',
|
||||
'open-notes',
|
||||
'show-pic',
|
||||
'mark-form',
|
||||
'open-tags',
|
||||
'open-quick-add'
|
||||
]
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.diary-sidebar-section {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.participant-toolbar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.65rem;
|
||||
margin-bottom: 0.85rem;
|
||||
}
|
||||
|
||||
.participant-search-input {
|
||||
width: 100%;
|
||||
padding: 0.55rem 0.7rem;
|
||||
border: 1px solid #ccd8e0;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.participant-filter-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.45rem;
|
||||
}
|
||||
|
||||
.participant-filter-chip {
|
||||
border: 1px solid #ccd8e0;
|
||||
background: #f3f7fa;
|
||||
color: #365266;
|
||||
border-radius: 999px;
|
||||
padding: 0.35rem 0.75rem;
|
||||
font-size: 0.85rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.participant-filter-chip.active {
|
||||
background: #dcebf5;
|
||||
border-color: #8eb7d1;
|
||||
color: #14374c;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.member-group-select {
|
||||
margin-left: 10px;
|
||||
font-size: 10px;
|
||||
width: 5em;
|
||||
padding: 2px 4px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
background-color: white;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
@@ -400,20 +400,32 @@
|
||||
"applySuggestion": "Vorschlag übernehmen",
|
||||
"skipSuggestion": "Ohne Vorschlag fortfahren",
|
||||
"createNewDate": "Neues Datum anlegen",
|
||||
"activeTrainingDay": "Aktiver Trainingstag",
|
||||
"trainingDaySection": "Trainingstag",
|
||||
"trainingStart": "Trainingsbeginn",
|
||||
"trainingEnd": "Trainingsende",
|
||||
"trainingWindow": "Trainingsfenster",
|
||||
"trainingWindowUnset": "Noch nicht gesetzt",
|
||||
"groupsLabel": "Gruppen",
|
||||
"groupsSection": "Gruppen",
|
||||
"createDate": "Datum anlegen",
|
||||
"editTrainingTimes": "Trainingszeiten bearbeiten",
|
||||
"updateTimes": "Zeiten aktualisieren",
|
||||
"groupManagement": "Gruppenverwaltung",
|
||||
"createGroups": "Gruppen erstellen",
|
||||
"trainingPlan": "Trainingsplan",
|
||||
"planActivitiesCount": "Plan-Aktivitäten",
|
||||
"timeblocksCount": "Zeitblöcke",
|
||||
"planEmptyState": "Im Trainingsplan ist noch nichts eingetragen.",
|
||||
"planAddHint": "Neue Plan-Elemente fügst du über die Aktionen oben hinzu.",
|
||||
"startTime": "Startzeit",
|
||||
"group": "Gruppe...",
|
||||
"timeblock": "Zeitblock",
|
||||
"assignParticipants": "Teilnehmer zuordnen",
|
||||
"addTimeblock": "Zeitblock",
|
||||
"activities": "Aktivitäten",
|
||||
"freeActivities": "Freie Aktivitäten",
|
||||
"noFreeActivitiesYet": "Noch keine freien Aktivitäten erfasst.",
|
||||
"addActivity": "Aktivität hinzufügen",
|
||||
"bookAccident": "Unfall buchen",
|
||||
"activity": "Aktivität",
|
||||
@@ -433,12 +445,21 @@
|
||||
"all": "Alle",
|
||||
"selectGroup": "Gruppe auswählen...",
|
||||
"activityPlaceholder": "Aktivität",
|
||||
"assignShort": "Zuordnen",
|
||||
"showImage": "Bild/Zeichnung anzeigen",
|
||||
"participants": "Teilnehmer",
|
||||
"searchParticipants": "Teilnehmer suchen",
|
||||
"filterAll": "Alle",
|
||||
"filterPresent": "Anwesend",
|
||||
"filterAbsent": "Abwesend",
|
||||
"filterTest": "Probe",
|
||||
"quickAdd": "+ Schnell hinzufügen",
|
||||
"selectTags": "Tags auswählen",
|
||||
"createDrawing": "Übungszeichnung erstellen",
|
||||
"overallActivity": "Gesamt-Aktivität",
|
||||
"editActivity": "Aktivität bearbeiten",
|
||||
"editGroupActivity": "Gruppen-Aktivität bearbeiten",
|
||||
"assignParticipantsForGroupActivity": "Teilnehmer für Gruppen-Aktivität zuordnen",
|
||||
"delete": "Löschen",
|
||||
"min": "Min",
|
||||
"errorLoadingPredefinedActivities": "Fehler beim Laden der vordefinierten Aktivitäten",
|
||||
@@ -459,11 +480,18 @@
|
||||
"errorDeletingGroup": "Fehler beim Löschen der Gruppe",
|
||||
"errorCreatingActivity": "Fehler beim Erstellen der Aktivität",
|
||||
"trainingPlanAsPDF": "Trainingsplan als PDF",
|
||||
"trainingPlanPdfShort": "Ablaufplan als PDF",
|
||||
"trainingDayAsPDF": "Trainingstag als PDF herunterladen",
|
||||
"trainingDayAsPDFShort": "Trainingstag als PDF",
|
||||
"trainingDaySummaryPdfShort": "Teilnehmerübersicht als PDF",
|
||||
"minutes": "Minuten",
|
||||
"formHandedOver": "Mitgliedsformular ausgehändigt",
|
||||
"errorOccurred": "Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.",
|
||||
"trainingTimesUpdated": "Trainingszeiten erfolgreich aktualisiert.",
|
||||
"noActiveTrainingDay": "Kein Trainingstag ausgewählt.",
|
||||
"statusReady": "Zeiten, Teilnehmer und Trainingsplan sind gepflegt.",
|
||||
"statusEmpty": "Dieser Trainingstag ist noch leer.",
|
||||
"statusInProgress": "Dieser Trainingstag ist teilweise vorbereitet.",
|
||||
"formMarkedAsHandedOver": "Mitgliedsformular als ausgehändigt markiert",
|
||||
"errorMarkingForm": "Fehler beim Markieren des Mitgliedsformulars",
|
||||
"dateNoLongerCurrent": "Ausgewähltes Datum war nicht mehr aktuell. Bitte erneut versuchen.",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user