feat: Update member table schema to link users and enhance calendar events structure
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 52s

This commit is contained in:
Torsten Schulz (local)
2026-07-24 14:47:21 +02:00
parent 01ca924e3e
commit ee526dc3f2
6 changed files with 65 additions and 29 deletions

View File

@@ -1,4 +1,4 @@
ALTER TABLE member
ADD COLUMN user_id INT NULL,
ADD UNIQUE KEY member_user_id_unique (user_id),
ADD CONSTRAINT member_user_id_fk FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE SET NULL;
ALTER TABLE `member`
ADD COLUMN `user_id` INT NULL,
ADD UNIQUE KEY `member_user_id_unique` (`user_id`),
ADD CONSTRAINT `member_user_id_fk` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE SET NULL;

View File

@@ -0,0 +1,11 @@
-- Ergänzt eine bereits vorhandene ältere calendar_events-Tabelle um die
-- Felder des aktuellen Kalender-Modells. Bestehende Termine bleiben erhalten.
ALTER TABLE `calendar_events`
ADD COLUMN IF NOT EXISTS `event_type` VARCHAR(32) NOT NULL DEFAULT 'club_event' AFTER `club_id`,
ADD COLUMN IF NOT EXISTS `status` VARCHAR(32) NOT NULL DEFAULT 'planning' AFTER `event_type`,
ADD COLUMN IF NOT EXISTS `description` TEXT NULL AFTER `title`,
ADD COLUMN IF NOT EXISTS `location` VARCHAR(255) NULL AFTER `description`,
ADD COLUMN IF NOT EXISTS `registration_deadline` DATE NULL AFTER `end_date`,
ADD COLUMN IF NOT EXISTS `organizer_user_id` INT NULL AFTER `registration_deadline`,
ADD COLUMN IF NOT EXISTS `category` VARCHAR(64) NULL AFTER `organizer_user_id`,
ADD COLUMN IF NOT EXISTS `notes` TEXT NULL AFTER `category`;

View File

