import { formatLocalMonthDayTime } from "@/shared/utils/date-time"; import { useCallback, useEffect, useRef, useState, type ReactNode, type RefObject } from "react"; import type { Map as MapLibreMap } from "maplibre-gl"; import { CalendarClock, LoaderCircle, Pencil, Plus, RefreshCw, Sparkles, Trash2, X } from "lucide-react"; import { Button } from "@/shared/ui/button"; import { DateTimeField } from "@/shared/ui/date-time-field"; import { Disclosure } from "@/shared/ui/disclosure"; import { FieldSelect } from "@/shared/ui/field-select"; import { Input } from "@/shared/ui/input"; import { StatusBadge, type StatusTone } from "@/shared/ui/status"; import { Textarea } from "@/shared/ui/textarea"; import { WorkOrderReportPanel } from "../components/scheduled-condition-detail-panel"; import { WorkbenchNotice } from "../components/workbench-panel-ui"; import type { ScheduledWorkOrderItem } from "../types"; import type { PeripheralContext, PeripheralSampleClient, PeripheralWorkOrder } from "./client"; import { EventLocationPicker, type EventGpsCoordinate } from "./event-location-picker"; import { mergeEventLocation } from "./event-location-coordinate"; import { useEventLocationAddress, type EventLocationAddressResolver } from "./use-event-location-address"; export function ManagedTestResources({ client, canWrite, capabilities, refreshGeneration, mapReady, mapRef, resolveAddress, onAgentTrigger }: { client: PeripheralSampleClient; canWrite: boolean; capabilities: PeripheralContext["managed_resources"]; refreshGeneration?: number; mapReady: boolean; mapRef: RefObject; resolveAddress: EventLocationAddressResolver; onAgentTrigger?: (workOrder: PeripheralWorkOrder) => Promise; }) { const [orders, setOrders] = useState([]); const [busy, setBusy] = useState(null); const [error, setError] = useState(null); const refresh = useCallback(async () => { if (capabilities.work_orders !== "available") return; setBusy("refresh"); setError(null); try { setOrders((await client.listWorkOrders()).items); } catch (nextError) { setError(message(nextError)); } finally { setBusy(null); } }, [capabilities.work_orders, client]); useEffect(() => { void refresh(); }, [refresh, refreshGeneration]); const mutate = async (key: string, operation: () => Promise) => { setBusy(key); setError(null); try { await operation(); await refresh(); } catch (nextError) { setError(message(nextError)); setBusy(null); } }; return (

工单管理

预约安排、投诉调查与爆管处置,按类型查看和维护。

{error ? {error} : null} {capabilities.work_orders !== "available" ? ( 当前项目尚未配置工单存储,请联系管理员。 ) : ( )}
); } function WorkOrders({ orders, canWrite, busy, mutate, client, mapReady, mapRef, resolveAddress, onAgentTrigger }: { orders: PeripheralWorkOrder[]; canWrite: boolean; busy: string | null; mutate: (key: string, operation: () => Promise) => Promise; client: PeripheralSampleClient; mapReady: boolean; mapRef: RefObject; resolveAddress: EventLocationAddressResolver; onAgentTrigger?: (workOrder: PeripheralWorkOrder) => Promise; }) { const [editorOpen, setEditorOpen] = useState(false); const [editing, setEditing] = useState(null); const [title, setTitle] = useState(""); const [summary, setSummary] = useState(""); const [status, setStatus] = useState("draft"); const [priority, setPriority] = useState("normal"); const [assignee, setAssignee] = useState(""); const [location, setLocation] = useState(""); const [scenarioCode, setScenarioCode] = useState(""); const [scheduledAt, setScheduledAt] = useState(() => toLocalInput(new Date(Date.now() + 60 * 60_000))); const [feedbackRequirements, setFeedbackRequirements] = useState(""); const [progressNote, setProgressNote] = useState(""); const [feedbackAt, setFeedbackAt] = useState(""); const [eventEditorOpen, setEventEditorOpen] = useState(false); const [eventKind, setEventKind] = useState<"water_quality_complaint" | "burst_report">("water_quality_complaint"); const [eventLocation, setEventLocation] = useState(""); const [eventCoordinate, setEventCoordinate] = useState(null); const addressLookup = useEventLocationAddress({ active: eventEditorOpen, coordinate: eventCoordinate, resolveAddress, onChange: setEventLocation }); const [eventOccurredAt, setEventOccurredAt] = useState(toLocalInput(new Date())); const [eventDescription, setEventDescription] = useState(""); const [agentTriggerFailure, setAgentTriggerFailure] = useState<{ message: string; workOrder: PeripheralWorkOrder } | null>(null); const [deleteCandidateId, setDeleteCandidateId] = useState(null); const titleInputRef = useRef(null); const editorTriggerRef = useRef(null); const editingWorkOrderId = editing?.work_order_id; const clear = () => { setEditing(null); setEditorOpen(false); setTitle(""); setSummary(""); setStatus("draft"); setPriority("normal"); setAssignee(""); setLocation(""); setScenarioCode(""); setScheduledAt(toLocalInput(new Date(Date.now() + 60 * 60_000))); setFeedbackRequirements(""); setProgressNote(""); setFeedbackAt(""); }; const closeEditor = () => { const trigger = editorTriggerRef.current; const editTriggerId = editingWorkOrderId ? `work-order-edit-${editingWorkOrderId}` : null; clear(); requestAnimationFrame(() => { if (editTriggerId) document.getElementById(editTriggerId)?.focus(); else trigger?.focus(); }); }; useEffect(() => { if (!editorOpen || !titleInputRef.current) return; titleInputRef.current.focus({ preventScroll: true }); const target = editingWorkOrderId ? titleInputRef.current.closest("article") : titleInputRef.current; target?.scrollIntoView({ block: editingWorkOrderId ? "start" : "nearest" }); }, [editorOpen, editingWorkOrderId]); const create = (trigger: HTMLButtonElement) => { editorTriggerRef.current = trigger; clear(); setEditorOpen(true); }; const edit = (item: PeripheralWorkOrder, trigger: HTMLButtonElement) => { editorTriggerRef.current = trigger; setDeleteCandidateId(null); setEditing(item); setEditorOpen(true); setTitle(item.title); setSummary(item.summary); setStatus(item.status); setPriority(item.priority); setAssignee(item.assignee ?? ""); setLocation(item.location ?? ""); setScenarioCode(item.scenario_code ?? ""); setScheduledAt(item.scheduled_at ? toLocalInput(new Date(item.scheduled_at)) : ""); setFeedbackRequirements((item.feedback_requirements ?? []).join("\n")); setProgressNote(""); setFeedbackAt(completedFeedbackTime(item)); }; const triggerAgent = async (workOrder: PeripheralWorkOrder) => { if (!onAgentTrigger) { setAgentTriggerFailure({ message: "当前工作台未连接 Agent 事件入口。", workOrder }); return; } try { await onAgentTrigger(workOrder); setAgentTriggerFailure(null); } catch (error) { setAgentTriggerFailure({ message: message(error), workOrder }); } }; const createEventWorkOrder = () => void mutate("create-event-order", async () => { const waterQuality = eventKind === "water_quality_complaint"; const submittedLocation = mergeEventLocation(eventLocation, eventCoordinate); const workOrder = await client.createWorkOrder({ title: waterQuality ? `水质投诉:${submittedLocation}` : `爆管上报:${submittedLocation}`, summary: eventDescription.trim(), status: "open", priority: waterQuality ? "high" : "urgent", assignee: null, scheduled_at: new Date(eventOccurredAt).toISOString(), work_order_type: eventKind, location: submittedLocation, scenario_code: null, origin: "system_data", feedback_requirements: [] }); setEventEditorOpen(false); setEventLocation(""); setEventCoordinate(null); setEventDescription(""); await triggerAgent(workOrder); }); const feedbackTimeChanged = feedbackAt !== (editing ? completedFeedbackTime(editing) : ""); const canSave = canWrite && !busy && title.trim() && summary.trim() && (!feedbackTimeChanged || progressNote.trim()); const editorInputClassName = "mt-1 h-9 rounded-lg border-border bg-card"; const editingEvent = editing?.work_order_type !== undefined && editing.work_order_type !== "general"; const orderEditor = editorOpen ? (
{ if (event.key === "Escape" && !busy && !event.defaultPrevented && event.currentTarget.contains(event.target as Node)) { event.preventDefault(); event.stopPropagation(); closeEditor(); } }} >