From e38f2c2e1937e3b3738c9d7a7869ddd590200a03 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Wed, 22 Jul 2026 16:07:31 +0200 Subject: [PATCH] feat: add archived documents section and filtering options to club archive view --- backend/services/clubArchiveService.js | 21 +++++++++- frontend/src/views/ClubArchiveView.vue | 54 ++++++++++++++++++++------ frontend/src/views/ClubHistoryView.vue | 24 ++++++++++-- 3 files changed, 83 insertions(+), 16 deletions(-) diff --git a/backend/services/clubArchiveService.js b/backend/services/clubArchiveService.js index adb97538..125621e1 100755 --- a/backend/services/clubArchiveService.js +++ b/backend/services/clubArchiveService.js @@ -1,6 +1,6 @@ import { Op } from 'sequelize'; import sequelize from '../database.js'; -import { ClubPaymentClaim, ClubRequest, ClubTask, Member } from '../models/index.js'; +import { ClubDocument, ClubPaymentClaim, ClubRequest, ClubTask, Member } from '../models/index.js'; import { hasClubPaymentClaimPaidAmountCentsColumn } from './clubPaymentClaimCompatibility.js'; const DEFAULT_LIMIT = 50; @@ -90,6 +90,16 @@ class ClubArchiveService { ) : []; + const archivedDocuments = await loadOptionalTableData( + availableTables, + 'club_documents', + () => ClubDocument.findAll({ + where: { clubId, status: { [Op.in]: ['archived', 'obsolete'] } }, + order: [['archivedAt', 'DESC'], ['updatedAt', 'DESC']], + limit: DEFAULT_LIMIT, + }) + ); + const inactiveMembers = members .filter((member) => !member.active) .slice(0, DEFAULT_LIMIT) @@ -110,6 +120,7 @@ class ClubArchiveService { archivedRequests: archivedRequests.length, archivedTasks: archivedTasks.length, archivedClaims: archivedClaims.length, + archivedDocuments: archivedDocuments.length, }, inactiveMembers, archivedRequests: archivedRequests.map((entry) => ({ @@ -148,6 +159,14 @@ class ClubArchiveService { updatedAt: entry.updatedAt || null, }; }), + archivedDocuments: archivedDocuments.map((entry) => ({ + id: entry.id, + title: entry.title || 'Dokument', + documentType: entry.documentType || 'other', + status: entry.status, + archivedAt: entry.archivedAt || null, + updatedAt: entry.updatedAt || null, + })), notes: [ 'Das Vereinsarchiv zeigt inaktive Mitglieder sowie archivierte oder abgeschlossene Vereinsvorgänge.', 'Ein zentrales Dokumentenarchiv wird ergänzt, sobald Dokumente und Rechnungen produktiv archiviert werden.', diff --git a/frontend/src/views/ClubArchiveView.vue b/frontend/src/views/ClubArchiveView.vue index 61450cb5..a31a6737 100755 --- a/frontend/src/views/ClubArchiveView.vue +++ b/frontend/src/views/ClubArchiveView.vue @@ -38,6 +38,15 @@ Historische Forderungen {{ archive.summary?.archivedClaims ?? 0 }} +
+ Archivierte Dokumente + {{ archive.summary?.archivedDocuments ?? 0 }} +
+ + +
+ +
@@ -47,7 +56,7 @@
-
+

Ehemalige Mitglieder

@@ -58,7 +67,7 @@
-

Keine inaktiven Mitglieder im Archiv.

+

Keine passenden inaktiven Mitglieder im Archiv.

@@ -71,7 +80,7 @@ - + @@ -85,7 +94,7 @@ -
+

Archivierte Anfragen

@@ -96,7 +105,7 @@
-

Keine archivierten Anfragen vorhanden.

+

Keine passenden archivierten Anfragen vorhanden.

{{ member.displayName }} {{ member.email || '–' }} {{ member.city || '–' }}
@@ -109,7 +118,7 @@ - + @@ -123,7 +132,7 @@ -
+

Archivierte Aufgaben

@@ -134,7 +143,7 @@
-

Keine archivierten Aufgaben vorhanden.

+

Keine passenden archivierten Aufgaben vorhanden.

{{ displayRequestType(request.requestType) }} {{ request.subject || '–' }} {{ request.personName || request.email || '–' }}
@@ -147,7 +156,7 @@ - + @@ -161,7 +170,7 @@ -
+

Historische Forderungen

@@ -169,7 +178,7 @@
-

Keine historischen Forderungen vorhanden.

+

Keine passenden historischen Forderungen vorhanden.

{{ task.title }} {{ task.taskType || '–' }} {{ displayPriority(task.priority) }}
@@ -182,7 +191,7 @@ - + @@ -193,6 +202,12 @@
{{ claim.memberName || '–' }} {{ claim.claimType || '–' }} {{ displayClaimStatus(claim.status) }}
+ +
+

Archivierte Dokumente

Abgelegte oder ersetzte Vereinsdokumente.

+

Keine passenden archivierten Dokumente vorhanden.

+
TitelTypStatusZuletzt geändert
{{ document.title }}{{ document.documentType }}{{ document.status }}{{ formatDateTime(document.archivedAt || document.updatedAt) }}
+
@@ -209,11 +224,13 @@ function createEmptyArchive() { archivedRequests: 0, archivedTasks: 0, archivedClaims: 0, + archivedDocuments: 0, }, inactiveMembers: [], archivedRequests: [], archivedTasks: [], archivedClaims: [], + archivedDocuments: [], notes: [], }; } @@ -225,10 +242,22 @@ export default { loading: false, loadError: '', archive: createEmptyArchive(), + filters: { entity: '', search: '' }, }; }, computed: { ...mapGetters(['currentClub']), + filteredArchive() { + const needle = this.filters.search.toLowerCase(); + const filter = (entries, fields) => entries.filter((entry) => !needle || fields.map((field) => entry[field]).join(' ').toLowerCase().includes(needle)); + return { + inactiveMembers: filter(this.archive.inactiveMembers, ['displayName', 'email', 'city']), + archivedRequests: filter(this.archive.archivedRequests, ['subject', 'personName', 'email', 'requestType', 'status']), + archivedTasks: filter(this.archive.archivedTasks, ['title', 'taskType', 'priority', 'status']), + archivedClaims: filter(this.archive.archivedClaims, ['memberName', 'claimType', 'status']), + archivedDocuments: filter(this.archive.archivedDocuments || [], ['title', 'documentType', 'status']), + }; + }, }, watch: { currentClub: { @@ -243,6 +272,7 @@ export default { }, }, methods: { + showEntity(entity) { return !this.filters.entity || this.filters.entity === entity; }, async loadArchive() { if (!this.currentClub) return; this.loading = true; diff --git a/frontend/src/views/ClubHistoryView.vue b/frontend/src/views/ClubHistoryView.vue index aa6245ec..3f3eb50e 100755 --- a/frontend/src/views/ClubHistoryView.vue +++ b/frontend/src/views/ClubHistoryView.vue @@ -8,9 +8,10 @@ Letzte Vorgänge aus Anfragen und Aufgaben für den Vereinsalltag in einer gemeinsamen Übersicht.

- +
+ + +
@@ -61,6 +62,8 @@ Suche + +
@@ -208,6 +211,8 @@ export default { filters: { source: '', search: '', + from: '', + to: '', }, }; }, @@ -381,6 +386,9 @@ export default { if (this.filters.source && entry.source !== this.filters.source) { return false; } + const occurredAt = new Date(entry.occurredAt); + if (this.filters.from && occurredAt < new Date(`${this.filters.from}T00:00:00`)) return false; + if (this.filters.to && occurredAt > new Date(`${this.filters.to}T23:59:59`)) return false; if (!search) { return true; } @@ -482,6 +490,16 @@ export default { } this.$router.push(entry.to); }, + exportHistory() { + const rows = [['Zeitpunkt', 'Quelle', 'Titel', 'Beschreibung', 'Person', 'Status'], ...this.filteredEntries.map((entry) => [entry.occurredAt, entry.sourceLabel, entry.title, entry.description, entry.personLabel, entry.statusLabel])]; + const csv = rows.map((row) => row.map((value) => `"${String(value || '').replace(/"/g, '""')}"`).join(';')).join('\n'); + const url = URL.createObjectURL(new Blob([`\uFEFF${csv}`], { type: 'text/csv;charset=utf-8' })); + const link = document.createElement('a'); + link.href = url; + link.download = `vereinshistorie-${new Date().toISOString().slice(0, 10)}.csv`; + link.click(); + URL.revokeObjectURL(url); + }, formatDateTime(value) { if (!value) return '–'; const date = new Date(value);