diff --git a/modules/elders/components/EldersClient.tsx b/modules/elders/components/EldersClient.tsx index 7dfec5b..4b00cba 100644 --- a/modules/elders/components/EldersClient.tsx +++ b/modules/elders/components/EldersClient.tsx @@ -20,14 +20,21 @@ type EldersClientProps = { permissions: Permission[]; }; -const emptyInput: ElderInput = { +type ElderFormState = Omit & { + age: string; + careLevel: CareLevel | ""; + gender: ElderInput["gender"] | ""; + status: ElderStatus | ""; +}; + +const emptyInput: ElderFormState = { name: "", - gender: "male", - age: 75, - careLevel: "self-care", + gender: "", + age: "", + careLevel: "", room: "", bed: "", - status: "active", + status: "", primaryContact: "", phone: "", medicalNotes: "", @@ -37,11 +44,11 @@ const PAGE_SIZE = 8; const ALL_STATUS = "all"; const ALL_CARE_LEVELS = "all"; -function elderToInput(elder: Elder): ElderInput { +function elderToInput(elder: Elder): ElderFormState { return { name: elder.name, gender: elder.gender, - age: elder.age, + age: String(elder.age), careLevel: elder.careLevel, room: elder.room, bed: elder.bed, @@ -60,18 +67,6 @@ function formatBedLabel(elder: Pick): string { return elder.room && elder.bed ? `${elder.room}-${elder.bed}` : "-"; } -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" }; -} - const statusOptions = [ { value: ALL_STATUS, label: "全部状态" }, ...Object.entries(ELDER_STATUS_LABELS).map(([value, label]) => ({ value, label })), @@ -82,9 +77,18 @@ const careLevelOptions = [ ...Object.entries(CARE_LEVEL_LABELS).map(([value, label]) => ({ value, label })), ]; -const genderOptions = Object.entries(GENDER_LABELS).map(([value, label]) => ({ value, label })); -const formCareLevelOptions = Object.entries(CARE_LEVEL_LABELS).map(([value, label]) => ({ value, label })); -const formStatusOptions = Object.entries(ELDER_STATUS_LABELS).map(([value, label]) => ({ value, label })); +const genderOptions = [ + { value: "", label: "选择性别", disabled: true }, + ...Object.entries(GENDER_LABELS).map(([value, label]) => ({ value, label })), +]; +const formCareLevelOptions = [ + { value: "", label: "选择护理等级", disabled: true }, + ...Object.entries(CARE_LEVEL_LABELS).map(([value, label]) => ({ value, label })), +]; +const formStatusOptions = [ + { value: "", label: "选择档案状态", disabled: true }, + ...Object.entries(ELDER_STATUS_LABELS).map(([value, label]) => ({ value, label })), +]; function matchesFilters( elder: Elder, @@ -108,7 +112,7 @@ function matchesFilters( export function EldersClient({ initialElders, permissions }: EldersClientProps): React.ReactElement { const [elders, setElders] = useState(initialElders); const [editingId, setEditingId] = useState(null); - const [form, setForm] = useState(emptyInput); + const [form, setForm] = useState(emptyInput); const [message, setMessage] = useState(""); const [isPending, setIsPending] = useState(false); const [isFormOpen, setIsFormOpen] = useState(false); @@ -125,11 +129,11 @@ export function EldersClient({ initialElders, permissions }: EldersClientProps): const canSubmit = isEditing ? canUpdate : canCreate; const metrics = useMemo(() => { + const totalCount = elders.length; 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 }; + const dischargedCount = elders.filter((elder) => elder.status === "discharged").length; + return { activeCount, dischargedCount, pendingCount, totalCount }; }, [elders]); const filteredElders = useMemo( @@ -142,7 +146,7 @@ export function EldersClient({ initialElders, permissions }: EldersClientProps): const visibleElders = filteredElders.slice(pageStart, pageStart + PAGE_SIZE); const editingElder = editingId ? elders.find((elder) => elder.id === editingId) : undefined; - function updateField(key: K, value: ElderInput[K]): void { + function updateField(key: K, value: ElderFormState[K]): void { setForm((current) => ({ ...current, [key]: value })); } @@ -203,10 +207,31 @@ export function EldersClient({ initialElders, permissions }: EldersClientProps): setIsPending(true); setMessage(""); + if (!form.gender || !form.careLevel || !form.status) { + setIsPending(false); + setMessage("请完整选择性别、护理等级和档案状态"); + return; + } + + const numericAge = Number(form.age); + if (!Number.isInteger(numericAge) || numericAge < 0 || numericAge > 130) { + setIsPending(false); + setMessage("年龄需为0到130之间的整数"); + return; + } + + const payload: ElderInput = { + ...form, + age: numericAge, + careLevel: form.careLevel, + gender: form.gender, + status: form.status, + }; + const response = await fetch(isEditing ? `/api/elders/${editingId}` : "/api/elders", { method: isEditing ? "PATCH" : "POST", headers: { "content-type": "application/json" }, - body: JSON.stringify(form), + body: JSON.stringify(payload), }); const result = (await response.json()) as ApiResult<{ elder: Elder }>; setIsPending(false); @@ -269,7 +294,13 @@ export function EldersClient({ initialElders, permissions }: EldersClientProps):
- 在院老人 + 档案总数 + {metrics.totalCount} + + + + + 在院 {metrics.activeCount} @@ -279,16 +310,10 @@ export function EldersClient({ initialElders, permissions }: EldersClientProps): {metrics.pendingCount} - - - 红色重点 - {metrics.intensiveCount} - - - 需关注标记 - {metrics.warningCount} + 已退住 + {metrics.dischargedCount}
@@ -347,65 +372,57 @@ export function EldersClient({ initialElders, permissions }: EldersClientProps): 床位 联系人 状态 - 标记 操作 - {visibleElders.map((elder) => { - const risk = getRiskLabel(elder); - - return ( - - -

{elder.name}

-

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

-
- {CARE_LEVEL_LABELS[elder.careLevel]} - {formatBedLabel(elder)} - - {elder.primaryContact} / {elder.phone} - - - - {ELDER_STATUS_LABELS[elder.status]} - - - - {risk.label} - - -
- - -
-
-
- ); - })} + {visibleElders.map((elder) => ( + + +

{elder.name}

+

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

+
+ {CARE_LEVEL_LABELS[elder.careLevel]} + {formatBedLabel(elder)} + + {elder.primaryContact} / {elder.phone} + + + + {ELDER_STATUS_LABELS[elder.status]} + + + +
+ + +
+
+
+ ))} {visibleElders.length === 0 ? ( - + 暂无匹配老人档案 @@ -457,8 +474,9 @@ export function EldersClient({ initialElders, permissions }: EldersClientProps): 性别 updateField("age", Number(event.target.value))} + onChange={(event) => updateField("age", event.target.value)} + required type="number" value={form.age} /> @@ -478,8 +497,9 @@ export function EldersClient({ initialElders, permissions }: EldersClientProps): 护理等级 updateField("status", value as ElderInput["status"])} + onValueChange={(value) => updateField("status", value as ElderFormState["status"])} options={formStatusOptions} + required value={form.status} /> diff --git a/modules/settings/components/OrganizationInviteDialog.tsx b/modules/settings/components/OrganizationInviteDialog.tsx index 00d0c95..78c12d9 100644 --- a/modules/settings/components/OrganizationInviteDialog.tsx +++ b/modules/settings/components/OrganizationInviteDialog.tsx @@ -36,7 +36,6 @@ export function OrganizationInviteDialog({ () => roles.filter((role) => role.scope === "organization" && role.organizationId === organization.id && role.isEnabled), [organization.id, roles], ); - const defaultRoleId = organizationRoles.find((role) => role.key === "caregiver")?.id ?? organizationRoles[0]?.id ?? ""; function closeDialog(): void { if (isPending) { @@ -52,14 +51,20 @@ export function OrganizationInviteDialog({ event.preventDefault(); setMessage(""); setLastInviteHref(""); - setIsPending(true); const formData = new FormData(event.currentTarget); + const roleId = String(formData.get("roleId") ?? ""); + if (!roleId) { + setMessage("请选择邀请角色"); + return; + } + + setIsPending(true); const response = await fetch(`/api/organizations/${organization.id}/invitations`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: String(formData.get("email") ?? ""), - roleId: String(formData.get("roleId") ?? ""), + roleId, }), }); const result = (await response.json()) as ApiResult<{ invitation: OrganizationInvitation }>; @@ -94,11 +99,14 @@ export function OrganizationInviteDialog({ 角色 ({ value: role.id, label: role.label }))} + options={[ + { value: "", label: selectedOrganizationId ? "选择角色" : "先选择机构", disabled: true }, + ...organizationRoles.map((role) => ({ value: role.id, label: role.label })), + ]} required /> @@ -303,17 +315,14 @@ export function UserAccountActions({ }: UserAccountActionsProps): React.ReactElement { const router = useRouter(); const [isEditOpen, setIsEditOpen] = useState(false); - const [selectedOrganizationId, setSelectedOrganizationId] = useState( - () => account.organizationId ?? getDefaultOrganizationId(organizations), - ); + const [selectedOrganizationId, setSelectedOrganizationId] = useState(() => account.organizationId ?? ""); const [message, setMessage] = useState(""); const [isPending, setIsPending] = useState(false); const organizationRoles = useMemo( () => getOrganizationRoles(roles, selectedOrganizationId), [roles, selectedOrganizationId], ); - const defaultRoleId = - organizationRoles.find((role) => role.key === account.role || role.id === account.role)?.id ?? organizationRoles[0]?.id ?? ""; + const defaultRoleId = organizationRoles.find((role) => role.key === account.role || role.id === account.role)?.id ?? ""; const isCurrentAccount = account.id === currentAccountId; async function updateAccount(payload: Record): Promise { @@ -430,7 +439,10 @@ export function UserAccountActions({ aria-label="账号机构" name="organizationId" onValueChange={setSelectedOrganizationId} - options={organizations.map((organization) => ({ value: organization.id, label: organization.name }))} + options={[ + { value: "", label: "选择机构", disabled: true }, + ...organizations.map((organization) => ({ value: organization.id, label: organization.name })), + ]} value={selectedOrganizationId} /> @@ -442,7 +454,10 @@ export function UserAccountActions({ disabled={organizationRoles.length === 0} key={selectedOrganizationId} name="roleId" - options={organizationRoles.map((role) => ({ value: role.id, label: role.label }))} + options={[ + { value: "", label: selectedOrganizationId ? "选择角色" : "先选择机构", disabled: true }, + ...organizationRoles.map((role) => ({ value: role.id, label: role.label })), + ]} />