Files
Jyotisha/frontend/src/components/rectification-range-delivery.tsx
T
jesse-ux a266b6b727
Independent Staging Quality Gate / validate (push) Failing after 10m16s
Independent Staging Quality Gate / publish (push) Skipped
fix(rectification): invite another dated event instead of saying the pool is closed (BUG-689)
When the seven collect lines are closed, the range card still appears, but the copy asks for one more exact-day event of any kind instead of saying the questions are finished. A new dated event after delivery stales the snapshot so the session does not stay delivered.
2026-09-14 22:56:01 +08:00

150 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import type { RectificationCandidateResult } from "@/lib/rectification-candidate-result";
import {
RECTIFICATION_USER_COPY,
REPRESENTATIVE_MINUTE_DISCLAIMER,
rangeDeliveryCaptionWithoutClosedInvite,
rangeDeliveryEventCopy,
rangeDeliveryShowsOpenCollectInvite,
} from "@/lib/rectification-agentic/user-copy";
import type { RangeDeliveryProjection } from "@/lib/rectification-agentic/v9/divergence-panel";
import { RectificationVerificationReport } from "@/components/rectification-verification-report";
function columnHasTraits(column: RangeDeliveryProjection["columns"][number]): boolean {
return Boolean(column.traits.d9 || column.traits.d10 || column.traits.nakshatra.length > 0);
}
export function RectificationRangeDelivery({
result,
acceptingCandidateId,
readonly,
onAccept,
onTieBreak,
}: Readonly<{
result: RectificationCandidateResult;
acceptingCandidateId: string | null;
readonly: boolean;
onAccept: (candidateId: string) => void;
onTieBreak?: () => void;
}>) {
const delivery: RangeDeliveryProjection | null = result.rangeDelivery;
const range = delivery?.range ?? result.credibleRange;
const eventCount = delivery?.event_count ?? 0;
const selected = result.selectedTime;
const busy = Boolean(acceptingCandidateId) || readonly;
const columns = delivery?.columns ?? [];
const title = range
? eventCount > 0
? `${RECTIFICATION_USER_COPY.rangeDeliveryTitle} ${range[0]}${range[1]}${rangeDeliveryEventCopy(eventCount)}`
: `${RECTIFICATION_USER_COPY.rangeDeliveryTitle} ${range[0]}${range[1]}`
: RECTIFICATION_USER_COPY.rangeDeliveryTitle;
const markdown = delivery?.verification_markdown
?? result.verificationReportMarkdown;
const sharedTraits = delivery?.shared_traits ?? [];
const showOpenCollectInvite = rangeDeliveryShowsOpenCollectInvite({
hint: delivery?.narrow_hint,
columns,
});
const caption = showOpenCollectInvite || delivery?.narrow_hint?.includes(
RECTIFICATION_USER_COPY.rangeDeliveryCollectClosed,
)
? rangeDeliveryCaptionWithoutClosedInvite(delivery?.narrow_hint)
: delivery?.narrow_hint ?? null;
return (
<section className="rectification-candidates rectification-range-delivery" aria-label="生时校正区间">
<div className="rectification-candidates-heading">
<strong>{title}</strong>
</div>
{caption ? (
<p className="rectification-range-delivery__narrow">{caption}</p>
) : null}
{showOpenCollectInvite ? (
<p className="rectification-range-delivery__invite">
{RECTIFICATION_USER_COPY.rangeDeliveryCollectClosed}
</p>
) : null}
{delivery?.tie_break_available && onTieBreak && !readonly ? (
<button
type="button"
className="rectification-range-delivery__tie-break"
disabled={busy}
onClick={onTieBreak}
>
{RECTIFICATION_USER_COPY.rangeDeliveryTieBreakEntry}
</button>
) : null}
{delivery?.tie_break_note ? (
<p className="rectification-range-delivery__narrow">{delivery.tie_break_note}</p>
) : null}
{sharedTraits.length > 0 ? (
<ul className="rectification-range-delivery__shared">
{sharedTraits.map((line) => (
<li key={line}>{line}</li>
))}
</ul>
) : null}
<ul className="rectification-range-delivery__columns">
{columns.map((column) => {
const adopted = selected === column.time;
const adopting = acceptingCandidateId === column.candidate_id;
const missing = column.fit == null || column.windows == null;
return (
<li
key={column.candidate_id}
className={`rectification-range-delivery__column${adopted ? " is-adopted" : ""}`}
>
<div className="rectification-range-delivery__time">{column.time}</div>
<p className="rectification-range-delivery__likelihood">
{RECTIFICATION_USER_COPY.rangeDeliveryRelativeLikelihood}
{" "}
{column.probability_percent}%
</p>
{columnHasTraits(column) ? (
<div className="rectification-range-delivery__block">
{column.traits.d9 ? <p>{column.traits.d9}</p> : null}
{column.traits.d10 ? <p>{column.traits.d10}</p> : null}
{column.traits.nakshatra.map((line) => (
<p key={line}>{line}</p>
))}
</div>
) : null}
{missing ? (
<p className="rectification-range-delivery__muted">
{RECTIFICATION_USER_COPY.rangeDeliveryNotCompared}
</p>
) : (
<>
<p>{column.fit_line}</p>
<p>{column.window_line}</p>
</>
)}
<button
type="button"
className="rectification-range-delivery__accept"
aria-pressed={adopted}
disabled={busy || adopted}
onClick={() => onAccept(column.candidate_id)}
>
{adopting
? RECTIFICATION_USER_COPY.rangeDeliveryAdopting
: adopted
? RECTIFICATION_USER_COPY.rangeDeliveryAdopted
: RECTIFICATION_USER_COPY.rangeDeliveryMoreLikeThis}
</button>
</li>
);
})}
</ul>
{delivery?.more_label ? (
<p className="rectification-range-delivery__more">{delivery.more_label}</p>
) : null}
<p className="rectification-range-delivery__boundary">
{delivery?.boundary ?? REPRESENTATIVE_MINUTE_DISCLAIMER}
</p>
<RectificationVerificationReport markdown={markdown} />
</section>
);
}