diff --git a/apps/admin-desktop/src/pages/CancelDispatchModal.tsx b/apps/admin-desktop/src/pages/CancelDispatchModal.tsx new file mode 100644 index 00000000..f8b07b41 --- /dev/null +++ b/apps/admin-desktop/src/pages/CancelDispatchModal.tsx @@ -0,0 +1,90 @@ +import { useState } from 'react' +import { callables } from '../services/callables' + +type CancelReason = + | 'responder_unavailable' + | 'duplicate_report' + | 'admin_error' + | 'citizen_withdrew' + +const REASONS: CancelReason[] = [ + 'responder_unavailable', + 'duplicate_report', + 'admin_error', + 'citizen_withdrew', +] + +export function CancelDispatchModal({ + dispatchId, + currentStatus, + onClose, + onError, +}: { + dispatchId: string + currentStatus: string + onClose: () => void + onError: (msg: string) => void +}) { + const [reason, setReason] = useState('admin_error') + const [submitting, setSubmitting] = useState(false) + + async function confirm() { + setSubmitting(true) + try { + await callables.cancelDispatch({ + dispatchId, + reason, + idempotencyKey: crypto.randomUUID(), + }) + onClose() + } catch (err: unknown) { + onError(err instanceof Error ? err.message : 'Cancel failed') + setSubmitting(false) + } + } + + const cancellable = + currentStatus === 'pending' || + currentStatus === 'accepted' || + currentStatus === 'acknowledged' || + currentStatus === 'en_route' || + currentStatus === 'on_scene' + + return ( +
+

Cancel Dispatch

+

This will revert the report back to verified status. The responder will be notified.

+ {cancellable ? ( + <> +
+ Reason + {REASONS.map((r) => ( + + ))} +
+ + + ) : ( +

+ Dispatch in {currentStatus} cannot be cancelled (only{' '} + {REASONS.slice(0, -1).join(', ')} or {REASONS[REASONS.length - 1]} from{' '} + pending/accepted/acknowledged/en_route/on_scene). +

+ )} + +
+ ) +} diff --git a/apps/admin-desktop/src/pages/CloseReportModal.tsx b/apps/admin-desktop/src/pages/CloseReportModal.tsx new file mode 100644 index 00000000..19711197 --- /dev/null +++ b/apps/admin-desktop/src/pages/CloseReportModal.tsx @@ -0,0 +1,56 @@ +import { useState } from 'react' +import { callables } from '../services/callables' + +export function CloseReportModal({ + reportId, + onClose, + onError, +}: { + reportId: string + onClose: () => void + onError: (msg: string) => void +}) { + const [summary, setSummary] = useState('') + const [submitting, setSubmitting] = useState(false) + + async function confirm() { + setSubmitting(true) + try { + const trimmed = summary.trim() + await callables.closeReport({ + reportId, + idempotencyKey: crypto.randomUUID(), + ...(trimmed ? { closureSummary: trimmed } : {}), + }) + onClose() + } catch (err: unknown) { + onError(err instanceof Error ? err.message : 'Close failed') + setSubmitting(false) + } + } + + return ( +
+

Close Report

+

+ This will archive the report. Only close a report after the incident has been fully resolved + by responders. +

+