Files
Jyotisha/frontend/src/components/birth-time-intake.tsx
T

164 lines
6.3 KiB
TypeScript

"use client";
import { useId, useState } from "react";
import { BirthDatePicker } from "@/components/birth-date-picker";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { birthTimeConsultationOptionsCopy } from "@/lib/birth-time-consultation-consent";
import {
birthTimeDisplayState,
birthTimeSourceDefaults,
birthTimeSourceOptions,
type BirthTimeDraft,
type BirthTimeDraftPatch,
} from "@/lib/birth-time-intake-model";
type BirthTimeIntakeProps = {
readonly value: BirthTimeDraft;
readonly onPatch: (patch: BirthTimeDraftPatch) => void;
};
const hours = Array.from({ length: 24 }, (_, index) => String(index).padStart(2, "0"));
const minutes = Array.from({ length: 60 }, (_, index) => String(index).padStart(2, "0"));
function splitBirthTime(value: string) {
const [hour = "", minute = ""] = value.split(":");
return { hour, minute };
}
type BirthClockSelectProps = {
readonly value: string;
readonly onChange: (value: string) => void;
};
function BirthClockSelect({ value, onChange }: BirthClockSelectProps) {
const currentTime = value || "";
const [timeParts, setTimeParts] = useState(() => splitBirthTime(currentTime));
const patchTime = (part: "hour" | "minute", nextValue: string | null) => {
if (nextValue === null) return;
const nextParts = { ...timeParts, [part]: nextValue };
setTimeParts(nextParts);
if (nextParts.hour && nextParts.minute) onChange(`${nextParts.hour}:${nextParts.minute}`);
};
return (
<div className="birth-time-clock-selects" aria-label="选择出生时间">
<Select required value={timeParts.hour || null} onValueChange={(nextValue) => patchTime("hour", nextValue)}>
<SelectTrigger aria-label="出生时间小时">
<SelectValue placeholder="时" />
</SelectTrigger>
<SelectContent className="birth-time-clock-menu" style={{ width: 108, minWidth: 108 }}>
{hours.map((hour) => <SelectItem key={hour} value={hour}>{hour}</SelectItem>)}
</SelectContent>
</Select>
<span className="birth-time-clock-separator" aria-hidden="true">:</span>
<Select required value={timeParts.minute || null} onValueChange={(nextValue) => patchTime("minute", nextValue)}>
<SelectTrigger aria-label="出生时间分钟">
<SelectValue placeholder="分" />
</SelectTrigger>
<SelectContent className="birth-time-clock-menu" style={{ width: 108, minWidth: 108 }}>
{minutes.map((minute) => <SelectItem key={minute} value={minute}>{minute}</SelectItem>)}
</SelectContent>
</Select>
</div>
);
}
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"
|| source === "legacy_import";
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" ? "候选时间" : displayState.kind === "accepted" ? "校正采用" : "引擎确认"}</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>这仍是未确认候选,不会自动成为出生分钟;{birthTimeConsultationOptionsCopy(value)}</p>
)}
{displayState.kind === "accepted" && (
<p>这是你从候选中选择的校正采用时间,并非引擎唯一确认分钟;后续排盘会使用它。</p>
)}
</section>
)}
<BirthDatePicker
value={value.date}
disabled={isConfirmed}
onChange={(date) => onPatch({ date })}
/>
{source === "legacy_import" && (
<p className="birth-time-legacy-note">
这是账号里已有的确认时间。本阶段保留原始记录,不在资料表内直接覆盖。
</p>
)}
{!isConfirmed && <fieldset className="birth-time-source-fieldset">
<legend>填写出生时间</legend>
<p className="birth-time-source-intro">请填写你初始化提供的具体出生时间。</p>
<div className="birth-time-source-list">
{birthTimeSourceOptions.map((option) => (
<label
className={`birth-time-source-option ${source === option.value ? "is-selected" : ""}`}
key={option.value}
>
<input
checked={source === option.value}
name={`birth-time-source-${groupId}`}
type="radio"
value={option.value}
onChange={() => onPatch({
birthTimeSource: option.value,
birthTimeStatus: "reported",
time: "",
...birthTimeSourceDefaults[option.value],
})}
/>
<span>
<b>{option.label}</b>
<small>{option.hint}</small>
</span>
</label>
))}
</div>
</fieldset>}
{usesClockTime && (
<div className="birth-time-detail-grid onboarding-card-reveal">
<label>
<span>{isConfirmed ? "当前排盘时间" : source === "approximate" ? "大概时间" : "记录时间"}</span>
<BirthClockSelect
key={`${source}-${value.reportedTime || value.time}`}
value={value.reportedTime || value.time}
onChange={(reportedTime) => onPatch({ reportedTime })}
/>
</label>
{source === "hospital_record" && (
<p className="birth-time-detail-note">系统会无感检查前后 2 分钟的上升与 D9 / D10 稳定性。</p>
)}
</div>
)}
</div>
);
}