"use client"; import { FormEvent, useMemo, useState } from "react"; import { Edit3, Filter, Plus, Search, Trash2 } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Dialog } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import type { ApiResult } from "@/modules/core/server/api"; import type { Permission } from "@/modules/core/types"; import type { CareLevel, Elder, ElderInput, ElderStatus } from "@/modules/elders/types"; import { CARE_LEVEL_LABELS, ELDER_STATUS_LABELS, GENDER_LABELS } from "@/modules/elders/types"; type EldersClientProps = { initialElders: Elder[]; permissions: Permission[]; }; const emptyInput: ElderInput = { name: "", gender: "male", age: 75, careLevel: "self-care", room: "", bed: "", status: "active", primaryContact: "", phone: "", medicalNotes: "", }; const PAGE_SIZE = 8; const ALL_STATUS = "all"; const ALL_CARE_LEVELS = "all"; function elderToInput(elder: Elder): ElderInput { return { name: elder.name, gender: elder.gender, age: elder.age, careLevel: elder.careLevel, room: elder.room, bed: elder.bed, status: elder.status, primaryContact: elder.primaryContact, phone: elder.phone, medicalNotes: elder.medicalNotes, }; } function formatDateTime(value: string): string { return new Date(value).toLocaleString("zh-CN"); } function getRiskLabel(elder: Elder): { label: string; variant: "danger" | "warning" | "success" } { if (elder.careLevel === "intensive") { return { label: "红色重点", variant: "danger" }; } if (elder.status === "pending" || elder.careLevel === "assisted") { return { label: "黄色关注", variant: "warning" }; } return { label: "绿色稳定", variant: "success" }; } function matchesFilters( elder: Elder, query: string, statusFilter: ElderStatus | typeof ALL_STATUS, careLevelFilter: CareLevel | typeof ALL_CARE_LEVELS, ): boolean { const normalizedQuery = query.trim().toLowerCase(); const matchesSearch = normalizedQuery.length === 0 || [elder.name, elder.room, elder.bed, elder.primaryContact, elder.phone, elder.medicalNotes] .join(" ") .toLowerCase() .includes(normalizedQuery); const matchesStatus = statusFilter === ALL_STATUS || elder.status === statusFilter; const matchesCareLevel = careLevelFilter === ALL_CARE_LEVELS || elder.careLevel === careLevelFilter; return matchesSearch && matchesStatus && matchesCareLevel; } export function EldersClient({ initialElders, permissions }: EldersClientProps): React.ReactElement { const [elders, setElders] = useState(initialElders); const [editingId, setEditingId] = useState(null); const [form, setForm] = useState(emptyInput); const [message, setMessage] = useState(""); const [isPending, setIsPending] = useState(false); const [isFormOpen, setIsFormOpen] = useState(false); const [deleteTarget, setDeleteTarget] = useState(null); const [query, setQuery] = useState(""); const [statusFilter, setStatusFilter] = useState(ALL_STATUS); const [careLevelFilter, setCareLevelFilter] = useState(ALL_CARE_LEVELS); const [page, setPage] = useState(1); const canCreate = permissions.includes("elder:create"); const canUpdate = permissions.includes("elder:update"); const canDelete = permissions.includes("elder:delete"); const isEditing = editingId !== null; const canSubmit = isEditing ? canUpdate : canCreate; const metrics = useMemo(() => { const activeCount = elders.filter((elder) => elder.status === "active").length; const pendingCount = elders.filter((elder) => elder.status === "pending").length; const intensiveCount = elders.filter((elder) => elder.careLevel === "intensive").length; const warningCount = elders.filter((elder) => getRiskLabel(elder).variant !== "success").length; return { activeCount, pendingCount, intensiveCount, warningCount }; }, [elders]); const filteredElders = useMemo( () => elders.filter((elder) => matchesFilters(elder, query, statusFilter, careLevelFilter)), [careLevelFilter, elders, query, statusFilter], ); const pageCount = Math.max(1, Math.ceil(filteredElders.length / PAGE_SIZE)); const safePage = Math.min(page, pageCount); const pageStart = (safePage - 1) * PAGE_SIZE; const visibleElders = filteredElders.slice(pageStart, pageStart + PAGE_SIZE); function updateField(key: K, value: ElderInput[K]): void { setForm((current) => ({ ...current, [key]: value })); } function beginCreate(): void { setEditingId(null); setForm(emptyInput); setMessage(""); setIsFormOpen(true); } function beginEdit(elder: Elder): void { setEditingId(elder.id); setForm(elderToInput(elder)); setMessage(""); setIsFormOpen(true); } function closeForm(): void { if (isPending) { return; } setIsFormOpen(false); setEditingId(null); setForm(emptyInput); } function updateQuery(value: string): void { setQuery(value); setPage(1); } function updateStatusFilter(value: string): void { setStatusFilter(value as ElderStatus | typeof ALL_STATUS); setPage(1); } function updateCareLevelFilter(value: string): void { setCareLevelFilter(value as CareLevel | typeof ALL_CARE_LEVELS); setPage(1); } async function refreshElders(): Promise { const response = await fetch("/api/elders"); const result = (await response.json()) as ApiResult<{ elders: Elder[] }>; if (result.success) { setElders(result.elders); } } async function handleSubmit(event: FormEvent): Promise { event.preventDefault(); if (!canSubmit) { setMessage("当前角色无权保存老人档案"); return; } setIsPending(true); setMessage(""); const response = await fetch(isEditing ? `/api/elders/${editingId}` : "/api/elders", { method: isEditing ? "PATCH" : "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(form), }); const result = (await response.json()) as ApiResult<{ elder: Elder }>; setIsPending(false); if (!result.success) { setMessage(result.reason); return; } await refreshElders(); setEditingId(null); setForm(emptyInput); setIsFormOpen(false); setMessage(result.reason); } async function handleDelete(): Promise { if (!deleteTarget) { return; } if (!canDelete) { setMessage("当前角色无权删除老人档案"); return; } setIsPending(true); setMessage(""); const response = await fetch(`/api/elders/${deleteTarget.id}`, { method: "DELETE" }); const result = (await response.json()) as ApiResult<{ elder: Elder }>; setIsPending(false); if (!result.success) { setMessage(result.reason); return; } await refreshElders(); setDeleteTarget(null); setMessage(result.reason); } return (
全栈 CRUD

老人档案

列表管理、筛选搜索、分页、弹窗维护和审计持久化。

在院老人 {metrics.activeCount} 待入住 {metrics.pendingCount} 红色重点 {metrics.intensiveCount} 需关注标记 {metrics.warningCount}
档案列表 共 {filteredElders.length} 条匹配记录,按状态、照护等级和关键词快速定位。
{message ? (

{message}

) : null}
{visibleElders.map((elder) => { const risk = getRiskLabel(elder); return ( ); })} {visibleElders.length === 0 ? ( ) : null}
姓名 照护 床位 联系人 状态 标记 操作

{elder.name}

{GENDER_LABELS[elder.gender]} · {elder.age} 岁

{CARE_LEVEL_LABELS[elder.careLevel]} {elder.room}-{elder.bed} {elder.primaryContact} / {elder.phone} {ELDER_STATUS_LABELS[elder.status]} {risk.label}
暂无匹配老人档案

第 {safePage} / {pageCount} 页