feat: implement load request ID for improved data loading consistency across views
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:
@@ -11,11 +11,17 @@ Die vorhandenen Club-Module sollen nicht mehr nur "vorhanden", sondern im Alltag
|
||||
|
||||
## Aktueller Fokus
|
||||
|
||||
- Kommunikation
|
||||
- Historie
|
||||
- Archiv
|
||||
- Restliche Club-UI-Absicherung in den Kernviews
|
||||
- Danach erst das `mein-tt.de`-Produkt inhaltlich weiter ausbauen
|
||||
Die Reihenfolge folgt Abhaengigkeiten und Betriebsrisiko: Erst muessen die
|
||||
vorhandenen Kernarbeitsbereiche bei Clubwechsel, Rechten und Fehlern stabil
|
||||
sein. Danach wird der reale Kommunikationsversand abgesichert. Erst auf dieser
|
||||
Basis lohnt sich der Ausbau von Nachvollziehbarkeit (Historie und Archiv) und
|
||||
der letzte UX-Feinschliff.
|
||||
|
||||
1. Club-UI-Absicherung in den Kernviews
|
||||
2. Kommunikation produktiv absichern
|
||||
3. Historie und Archiv vertiefen
|
||||
4. Restliche Club-UX und Dashboard verdichten
|
||||
5. Erst danach das `mein-tt.de`-Produkt inhaltlich ausbauen
|
||||
|
||||
## Arbeitsreihenfolge
|
||||
|
||||
@@ -48,47 +54,66 @@ Fertig, wenn:
|
||||
- Beim Clubwechsel keine Formularwerte aus dem vorherigen Club sichtbar bleiben.
|
||||
- Read-only-Nutzer die Bereiche verstehen, ohne in kaputte Aktionen zu laufen.
|
||||
|
||||
Abnahmefolge je View:
|
||||
1. Mit Schreibrechten Liste laden, Datensatz oeffnen, bearbeiten, Auswahl loeschen und neu anlegen.
|
||||
2. Waerend ein Detail offen ist den Club wechseln und pruefen, dass keine alten Werte bleiben.
|
||||
3. Mit Leserechten laden und pruefen, dass Orientierung, Navigation und Hinweise funktionieren.
|
||||
4. Leere Liste, Reload und einen API-Fehler pruefen.
|
||||
|
||||
Prioritaet innerhalb der Phase:
|
||||
1. `ClubTasksView.vue` und `ClubCommunicationView.vue` (taegliche, zustandsreiche Arbeitsbereiche).
|
||||
2. `ClubAccountsView.vue` und `ClubInvoicesView.vue` (finanzielle Folgerisiken).
|
||||
|
||||
### Phase 2: Kommunikation produktiv absichern
|
||||
|
||||
Ziel:
|
||||
- Nachrichtenfluss nicht nur funktional, sondern praxisnah robust machen.
|
||||
|
||||
Arbeitspakete:
|
||||
- SMTP real testen, inklusive Zustellprotokoll und Fehlerfaelle.
|
||||
- Optional Reply-To pro Verein oder Kommunikationsvorlage sauber ergaenzen.
|
||||
- Dokumentanhaenge fuer Nachrichten und Vorlagen einfuehren.
|
||||
- Serienvorlagen und wiederkehrende Nachrichtentypen vorbereiten.
|
||||
1. SMTP real testen, inklusive Zustellprotokoll, Konfigurationsfehlern und Fehlerfaellen.
|
||||
2. Ergebnis und notwendige Betriebsparameter pro Verein dokumentieren.
|
||||
3. Optional Reply-To pro Verein oder Kommunikationsvorlage sauber ergaenzen.
|
||||
4. Dokumentanhaenge fuer Nachrichten und Vorlagen einfuehren.
|
||||
5. Serienvorlagen und wiederkehrende Nachrichtentypen vorbereiten.
|
||||
|
||||
Fertig, wenn:
|
||||
- Eine Testzustellung je Verein reproduzierbar gelingt.
|
||||
- Fehlende SMTP-Konfiguration klar und frueh sichtbar wird.
|
||||
- Nachrichten mit Anhaengen und Vorlagen ohne Sonderlogik im Alltag einsetzbar sind.
|
||||
|
||||
Hinweis zur Abhaengigkeit:
|
||||
- Anhaenge und Serienvorlagen erst nach erfolgreichem SMTP-Smoke-Test umsetzen.
|
||||
- Der SMTP-Test braucht eine reale, freigegebene Versandkonfiguration und mindestens eine Testadresse.
|
||||
|
||||
### Phase 3: Historie und Archiv vertiefen
|
||||
|
||||
Ziel:
|
||||
- Vergaengliche Vorgange muessen spaeter besser auffindbar und nachvollziehbar sein.
|
||||
|
||||
Arbeitspakete:
|
||||
- Historie nach Modulen und Vorgangstypen filtern.
|
||||
- Historie exportierbar machen.
|
||||
- Historie mit Zielobjekten und Querverweisen versehen.
|
||||
- Archiv um weitere Entitaeten erweitern.
|
||||
- Archivsuche und Schnellfilter verbessern.
|
||||
1. Historie nach Modulen und Vorgangstypen filtern.
|
||||
2. Historie mit Zielobjekten und Querverweisen versehen.
|
||||
3. Archivsuche und Schnellfilter verbessern.
|
||||
4. Archiv um weitere Entitaeten erweitern.
|
||||
5. Historie exportierbar machen.
|
||||
|
||||
Fertig, wenn:
|
||||
- Vorstand oder Verwaltung einen Vorgang aus Historie oder Archiv ohne Umweg wiederfinden kann.
|
||||
- Wichtige Clubobjekte nicht nur archiviert, sondern auch wieder auffindbar und verlinkt sind.
|
||||
|
||||
Hinweis zur Abhaengigkeit:
|
||||
- Export erst nach Festlegung von Filtern, Zielobjekten und darzustellenden Feldern bauen; sonst wird ein instabiles Datenformat festgeschrieben.
|
||||
|
||||
### Phase 4: Restliche Club-UX verdichten
|
||||
|
||||
Ziel:
|
||||
- Das Dashboard und die Detailmodule sollen gleiche Sprache sprechen.
|
||||
|
||||
Arbeitspakete:
|
||||
- Dashboard-Schnellzugriffe weiter auf Tagesgeschaeft trimmen.
|
||||
- Verlinkungen zwischen Dashboard, Mitgliedern, Zahlungen, Kommunikation und Archiv schaerfen.
|
||||
- Kleine Inkonsistenzen in Statusworten, Akzentfarben und Listenlabels bereinigen.
|
||||
1. Verlinkungen zwischen Dashboard, Mitgliedern, Zahlungen, Kommunikation und Archiv schaerfen.
|
||||
2. Dashboard-Schnellzugriffe mit den tatsaechlich haeufigsten Tagesaktionen abgleichen und verdichten.
|
||||
3. Kleine Inkonsistenzen in Statusworten, Akzentfarben und Listenlabels bereinigen.
|
||||
4. Einen abschliessenden bereichsuebergreifenden Smoke-Check fuer Navigation, Rechte und leere Zustaende ausfuehren.
|
||||
|
||||
Fertig, wenn:
|
||||
- Der Einstieg immer zur naechsten sinnvollen Aktion fuehrt.
|
||||
@@ -112,11 +137,26 @@ Arbeitspakete:
|
||||
|
||||
## Konkrete naechste Tickets
|
||||
|
||||
- SMTP-Test fuer Kommunikation mit realer Zieladresse und dokumentiertem Ergebnis.
|
||||
- Dokumentanhaenge fuer Kommunikation und Vorlagen.
|
||||
- Historie: Filter und Export.
|
||||
- Archiv: weitere Objektklassen und Suche.
|
||||
- Club-UI-Smoke-Check fuer Aufgaben, Kommunikation, Konten und Rechnungen.
|
||||
### Jetzt
|
||||
|
||||
1. Club-UI-Smoke-Check und Kantenfallkorrekturen fuer Aufgaben und Kommunikation.
|
||||
2. Club-UI-Smoke-Check und Kantenfallkorrekturen fuer Konten und Rechnungen.
|
||||
3. Einheitliche Read-only-, Lade- und Fehlerzustaende in den vier Kernviews.
|
||||
|
||||
### Danach
|
||||
|
||||
4. SMTP-Test fuer Kommunikation mit realer Zieladresse und dokumentiertem Ergebnis.
|
||||
5. Sichtbare Behandlung fehlender oder fehlerhafter SMTP-Konfiguration nachziehen.
|
||||
6. Reply-To je Verein/Vorlage entscheiden und bei Bedarf umsetzen.
|
||||
7. Dokumentanhaenge fuer Kommunikation und Vorlagen.
|
||||
8. Serienvorlagen und wiederkehrende Nachrichtentypen.
|
||||
|
||||
### Anschliessend
|
||||
|
||||
9. Historie: Filter nach Modul und Vorgangstyp sowie Querverweise auf Zielobjekte.
|
||||
10. Archiv: Suche, Schnellfilter und danach weitere Objektklassen.
|
||||
11. Historie: Export auf Basis der stabilen Filter- und Objektstruktur.
|
||||
12. Dashboard-Deep-Links, Schnellzugriffe und Statuskonsistenz abschliessen.
|
||||
|
||||
## Abhaengigkeiten
|
||||
|
||||
|
||||
@@ -483,6 +483,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
loadRequestId: 0,
|
||||
saving: false,
|
||||
transactionSaving: false,
|
||||
loadError: '',
|
||||
@@ -562,6 +563,7 @@ export default {
|
||||
this.clearAccountsState();
|
||||
return;
|
||||
}
|
||||
this.clearAccountsState();
|
||||
await this.loadAccounts();
|
||||
},
|
||||
},
|
||||
@@ -619,6 +621,8 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
clearAccountsState() {
|
||||
this.loadRequestId += 1;
|
||||
this.loading = false;
|
||||
this.accounts = [];
|
||||
this.transactions = [];
|
||||
this.paymentClaims = [];
|
||||
@@ -732,28 +736,34 @@ export default {
|
||||
this.selectedTransactionId = null;
|
||||
this.transactionForm = createEmptyTransactionForm(accountId);
|
||||
},
|
||||
async loadPaymentClaims() {
|
||||
if (!this.currentClub) {
|
||||
async loadPaymentClaims(clubId = this.currentClub, requestId = this.loadRequestId) {
|
||||
if (!clubId) {
|
||||
this.paymentClaims = [];
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await apiClient.get(`/club-payment-claims/${this.currentClub}`);
|
||||
const response = await apiClient.get(`/club-payment-claims/${clubId}`);
|
||||
if (requestId !== this.loadRequestId || clubId !== this.currentClub) return;
|
||||
this.paymentClaims = Array.isArray(response.data?.claims) ? response.data.claims : [];
|
||||
} catch (_error) {
|
||||
if (requestId !== this.loadRequestId || clubId !== this.currentClub) return;
|
||||
this.paymentClaims = [];
|
||||
}
|
||||
},
|
||||
async loadAccounts() {
|
||||
if (!this.currentClub) return;
|
||||
const clubId = this.currentClub;
|
||||
if (!clubId) return;
|
||||
const requestId = ++this.loadRequestId;
|
||||
this.loading = true;
|
||||
this.loadError = '';
|
||||
try {
|
||||
const response = await apiClient.get(`/club-accounts/${this.currentClub}`);
|
||||
const response = await apiClient.get(`/club-accounts/${clubId}`);
|
||||
if (requestId !== this.loadRequestId || clubId !== this.currentClub) return;
|
||||
const entries = Array.isArray(response.data?.accounts) ? response.data.accounts : [];
|
||||
this.accounts = entries.map(normalizeAccount);
|
||||
this.transactions = Array.isArray(response.data?.transactions) ? response.data.transactions.map(normalizeTransaction) : [];
|
||||
await this.loadPaymentClaims();
|
||||
await this.loadPaymentClaims(clubId, requestId);
|
||||
if (requestId !== this.loadRequestId || clubId !== this.currentClub) return;
|
||||
this.applyRouteQuery();
|
||||
if (this.selectedAccountId && !this.selectedAccount) {
|
||||
this.resetForm();
|
||||
@@ -767,9 +777,10 @@ export default {
|
||||
this.resetForm();
|
||||
}
|
||||
} catch (error) {
|
||||
if (requestId !== this.loadRequestId || clubId !== this.currentClub) return;
|
||||
this.loadError = safeErrorMessage(error, 'Konten konnten nicht geladen werden.');
|
||||
} finally {
|
||||
this.loading = false;
|
||||
if (requestId === this.loadRequestId) this.loading = false;
|
||||
}
|
||||
},
|
||||
async submitAccount() {
|
||||
|
||||
@@ -670,6 +670,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
loadRequestId: 0,
|
||||
saving: false,
|
||||
messageSaving: false,
|
||||
groupSaving: false,
|
||||
@@ -756,6 +757,7 @@ export default {
|
||||
this.clearCommunicationState();
|
||||
return;
|
||||
}
|
||||
this.clearCommunicationState();
|
||||
await this.loadCommunication();
|
||||
},
|
||||
},
|
||||
@@ -803,6 +805,8 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
clearCommunicationState() {
|
||||
this.loadRequestId += 1;
|
||||
this.loading = false;
|
||||
this.threads = [];
|
||||
this.groups = [];
|
||||
this.members = [];
|
||||
@@ -925,11 +929,14 @@ export default {
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
||||
},
|
||||
async loadCommunication() {
|
||||
if (!this.currentClub) return;
|
||||
const clubId = this.currentClub;
|
||||
if (!clubId) return;
|
||||
const requestId = ++this.loadRequestId;
|
||||
this.loading = true;
|
||||
this.loadError = '';
|
||||
try {
|
||||
const response = await apiClient.get(`/club-communication/${this.currentClub}`);
|
||||
const response = await apiClient.get(`/club-communication/${clubId}`);
|
||||
if (requestId !== this.loadRequestId || clubId !== this.currentClub) return;
|
||||
this.threads = Array.isArray(response.data?.threads) ? response.data.threads.map(normalizeThread) : [];
|
||||
this.groups = Array.isArray(response.data?.groups) ? response.data.groups.map(normalizeGroup) : [];
|
||||
this.members = Array.isArray(response.data?.members) ? response.data.members.map(normalizeMember) : [];
|
||||
@@ -955,9 +962,10 @@ export default {
|
||||
this.resetTemplateForm();
|
||||
}
|
||||
} catch (error) {
|
||||
if (requestId !== this.loadRequestId || clubId !== this.currentClub) return;
|
||||
this.loadError = safeErrorMessage(error, 'Kommunikation konnte nicht geladen werden.');
|
||||
} finally {
|
||||
this.loading = false;
|
||||
if (requestId === this.loadRequestId) this.loading = false;
|
||||
}
|
||||
},
|
||||
selectThread(thread) {
|
||||
|
||||
@@ -508,6 +508,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
loadRequestId: 0,
|
||||
saving: false,
|
||||
partySaving: false,
|
||||
loadError: '',
|
||||
@@ -619,6 +620,7 @@ export default {
|
||||
this.clearInvoiceState();
|
||||
return;
|
||||
}
|
||||
this.clearInvoiceState();
|
||||
await this.loadInvoices();
|
||||
},
|
||||
},
|
||||
@@ -682,6 +684,8 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
clearInvoiceState() {
|
||||
this.loadRequestId += 1;
|
||||
this.loading = false;
|
||||
this.invoices = [];
|
||||
this.parties = [];
|
||||
this.accounts = [];
|
||||
@@ -751,11 +755,14 @@ export default {
|
||||
}).format(amount);
|
||||
},
|
||||
async loadInvoices() {
|
||||
if (!this.currentClub) return;
|
||||
const clubId = this.currentClub;
|
||||
if (!clubId) return;
|
||||
const requestId = ++this.loadRequestId;
|
||||
this.loading = true;
|
||||
this.loadError = '';
|
||||
try {
|
||||
const response = await apiClient.get(`/club-invoices/${this.currentClub}`);
|
||||
const response = await apiClient.get(`/club-invoices/${clubId}`);
|
||||
if (requestId !== this.loadRequestId || clubId !== this.currentClub) return;
|
||||
this.invoices = Array.isArray(response.data?.invoices) ? response.data.invoices.map(normalizeInvoice) : [];
|
||||
this.parties = Array.isArray(response.data?.parties) ? response.data.parties.map(normalizeParty) : [];
|
||||
this.accounts = Array.isArray(response.data?.accounts) ? response.data.accounts : [];
|
||||
@@ -784,9 +791,10 @@ export default {
|
||||
}
|
||||
this.applyWorkflowQueryPrefill();
|
||||
} catch (error) {
|
||||
if (requestId !== this.loadRequestId || clubId !== this.currentClub) return;
|
||||
this.loadError = safeErrorMessage(error, 'Rechnungen konnten nicht geladen werden.');
|
||||
} finally {
|
||||
this.loading = false;
|
||||
if (requestId === this.loadRequestId) this.loading = false;
|
||||
}
|
||||
},
|
||||
selectInvoice(invoice) {
|
||||
|
||||
@@ -444,6 +444,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
loadRequestId: 0,
|
||||
saving: false,
|
||||
materializing: false,
|
||||
loadError: '',
|
||||
@@ -516,6 +517,7 @@ export default {
|
||||
this.clearTaskState();
|
||||
return;
|
||||
}
|
||||
this.clearTaskState();
|
||||
await this.loadTasks();
|
||||
},
|
||||
},
|
||||
@@ -544,6 +546,8 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
clearTaskState() {
|
||||
this.loadRequestId += 1;
|
||||
this.loading = false;
|
||||
this.tasks = [];
|
||||
this.taskDefinitions = [];
|
||||
this.taskSuggestions = [];
|
||||
@@ -646,11 +650,14 @@ export default {
|
||||
this.form = createEmptyTaskForm();
|
||||
},
|
||||
async loadTasks() {
|
||||
if (!this.currentClub) return;
|
||||
const clubId = this.currentClub;
|
||||
if (!clubId) return;
|
||||
const requestId = ++this.loadRequestId;
|
||||
this.loading = true;
|
||||
this.loadError = '';
|
||||
try {
|
||||
const response = await apiClient.get(`/club-tasks/${this.currentClub}`);
|
||||
const response = await apiClient.get(`/club-tasks/${clubId}`);
|
||||
if (requestId !== this.loadRequestId || clubId !== this.currentClub) return;
|
||||
const entries = Array.isArray(response.data?.tasks)
|
||||
? response.data.tasks
|
||||
: Array.isArray(response.data)
|
||||
@@ -675,9 +682,10 @@ export default {
|
||||
this.resetForm();
|
||||
}
|
||||
} catch (error) {
|
||||
if (requestId !== this.loadRequestId || clubId !== this.currentClub) return;
|
||||
this.loadError = safeErrorMessage(error, 'Aufgaben konnten nicht geladen werden.');
|
||||
} finally {
|
||||
this.loading = false;
|
||||
if (requestId === this.loadRequestId) this.loading = false;
|
||||
}
|
||||
},
|
||||
async submitTask() {
|
||||
|
||||
Reference in New Issue
Block a user