@@ -24,10 +24,10 @@ Sichere optionale Zuordnung eines Login-Kontos zu einem Vereinsmitglied für
persönliche Mitgliederfunktionen:
```sql
ALTER TABLE member
ADD COLUMN user_id INT NULL,
ADD UNIQUE KEY member_user_id_unique (user_id),
ADD CONSTRAINT member_user_id_fk FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE SET NULL;
ALTER TABLE `member`
ADD COLUMN `user_id` INT NULL,
ADD UNIQUE KEY `member_user_id_unique` (`user_id`),
ADD CONSTRAINT `member_user_id_fk` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE SET NULL;
```
Bestehende Mitglieder bleiben zunächst unverknüpft. Die Zuordnung erfolgt
@@ -98,6 +98,24 @@ ALTER TABLE club_communication_recipients
ADD INDEX club_communication_recipients_member_read (club_id, member_id, read_at);
```
### Bestehende `calendar_events`-Tabelle an das aktuelle Modell anpassen
Wenn die Tabelle vor der Kalendererweiterung angelegt wurde, fehlen die neuen
Metadaten-Spalten. Die Migration ist additiv und lässt bestehende Termine
unverändert:
```sql
ALTER TABLE `calendar_events`
ADD COLUMN IF NOT EXISTS `event_type` VARCHAR(32) NOT NULL DEFAULT 'club_event' AFTER `club_id`,
ADD COLUMN IF NOT EXISTS `status` VARCHAR(32) NOT NULL DEFAULT 'planning' AFTER `event_type`,
ADD COLUMN IF NOT EXISTS `description` TEXT NULL AFTER `title`,
ADD COLUMN IF NOT EXISTS `location` VARCHAR(255) NULL AFTER `description`,
ADD COLUMN IF NOT EXISTS `registration_deadline` DATE NULL AFTER `end_date`,
ADD COLUMN IF NOT EXISTS `organizer_user_id` INT NULL AFTER `registration_deadline`,
ADD COLUMN IF NOT EXISTS `category` VARCHAR(64) NULL AFTER `organizer_user_id`,
ADD COLUMN IF NOT EXISTS `notes` TEXT NULL AFTER `category`;
```
## 2026-03-17
### `predefined_activities.exclude_from_stats`

View File

@@ -41,15 +41,15 @@
</div>
<div class="schedule-summary-item">
<span class="schedule-summary-label">{{ $t('schedule.matches') }}</span>
<strong>{{ matchesCount }}</strong>
<strong>{{ scheduleSummary.total }}</strong>
</div>
<div class="schedule-summary-item">
<span class="schedule-summary-label">{{ $t('schedule.completedShort') }}</span>
<strong>{{ completedMatchesCount }}</strong>
<strong>{{ scheduleSummary.completed }}</strong>
</div>
<div class="schedule-summary-item">
<span class="schedule-summary-label">{{ $t('schedule.pendingShort') }}</span>
<strong>{{ pendingMatchesCount }}</strong>
<strong>{{ scheduleSummary.pending }}</strong>
</div>
<div class="schedule-summary-item" v-if="nextScheduledMatchLabel">
<span class="schedule-summary-label">{{ $t('schedule.nextMatch') }}</span>
@@ -144,7 +144,7 @@
<div v-if="selectedLeague && selectedLeague !== '' && showTableTab" class="tab-navigation">
<button :class="['tab-button', { active: activeTab === 'schedule' }]" @click="$emit('update:active-tab', 'schedule')">
📅 {{ $t('schedule.scheduleTab') }} <span class="tab-count">{{ matchesCount }}</span>
📅 {{ $t('schedule.scheduleTab') }} <span class="tab-count">{{ scheduleSummary.total }}</span>
</button>
<button :class="['tab-button', { active: activeTab === 'table' }]" @click="$emit('update:active-tab', 'table')">
📊 {{ $t('schedule.tableTab') }} <span class="tab-count">{{ tableCount }}</span>
@@ -186,9 +186,13 @@ export default {
galleryLoading: { type: Boolean, required: true },
filteredScheduleTeamsCount: { type: Number, required: true },
teamsCount: { type: Number, required: true },
matchesCount: { type: Number, required: true },
completedMatchesCount: { type: Number, required: true },
pendingMatchesCount: { type: Number, required: true },
scheduleSummary: {
type: Object,
required: true,
validator(value) {
return Number.isFinite(value?.total) && Number.isFinite(value?.completed) && Number.isFinite(value?.pending);
},
},
nextScheduledMatchLabel: { type: String, default: '' },
teamSearchQuery: { type: String, required: true },
overallScheduleLabel: { type: String, required: true },

View File

@@ -317,7 +317,7 @@ export default {
};
},
computed: {
...mapGetters(['currentClub', 'currentClubName']),
...mapGetters(['currentClub']),
weekdays() {
return WEEKDAY_KEYS.map(key => this.$t(`calendar.weekdays.${key}`));
},
@@ -418,10 +418,6 @@ export default {
}
},
methods: {
isOurTeam(teamName) {
if (!teamName || !this.currentClubName) return false;
return String(teamName).startsWith(this.currentClubName);
},
async loadCalendarEvents() {
if (!this.currentClub) {
this.events = [];
@@ -775,9 +771,11 @@ export default {
async loadMatchEvents() {
const response = await apiClient.get(`/matches/leagues/${this.currentClub}/matches`);
this.ensureSuccess(response, this.$t('calendar.sources.matches'));
return (response.data || [])
.filter(match => this.isOurTeam(match.homeTeam?.name) || this.isOurTeam(match.guestTeam?.name))
.map(match => {
// Der Endpunkt liefert bereits ausschließlich Spiele der eigenen
// Vereinsmannschaften. Ein zusätzlicher Client-Filter anhand des
// sichtbaren Vereinsnamens ist fehleranfällig (z. B. "TSV Bonames"
// gegenüber "TSV 1875 Bonames") und hat Punktspiele ausgeblendet.
return (response.data || []).map(match => {
const date = this.parseDate(match.date);
const home = match.homeTeam?.name || this.$t('calendar.match.home');
const guest = match.guestTeam?.name || this.$t('calendar.match.guest');

View File

@@ -9,9 +9,7 @@
:gallery-loading="galleryLoading"
:filtered-schedule-teams-count="filteredScheduleTeams.length"
:teams-count="teams.length"
:matches-count="matches.length"
:completed-matches-count="completedMatchesCount"
:pending-matches-count="pendingMatchesCount"
:schedule-summary="scheduleSummary"
:next-scheduled-match-label="nextScheduledMatchLabel"
:team-search-query="teamSearchQuery"
:overall-schedule-label="$t('schedule.overallSchedule')"
@@ -711,6 +709,13 @@ export default {
pendingMatchesCount() {
return this.matches.length - this.completedMatchesCount;
},
scheduleSummary() {
return {
total: this.matches.length,
completed: this.completedMatchesCount,
pending: this.pendingMatchesCount,
};
},
friendlyResultScore() {
return this.calculateFriendlyResultScore(this.friendlyResultDialog.rows);
},
@@ -755,9 +760,9 @@ export default {
: this.friendlyOnly
? `Interne Freundschaftsspiele: ${this.matches.length}`
: this.$t('schedule.workspaceScheduleDescription', {
matches: this.matches.length,
completed: this.completedMatchesCount,
pending: this.pendingMatchesCount
matches: this.scheduleSummary.total,
completed: this.scheduleSummary.completed,
pending: this.scheduleSummary.pending
});
},
leagueTeamOptions() {