feat(tickets): Wiederöffnen→Rückfrage mit Zeitstempel + Soft-Delete-Papierkorb + Scroll-Erhalt

Wiederöffnen eines gelösten Tickets:
- landet jetzt in „Rückfragen" (NeedsInfo) statt „Offen".
- War es ein echt gelöstes Ticket OHNE Rückfrage, wird um mehr Infos gebeten und
  „Wieder geöffnet am" (ReopenedAt) gestempelt.
- Hatte es bereits eine offene Rückfrage (z. B. manuell gelöste Rückfrage), kehrt es
  ohne neuen Hinweis und ohne Zeitstempel in genau diese Frage zurück.

Löschen über die UI = Soft-Delete:
- Tickets wandern in die neue Kategorie „Gelöscht" (Papierkorb) statt entfernt zu werden.
- Von dort wiederherstellbar (POST /feedback/{id}/restore → DeletedAt = null).
- Countdown „Wird in N Tagen endgültig gelöscht"; nach 30 Tagen endgültige Löschung
  (lazy beim Abruf in GET /feedback).

Außerdem: Scrollposition bleibt beim Zurück-Navigieren (z. B. nach Klick auf einen
Tier-Link) erhalten, statt an den Seitenanfang zu springen.

EF-Migration FeedbackReopenedAndSoftDelete (ReopenedAt, DeletedAt — beide nullable).
Tests: 260 Backend grün (+2 Reopen-Fälle, CRUD auf Soft-Delete umgestellt), e2e Tickets
Desktop+Phone grün (+Soft-Delete/Restore/Reopen), vitest 149.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 11:02:04 +02:00
parent a0fa33da42
commit c0a7b1a5ee
13 changed files with 2237 additions and 43 deletions

View File

@@ -1221,20 +1221,31 @@ export const de = {
entityLabel: 'Datensatz',
createdLabel: 'Gesendet am',
resolvedLabel: 'Gelöst am',
reopenedLabel: 'Wieder geöffnet am',
markResolved: 'Als gelöst markieren',
reopen: 'Wieder öffnen',
reopenRequest:
'Du hast dieses Ticket wieder geöffnet. Bitte beschreibe kurz, was noch fehlt oder nicht stimmt — dann schaue ich es mir erneut an.',
edit: 'Bearbeiten',
save: 'Speichern',
cancel: 'Abbrechen',
saving: 'Wird gespeichert …',
delete: 'Löschen',
confirmDelete: 'Diesen Fehlerbericht wirklich löschen?',
confirmDelete: 'Dieses Ticket in den Papierkorb verschieben? Du kannst es unter „Gelöscht" wiederherstellen.',
editTitle: 'Fehlerbericht bearbeiten',
editLabel: 'Beschreibung',
saved: 'Änderung gespeichert.',
resolvedToast: 'Ticket als gelöst markiert.',
reopenedToast: 'Ticket wieder geöffnet.',
deletedToast: 'Ticket gelöscht.',
deletedToast: 'Ticket in den Papierkorb verschoben.',
restore: 'Wiederherstellen',
restoredToast: 'Ticket wiederhergestellt.',
restoreError: 'Ticket konnte nicht wiederhergestellt werden.',
/** Countdown im Papierkorb bis zur endgültigen Löschung. {n} = verbleibende Tage. */
trashCountdownDays: 'Wird in {n} Tagen endgültig gelöscht.',
trashCountdownTomorrow: 'Wird morgen endgültig gelöscht.',
trashCountdownToday: 'Wird heute endgültig gelöscht.',
deletedLabel: 'Gelöscht am',
updateError: 'Ticket konnte nicht aktualisiert werden.',
deleteError: 'Ticket konnte nicht gelöscht werden.',
emptyMessage: 'Bitte beschreibe den Fehler.',
@@ -1257,11 +1268,14 @@ export const de = {
open: 'Offen',
/** Gruppe „Geschlossen": erledigte Tickets mit Changelog. */
closed: 'Geschlossen',
/** Gruppe „Gelöscht": in den Papierkorb verschobene Tickets (wiederherstellbar). */
deleted: 'Gelöscht',
},
/** Leertext je gefilterter Ansicht. */
emptyDialog: 'Aktuell keine Tickets im Dialog.',
emptyOpen: 'Keine offenen Tickets.',
emptyClosed: 'Noch keine geschlossenen Tickets.',
emptyDeleted: 'Der Papierkorb ist leer.',
/** Changelog (fixNote) auf geschlossenen Tickets. */
changelogLabel: 'Was wurde geändert',
/** Überschrift des Frage/Antwort-Verlaufs (frühere Runden). */