INBOX-1: Anfragen-Posteingang UI - Liste (Status-Filter, Sync-Button mit MailNotConfigured-Hinweis), Detail (Triage/Zuordnen/Verwerfen, KI-Entwurf mit AiKeyMissing-Hinweis, bestaetigter Versand) + nav + 6 e2e-Specs (Mock um /api/requests erweitert)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
87
gerbil-manager-web/src/api/requests.ts
Normal file
87
gerbil-manager-web/src/api/requests.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* INBOX-1: Anfragen-Posteingang (/api/requests, Backend INBOX-0/2/3).
|
||||
*
|
||||
* Triage-Hinweis: PUT setzt assignedContactId UNBEDINGT aus dem Input
|
||||
* (Backend überschreibt mit null, wenn das Feld fehlt) — beim reinen
|
||||
* Status-Wechsel also IMMER die aktuelle Zuordnung mitsenden.
|
||||
*/
|
||||
import { ApiError, api } from './client'
|
||||
import { toQueryString, type GridifyQuery } from './gridify'
|
||||
import type { Paged } from './types'
|
||||
|
||||
/** C#-Enum RequestStatus — Stringnamen auf dem Draht. */
|
||||
export type RequestStatus = 'New' | 'InProgress' | 'Assigned' | 'Answered' | 'Abandoned'
|
||||
export const REQUEST_STATUSES: RequestStatus[] = [
|
||||
'New',
|
||||
'InProgress',
|
||||
'Assigned',
|
||||
'Answered',
|
||||
'Abandoned',
|
||||
]
|
||||
|
||||
export interface InboxRequest {
|
||||
id: string
|
||||
gmailMessageId: string
|
||||
threadId: string | null
|
||||
fromAddress: string
|
||||
fromName: string | null
|
||||
subject: string | null
|
||||
bodyText: string | null
|
||||
receivedAt: string
|
||||
status: RequestStatus
|
||||
assignedContactId: string | null
|
||||
draftReply: string | null
|
||||
answeredAt: string | null
|
||||
}
|
||||
|
||||
export interface RequestTriage {
|
||||
status?: RequestStatus | null
|
||||
assignedContactId?: string | null
|
||||
}
|
||||
|
||||
/** Ergebnis von POST /api/requests/sync. */
|
||||
export interface SyncResult {
|
||||
imported: number
|
||||
/** null = ok; "MailNotConfigured" | "MailAuthFailed" | … */
|
||||
error: string | null
|
||||
}
|
||||
|
||||
const BASE = '/api/requests'
|
||||
|
||||
export function listRequests(query: GridifyQuery): Promise<Paged<InboxRequest>> {
|
||||
return api.get<Paged<InboxRequest>>(`${BASE}${toQueryString(query)}`)
|
||||
}
|
||||
|
||||
export function getRequest(id: string): Promise<InboxRequest> {
|
||||
return api.get<InboxRequest>(`${BASE}/${id}`)
|
||||
}
|
||||
|
||||
export function triageRequest(id: string, triage: RequestTriage): Promise<void> {
|
||||
return api.put<void>(`${BASE}/${id}`, triage)
|
||||
}
|
||||
|
||||
export function syncRequests(): Promise<SyncResult> {
|
||||
return api.post<SyncResult>(`${BASE}/sync`, {})
|
||||
}
|
||||
|
||||
/** KI-Entwurf erzeugen (INBOX-2); 503 {code:'AiKeyMissing'} solange unkonfiguriert. */
|
||||
export function draftReply(id: string): Promise<InboxRequest> {
|
||||
return api.post<InboxRequest>(`${BASE}/${id}/draft`, {})
|
||||
}
|
||||
|
||||
/** (Bearbeitete) Antwort senden (INBOX-3); setzt serverseitig Answered. */
|
||||
export function sendReply(id: string, body: string): Promise<InboxRequest> {
|
||||
return api.post<InboxRequest>(`${BASE}/${id}/send`, { body })
|
||||
}
|
||||
|
||||
/**
|
||||
* ProblemDetails-Titel aus einem ApiError lesen (Send-Endpunkt meldet
|
||||
* MailNotConfigured/MailAuthFailed über `title`, nicht `code`).
|
||||
*/
|
||||
export function problemTitle(err: unknown): string | null {
|
||||
if (err instanceof ApiError && err.body && typeof err.body === 'object' && 'title' in err.body) {
|
||||
const title = (err.body as { title: unknown }).title
|
||||
return typeof title === 'string' ? title : null
|
||||
}
|
||||
return null
|
||||
}
|
||||
Reference in New Issue
Block a user