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
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 52s
This commit is contained in:
@@ -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;
|
||||
|
||||
11
backend/migrations/20260724_align_calendar_events_schema.sql
Normal file
11
backend/migrations/20260724_align_calendar_events_schema.sql
Normal 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`;
|
||||
@@ -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`
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user