feat: complete birth time consultation handoff
This commit is contained in:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user