feat: complete birth time consultation handoff

This commit is contained in:
Jesse_Chen
2026-07-19 14:51:06 +08:00
parent 05d2f3ca55
commit c22dea9979
24 changed files with 960 additions and 87 deletions
@@ -0,0 +1,35 @@
export type BirthTimeAssessmentPhase = "saving_profile" | "assessing";
const progressCopy = {
saving_profile: {
title: "正在保存出生资料",
detail: "已保留你刚才的选择,马上开始生成生时评估。",
},
assessing: {
title: "正在生成生时评估",
detail: "正在读取已有资料并准备下一步,不需要重复操作。",
},
} as const satisfies Record<BirthTimeAssessmentPhase, { readonly title: string; readonly detail: string }>;
export function BirthTimeAssessmentOverlay({ phase }: { readonly phase: BirthTimeAssessmentPhase | null }) {
if (phase === null) return null;
const copy = progressCopy[phase];
return (
<div
className="birth-time-assessment-overlay"
data-phase={phase}
role="status"
aria-live="polite"
>
<div className="birth-time-assessment-progress">
<div className="app-loading-symbol" aria-hidden="true">
<span className="app-loading-orbit" />
<span className="app-loading-mark" />
</div>
<strong>{copy.title}</strong>
<span>{copy.detail}</span>
</div>
</div>
);
}
@@ -24,7 +24,7 @@ export function BirthTimeCandidateResult({ journey, controller }: CandidateResul
return (
<div className="birth-time-candidate-result" aria-live="polite">
<p className="birth-time-evidence-boundary"><span className="phrase-nowrap">使</span></p>
{terminalPath && <NewAssessmentAction controller={controller} />}
{terminalPath && <TerminalAction controller={controller} path={terminalPath} />}
</div>
);
}
@@ -52,13 +52,13 @@ export function BirthTimeCandidateResult({ journey, controller }: CandidateResul
{action.kind === "present_low_result" && (
<div className="birth-time-candidate-terminal" role="status">
<p>{dynamic ? "目前没有足够的新信息继续稳定缩小范围,本次评估已结束并保存当前候选范围。" : "本轮校正已安全结束,只保留候选范围,当前排盘使用时间保持不变。"}</p>
{terminalPath && <NewAssessmentAction controller={controller} />}
{terminalPath && <TerminalAction controller={controller} path={terminalPath} />}
</div>
)}
{action.kind === "present_medium_result" && winner && dynamic && (
<div className="birth-time-candidate-terminal" role="status">
<p><span className="phrase-nowrap">使</span></p>
<NewAssessmentAction controller={controller} />
{terminalPath && <TerminalAction controller={controller} path={terminalPath} />}
</div>
)}
{action.kind === "present_medium_result" && winner && !dynamic && (
@@ -72,7 +72,7 @@ export function BirthTimeCandidateResult({ journey, controller }: CandidateResul
{action.kind === "candidate_saved" && (
<div className="birth-time-candidate-terminal" role="status">
<p className="birth-time-success-note"><span className="phrase-nowrap">使</span></p>
{terminalPath && <NewAssessmentAction controller={controller} />}
{terminalPath && <TerminalAction controller={controller} path={terminalPath} />}
</div>
)}
{action.kind === "request_candidate_confirmation" && winner && (
@@ -94,9 +94,20 @@ export function BirthTimeCandidateResult({ journey, controller }: CandidateResul
);
}
function NewAssessmentAction({ controller }: {
function TerminalAction({ controller, path }: {
readonly controller: BirthTimeGuidedController;
readonly path: NonNullable<ReturnType<typeof guidedTerminalPath>>;
}) {
if (path.kind === "complete_with_candidate") {
return (
<div className="birth-time-new-assessment">
<button className="button-primary birth-time-guided-action" disabled={controller.pending} onClick={() => controller.completeCandidate(path.time)} type="button">
{controller.pending ? "保存中…" : "完成并继续对话"}
</button>
<small> {path.time} </small>
</div>
);
}
return (
<div className="birth-time-new-assessment">
<button className="button-secondary birth-time-guided-action" disabled={controller.pending} onClick={controller.editBirthTimeDetails} type="button"></button>
@@ -3,6 +3,7 @@
import { useId } from "react";
import { BirthDatePicker } from "@/components/birth-date-picker";
import {
birthTimeDisplayState,
birthTimePeriodOptions,
birthTimeSourceOptions,
type BirthTimeDraft,
@@ -52,6 +53,7 @@ export function BirthTimeIntakeFields({ value, onPatch }: BirthTimeIntakeProps)
const groupId = useId();
const source = value.birthTimeSource;
const isConfirmed = value.birthTimeStatus === "confirmed";
const displayState = birthTimeDisplayState(value);
const usesClockTime = source === "hospital_record"
|| source === "family_exact"
|| source === "approximate"
@@ -59,6 +61,27 @@ export function BirthTimeIntakeFields({ value, onPatch }: BirthTimeIntakeProps)
return (
<div className="birth-time-intake">
{displayState && (
<section className="birth-time-profile-result" aria-label="生时校正结果">
<div className="birth-time-profile-result-heading">
<span></span>
<strong>{displayState.kind === "candidate" ? "候选时间" : "已确认"}</strong>
</div>
<dl>
<div>
<dt>{displayState.kind === "candidate" ? "当前工作排盘时间" : "当前排盘时间"}</dt>
<dd>{displayState.activeTime}</dd>
</div>
<div>
<dt></dt>
<dd>{displayState.reportedLabel}</dd>
</div>
</dl>
{displayState.kind === "candidate" && (
<p></p>
)}
</section>
)}
<BirthDatePicker
value={value.date}
disabled={isConfirmed}