fix(chart): keep zero-year chara and ephemeris dates apart
A zero-year Chara period is shown as 0 years instead of failing the whole chart. Switching ephemeris dates drops the previous day immediately and can be retried. Missing birth data links to chart settings.
This commit is contained in:
@@ -86,6 +86,9 @@ export const chartViewPeriodSchema = z.object({
|
||||
export const chartViewMahadashaSchema = chartViewPeriodSchema.extend({
|
||||
sign: z.string().nullable(),
|
||||
antardashas: z.array(chartViewPeriodSchema),
|
||||
// Present only for a Chara mahadasha whose engine duration is exactly 0.
|
||||
// It stays on the list, occupies no timeline, and has no antardashas.
|
||||
zeroYear: z.literal(true).optional(),
|
||||
});
|
||||
|
||||
export const chartViewDashaTrackSchema = z.object({
|
||||
|
||||
@@ -140,6 +140,10 @@ export const ANTARDASHA_DERIVED_NOTE = "分运按标准比例从大运边界推
|
||||
|
||||
export const CHARA_ANTARDASHA_DATE_NOTE = "分运时长来自引擎的十二等分;起止日期按大运边界推算,不是引擎单独返回的日历字段。";
|
||||
|
||||
export const CHARA_ZERO_YEAR_LABEL = "0 年";
|
||||
|
||||
export const CHARA_ZERO_YEAR_NOTE = "本段时长为 0,不展开";
|
||||
|
||||
export const WESTERN_UNAVAILABLE_COPY = "西洋盘这一栏要等回归黄道端点上线。现在还没有,先空着——不会拿印度盘的度数改写成回归黄道。";
|
||||
|
||||
export const QIZHENG_UNAVAILABLE_COPY = "七政四余这一栏要等宿度端点上线。现在还没有,先空着。";
|
||||
@@ -161,6 +165,8 @@ export const CHART_VIEW_COPY = {
|
||||
waitingVarga: "这一分盘还没拿到。",
|
||||
unauthenticated: "请先登录后再看星盘。",
|
||||
incomplete: "还没有可用来排盘的出生资料。把日期、时间和地点填进星盘资料后,打开就能看见盘。",
|
||||
profileEntryTitle: "还没有星盘资料",
|
||||
profileEntryAction: "去填写出生资料",
|
||||
profileQueryError: "星盘资料这会儿读不出来,我们已经记录下来了。",
|
||||
adoptedIncomplete: "已采用的出生时间缺了排盘要用的时区,这张盘先不能算。我们已经记录下来了。",
|
||||
timezoneFailure: "出生地的时区没有对上,这张盘先不能算。我们已经记录下来了。",
|
||||
|
||||
@@ -82,11 +82,19 @@ function isoDay(value: string): string {
|
||||
function addWeightedSpan(startIso: string, endIso: string, index: number, weights: readonly number[]): { start: string; end: string } {
|
||||
const start = Date.parse(`${startIso}T00:00:00Z`);
|
||||
const end = Date.parse(`${endIso}T00:00:00Z`);
|
||||
const total = weights.reduce((sum, weight) => sum + weight, 0);
|
||||
const before = weights.slice(0, index).reduce((sum, weight) => sum + weight, 0);
|
||||
const total = weights.reduce((sum, weight) => sum + (Number.isFinite(weight) ? weight : 0), 0);
|
||||
// A zero-year Chara mahadasha gives every antardasha weight 0. `??` does not
|
||||
// replace 0, so total is 0 and `before / total` is NaN. Never call
|
||||
// `Date#toISOString` on that — it throws RangeError and fails the whole chart.
|
||||
if (!Number.isFinite(start) || !Number.isFinite(end) || !(total > 0)) {
|
||||
return { start: startIso, end: startIso };
|
||||
}
|
||||
const before = weights.slice(0, index).reduce((sum, weight) => sum + (Number.isFinite(weight) ? weight : 0), 0);
|
||||
const weight = Number.isFinite(weights[index]) ? weights[index]! : 0;
|
||||
const span = end - start;
|
||||
const from = start + span * (before / total);
|
||||
const to = index === weights.length - 1 ? end : start + span * ((before + weights[index]!) / total);
|
||||
const to = index === weights.length - 1 ? end : start + span * ((before + weight) / total);
|
||||
if (!Number.isFinite(from) || !Number.isFinite(to)) return { start: startIso, end: startIso };
|
||||
return { start: new Date(from).toISOString().slice(0, 10), end: new Date(to).toISOString().slice(0, 10) };
|
||||
}
|
||||
|
||||
@@ -390,12 +398,16 @@ function charaTrack(charaPacket: Record<string, unknown> | null, birthDate: stri
|
||||
if (!row) return [];
|
||||
const sign = text(row.sign);
|
||||
const lord = text(row.lord) ?? sign ?? "—";
|
||||
const duration = finite(row.duration_years) ?? 1;
|
||||
const rawDuration = finite(row.duration_years);
|
||||
const duration = rawDuration === null ? 1 : rawDuration;
|
||||
const zeroYear = duration === 0;
|
||||
const start = cursor;
|
||||
const end = addYears(start, duration);
|
||||
cursor = end;
|
||||
const rawAntar = Array.isArray(row.antardashas) ? row.antardashas : [];
|
||||
const antardashas = rawAntar.length > 0
|
||||
const antardashas = zeroYear
|
||||
? []
|
||||
: rawAntar.length > 0
|
||||
? rawAntar.flatMap((antar, index) => {
|
||||
const item = record(antar);
|
||||
if (!item) return [];
|
||||
@@ -430,6 +442,7 @@ function charaTrack(charaPacket: Record<string, unknown> | null, birthDate: stri
|
||||
end,
|
||||
current: containsDay(start, end, asOf),
|
||||
derived: false,
|
||||
...(zeroYear ? { zeroYear: true as const } : {}),
|
||||
antardashas,
|
||||
}];
|
||||
});
|
||||
|
||||
@@ -19,7 +19,9 @@ export const EPHEMERIS_COPY = {
|
||||
transitsUnavailable: "这一天的行运还没拿到。",
|
||||
natalMissing: "还没有本命上升,相对本命这一列空着。",
|
||||
eventsHeading: "未来九十天",
|
||||
eventsPending: "换座和停滞还在取。",
|
||||
eventsUnavailable: "换座和停滞还没接上。这一页其余部分不受影响。",
|
||||
dateFailed: "这一天的星历没取到,点日期再试一次。",
|
||||
ask: "带这天去提问",
|
||||
loginTitle: "请先登录",
|
||||
loginBody: "登录后即可查看星历。",
|
||||
|
||||
@@ -3,12 +3,14 @@ import type { BillingPaneTab, MembershipPlanAlias } from "@/lib/membership";
|
||||
export const SETTINGS_QUERY_KEY = "settings";
|
||||
|
||||
export type SettingsQuery = {
|
||||
readonly pane: "billing";
|
||||
readonly pane: "billing" | "chart";
|
||||
readonly source: string | null;
|
||||
readonly plan: MembershipPlanAlias | null;
|
||||
readonly tab: BillingPaneTab | null;
|
||||
};
|
||||
|
||||
export const CHART_SETTINGS_HREF = "/?settings=chart";
|
||||
|
||||
const PLAN_ALIASES: ReadonlySet<string> = new Set(["trial", "monthly", "yearly"]);
|
||||
const BILLING_TABS: ReadonlySet<string> = new Set(["membership", "credits", "redeem", "orders"]);
|
||||
|
||||
@@ -18,7 +20,9 @@ function searchParams(search: string) {
|
||||
|
||||
export function parseSettingsQuery(search: string): SettingsQuery | null {
|
||||
const params = searchParams(search);
|
||||
if (params.get(SETTINGS_QUERY_KEY) !== "billing") return null;
|
||||
const pane = params.get(SETTINGS_QUERY_KEY);
|
||||
if (pane === "chart") return { pane: "chart", source: null, plan: null, tab: null };
|
||||
if (pane !== "billing") return null;
|
||||
const planRaw = params.get("plan");
|
||||
const tabRaw = params.get("tab");
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user