feat(tickets): Rückfragen-Dialog, Verlinkungen, Entwurfssicherung + PWA-Vorbereitung

- Feedback: fixNote (Changelog), agentContext (intern), thread (Q&A-Verlauf);
  3 gefilterte Ansichten (Rückfragen/Offen/Geschlossen)
- Tickets-Text: klickbare Tier-Links (ID→Name), Ticket→Ticket-Links (?focus=),
  Markdown-Links; Antwort-Entwurf pro Ticket in localStorage
- "Fehler melden": Entwurf übersteht App-Wechsel/Schließen (ein gemeinsamer Entwurf)
- entityName als Hyperlink zum referenzierten Datensatz

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 22:38:23 +02:00
parent b9021fe577
commit dcf0e40f9b
18 changed files with 2592 additions and 154 deletions

View File

@@ -44,10 +44,32 @@ function ReportErrorDialogBody({
}) {
const t = de.feedback
const toast = useToast()
const [message, setMessage] = useState('')
// EIN gemeinsamer Entwurf fürs Melde-Fenster (nicht pro Kontext): so kommt der Text auch
// dann zurück, wenn das Fenster (versehentlich) geschlossen wird, man in der App woanders
// hin navigiert (anderer Kontext) und „Fehler melden" erneut öffnet — oder die Seite am
// Handy beim App-Wechsel aus dem Speicher geworfen wird. Wird bei jeder Eingabe gesichert
// und erst nach erfolgreichem Senden gelöscht.
const draftKey = 'report-error-draft'
const [message, setMessage] = useState(() => {
try {
return localStorage.getItem(draftKey) ?? ''
} catch {
return ''
}
})
const [submitting, setSubmitting] = useState(false)
const textareaRef = useRef<HTMLTextAreaElement | null>(null)
// Entwurf laufend sichern (bei jeder Eingabe) bzw. entfernen, wenn leer.
useEffect(() => {
try {
if (message) localStorage.setItem(draftKey, message)
else localStorage.removeItem(draftKey)
} catch {
/* localStorage nicht verfügbar — Entwurf dann nur im Speicher */
}
}, [message, draftKey])
// Focus the textarea once on mount.
useEffect(() => {
const id = window.setTimeout(() => textareaRef.current?.focus(), 0)
@@ -84,6 +106,11 @@ function ReportErrorDialogBody({
url: window.location.href,
clientTimestamp: new Date().toISOString(),
})
try {
localStorage.removeItem(draftKey)
} catch {
/* ignorieren */
}
toast.success(t.success)
onClose()
} catch (err) {