/** * INBOX-1: Anfrage-Detail — Nachricht lesen, Triage (Status / Verwerfen / * Abnehmer zuordnen), KI-Antwortentwurf (INBOX-2) und Versand (INBOX-3). * * - Triage-PUT überschreibt assignedContactId immer → bei jedem Update wird * die aktuelle Zuordnung mitgesendet (Backend-Vertrag, siehe api/requests.ts). * - KI entwirft NUR (human-in-the-loop): Entwurf landet editierbar im * Textfeld; gesendet wird ausschließlich nach Bestätigung. * - 503 AiKeyMissing / MailNotConfigured / MailAuthFailed → deutsche Hinweise * (gleiches Muster wie die Verkaufstext-KI). */ import { useState } from 'react' import { Link, useParams } from 'react-router-dom' import { de } from '../strings/de' import { errorCode } from '../api/client' import { REQUEST_STATUSES, draftReply, getRequest, problemTitle, sendReply, triageRequest, type InboxRequest, type RequestStatus, } from '../api/requests' import { listContactsPaged } from '../api/contacts' import { useApi, useMutation } from '../hooks/useApi' import { StatusBadge } from './AnfragenPage' import { formatDateTime } from '../format/labels' import './anfragen.css' export default function AnfrageDetailPage() { const t = de.pages.anfragen const td = t.detail const { id = '' } = useParams() const request = useApi(() => getRequest(id), [id]) const contacts = useApi(() => listContactsPaged({ page: 1, pageSize: 1000, orderBy: 'name' }), []) // Antwort-Text: lokaler Entwurf; aus draftReply vorbefüllt, sobald geladen. const [reply, setReply] = useState('') const [replyInitFor, setReplyInitFor] = useState(null) if (request.data && replyInitFor !== request.data.id) { setReplyInitFor(request.data.id) setReply(request.data.draftReply ?? '') } const [notice, setNotice] = useState(null) const [hint, setHint] = useState(null) /** Aktuellen Server-Stand nach einer Mutation übernehmen (ohne Neu-Laden). */ const [override, setOverride] = useState(null) const current = override && override.id === id ? override : request.data const triage = useMutation((next: { status?: RequestStatus; assignedContactId: string | null }) => triageRequest(id, next), ) async function applyTriage(next: { status?: RequestStatus; assignedContactId: string | null }) { setNotice(null) const result = await triage.run(next) if (result.ok && current) { setOverride({ ...current, status: next.status ?? current.status, assignedContactId: next.assignedContactId, }) } } const draft = useMutation(() => draftReply(id)) async function onDraft() { setHint(null) setNotice(null) const result = await draft.run() if (result.ok) { setOverride(result.value) setReply(result.value.draftReply ?? '') return } const code = errorCode(result.cause) if (code === 'AiKeyMissing') setHint(td.aiKeyMissing) else if (code === 'AiUpstreamError') setHint(td.aiUpstreamError) // sonst: draft.error treibt den Alert } const send = useMutation(() => sendReply(id, reply)) async function onSend() { setHint(null) setNotice(null) if (reply.trim() === '') { setHint(td.sendEmptyBody) return } if (!window.confirm(td.sendConfirm)) return const result = await send.run() if (result.ok) { setOverride(result.value) setNotice(td.sent) return } const title = problemTitle(result.cause) if (title === 'MailNotConfigured') setHint(t.mailNotConfigured) else if (title === 'MailAuthFailed') setHint(t.mailAuthFailed) } if (request.loading) return

{de.common.loading}

if (request.error || !current) { return (

{request.error ?? td.notFound}

{td.back}
) } const r = current const contactItems = contacts.data?.items ?? [] const mutationError = triage.error ?? draft.error ?? send.error return (

{r.subject ?? td.noSubject}

{td.back}
{td.from}
{r.fromName ? `${r.fromName} <${r.fromAddress}>` : r.fromAddress}
{td.receivedAt}
{formatDateTime(r.receivedAt)}
{r.answeredAt && (
{td.answeredAt}
{formatDateTime(r.answeredAt)}
)}

{td.message}

{r.bodyText?.trim() ? r.bodyText : td.noBody}

{td.triage}

{mutationError &&
{mutationError}
}
{r.status !== 'Abandoned' && ( )}

{td.reply}

{notice &&
{notice}
} {hint &&
{hint}
}