"use client"; import { FormEvent, useMemo, useState } from "react"; import { CalendarCheck, MessageSquareText, Search, UserRoundCheck } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Dialog } from "@/components/ui/dialog"; import { Input, Textarea } from "@/components/ui/input"; import { Select } from "@/components/ui/select"; import { Table } from "@/components/ui/table"; import type { ApiResult } from "@/modules/core/server/api"; import type { FamilyContact, FamilyContactInput, FamilyContactStatus, FamilyFeedback, FamilyFeedbackInput, FamilyFeedbackStatus, FamilyFeedbackType, FamilyServiceData, FamilyVisitAppointment, FamilyVisitInput, FamilyVisitStatus } from "@/modules/family/types"; import { FAMILY_CONTACT_STATUS_LABELS, FAMILY_CONTACT_STATUS_VALUES, FAMILY_FEEDBACK_STATUS_LABELS, FAMILY_FEEDBACK_STATUS_VALUES, FAMILY_FEEDBACK_TYPE_LABELS, FAMILY_FEEDBACK_TYPE_VALUES, FAMILY_VISIT_STATUS_LABELS, FAMILY_VISIT_STATUS_VALUES } from "@/modules/family/types"; type Props = { canManage: boolean; initialData: FamilyServiceData }; type DialogState = | { kind: "contact"; mode: "create"; value: FamilyContactInput } | { kind: "contact"; mode: "edit"; id: string; value: FamilyContactInput } | { kind: "visit"; mode: "create"; value: FamilyVisitInput } | { kind: "visit"; mode: "edit"; id: string; value: FamilyVisitInput } | { kind: "feedback"; mode: "create"; value: FamilyFeedbackInput } | { kind: "feedback"; mode: "edit"; id: string; value: FamilyFeedbackInput }; const emptyContact: FamilyContactInput = { elderId: "", name: "", relationship: "", phone: "", status: "active", notes: "" }; const emptyVisit: FamilyVisitInput = { elderId: "", contactId: undefined, scheduledAt: "", status: "requested", notes: "" }; const emptyFeedback: FamilyFeedbackInput = { elderId: "", contactId: undefined, feedbackType: "service", content: "", status: "open", responseNotes: "" }; function formatDateTime(value: string | undefined): string { return value ? new Date(value).toLocaleString("zh-CN") : "-"; } function toLocalDateTime(value: string | undefined): string { return value ? value.slice(0, 16) : ""; } function contactToInput(contact: FamilyContact): FamilyContactInput { return { elderId: contact.elderId, name: contact.name, relationship: contact.relationship, phone: contact.phone, status: contact.status, notes: contact.notes }; } function visitToInput(visit: FamilyVisitAppointment): FamilyVisitInput { return { elderId: visit.elderId, contactId: visit.contactId, scheduledAt: visit.scheduledAt, status: visit.status, notes: visit.notes }; } function feedbackToInput(feedback: FamilyFeedback): FamilyFeedbackInput { return { elderId: feedback.elderId, contactId: feedback.contactId, feedbackType: feedback.feedbackType, content: feedback.content, status: feedback.status, responseNotes: feedback.responseNotes }; } function badgeVariant(value: string): "secondary" | "success" | "warning" { if (value === "active" || value === "approved" || value === "completed" || value === "resolved" || value === "closed") return "success"; if (value === "requested" || value === "open" || value === "in_progress") return "warning"; return "secondary"; } export function FamilyWorkspaceClient({ canManage, initialData }: Props): React.ReactElement { const [data, setData] = useState(initialData); const [query, setQuery] = useState(""); const [dialog, setDialog] = useState(); const [message, setMessage] = useState(""); const [isPending, setIsPending] = useState(false); const normalizedQuery = query.trim().toLowerCase(); const filteredContacts = useMemo(() => data.contacts.filter((item) => !normalizedQuery || [item.elderName, item.name, item.relationship, item.phone, item.notes].join(" ").toLowerCase().includes(normalizedQuery)), [data.contacts, normalizedQuery]); const filteredVisits = useMemo(() => data.visits.filter((item) => !normalizedQuery || [item.elderName, item.contactName, item.notes].join(" ").toLowerCase().includes(normalizedQuery)), [data.visits, normalizedQuery]); const filteredFeedback = useMemo(() => data.feedback.filter((item) => !normalizedQuery || [item.elderName, item.contactName, item.content, item.responseNotes].join(" ").toLowerCase().includes(normalizedQuery)), [data.feedback, normalizedQuery]); async function refreshData(): Promise { const response = await fetch("/api/family/contacts"); const result = (await response.json()) as ApiResult<{ data: FamilyServiceData }>; if (result.success) setData(result.data); } async function submitDialog(event: FormEvent): Promise { event.preventDefault(); if (!dialog || !canManage) { setMessage("当前角色无权维护家属服务"); return; } const isCreate = dialog.mode === "create"; const path = dialog.kind === "contact" ? isCreate ? "/api/family/contacts" : `/api/family/contacts/${dialog.id}` : dialog.kind === "visit" ? isCreate ? "/api/family/visits" : `/api/family/visits/${dialog.id}` : isCreate ? "/api/family/feedback" : `/api/family/feedback/${dialog.id}`; setIsPending(true); const response = await fetch(path, { method: isCreate ? "POST" : "PATCH", headers: { "content-type": "application/json" }, body: JSON.stringify(dialog.value) }); const result = (await response.json()) as ApiResult>; setIsPending(false); setMessage(result.reason); if (result.success) { setDialog(undefined); await refreshData(); } } async function remove(kind: "contact" | "visit" | "feedback", id: string): Promise { if (!canManage) { setMessage("当前角色无权删除家属服务记录"); return; } const path = kind === "contact" ? `/api/family/contacts/${id}` : kind === "visit" ? `/api/family/visits/${id}` : `/api/family/feedback/${id}`; setIsPending(true); const response = await fetch(path, { method: "DELETE" }); const result = (await response.json()) as ApiResult>; setIsPending(false); setMessage(result.reason); if (result.success) await refreshData(); } return (

家属服务

维护家属联系人、探访预约和反馈处理。

{message ?

{message}

: null} {filteredContacts.map((contact) =>

{contact.name}

{contact.relationship} / {contact.phone}

{contact.elderName}{FAMILY_CONTACT_STATUS_LABELS[contact.status]}{contact.notes || "-"} remove("contact", contact.id)} onEdit={() => setDialog({ kind: "contact", mode: "edit", id: contact.id, value: contactToInput(contact) })} />
)} {filteredContacts.length === 0 ? : null}
{filteredVisits.map((visit) =>

{visit.elderName}

{formatDateTime(visit.scheduledAt)}

{visit.contactName || "-"}{FAMILY_VISIT_STATUS_LABELS[visit.status]}{visit.notes || "-"} remove("visit", visit.id)} onEdit={() => setDialog({ kind: "visit", mode: "edit", id: visit.id, value: visitToInput(visit) })} />
)} {filteredVisits.length === 0 ? : null}
{filteredFeedback.map((feedback) =>

{feedback.elderName}

{feedback.content}

{feedback.contactName || "-"}{FAMILY_FEEDBACK_TYPE_LABELS[feedback.feedbackType]}{FAMILY_FEEDBACK_STATUS_LABELS[feedback.status]} remove("feedback", feedback.id)} onEdit={() => setDialog({ kind: "feedback", mode: "edit", id: feedback.id, value: feedbackToInput(feedback) })} />
)} {filteredFeedback.length === 0 ? : null}
setDialog(undefined)} open={dialog !== undefined} title={dialog?.kind === "contact" ? "家属联系人" : dialog?.kind === "visit" ? "探访预约" : "服务反馈"} width="lg"> {dialog ?
: null}
); } function FamilyForm({ contacts, dialog, elders, setDialog }: { contacts: FamilyContact[]; dialog: DialogState; elders: Array<{ id: string; name: string }>; setDialog: (dialog: DialogState) => void }): React.ReactElement { const elderOptions = [{ value: "", label: "选择老人", disabled: true }, ...elders.map((elder) => ({ value: elder.id, label: elder.name }))]; if (dialog.kind === "contact") { const value = dialog.value; const update = (next: Partial) => setDialog({ ...dialog, value: { ...value, ...next } }); return <> update({ name: event.target.value })} required value={value.name} /> update({ relationship: event.target.value })} required value={value.relationship} /> update({ phone: event.target.value })} required value={value.phone} />