fix(web): paint the chart page shell before engine calls
BUG-715/716/717: typed engine failures with logs and split copy, natal-only open, on-demand tabs, 10s timeout, in-memory cache, no speed-promise eyebrow.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { ChartViewOk } from "@/lib/chart-view-contract";
|
||||
import { CHART_VIEW_COPY } from "@/lib/chart-view-labels";
|
||||
|
||||
function Track({
|
||||
title,
|
||||
@@ -41,12 +42,24 @@ function Track({
|
||||
);
|
||||
}
|
||||
|
||||
export function ChartDashaTab({ view }: { view: ChartViewOk }) {
|
||||
export function ChartDashaTab({
|
||||
view,
|
||||
charaPending = false,
|
||||
}: {
|
||||
view: ChartViewOk;
|
||||
charaPending?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="chart-page-dasha">
|
||||
<div className="chart-page-dasha-columns">
|
||||
<Track {...view.dasha.vimshottari} />
|
||||
<Track {...view.dasha.chara} />
|
||||
{charaPending ? (
|
||||
<section className="chart-page-dasha-track">
|
||||
<p className="chart-page-note">{CHART_VIEW_COPY.waitingLayer}</p>
|
||||
</section>
|
||||
) : (
|
||||
<Track {...view.dasha.chara} />
|
||||
)}
|
||||
</div>
|
||||
<p className="chart-page-boundary">{view.dasha.dualTrackNote}</p>
|
||||
</div>
|
||||
|
||||
@@ -4,28 +4,60 @@ import Link from "next/link";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { useChartPage } from "@/hooks/use-chart-page";
|
||||
import {
|
||||
CHART_VIEW_TABS,
|
||||
type ChartViewResponse,
|
||||
type ChartViewTabId,
|
||||
} from "@/lib/chart-view-contract";
|
||||
import type { ChartViewLayer } from "@/lib/chart-view-engine";
|
||||
import { CHART_VIEW_COPY } from "@/lib/chart-view-labels";
|
||||
import { ChartBasicsTab } from "./chart-basics-tab";
|
||||
import { ChartDashaTab } from "./chart-dasha-tab";
|
||||
import { ChartQizhengTab } from "./chart-qizheng-tab";
|
||||
import { ChartVedicTab } from "./chart-vedic-tab";
|
||||
import { ChartWesternTab } from "./chart-western-tab";
|
||||
|
||||
export function ChartPageRoute() {
|
||||
const { view, pendingLayers, requestLayer } = useChartPage();
|
||||
return (
|
||||
<ChartPageView
|
||||
view={view}
|
||||
pendingLayers={pendingLayers}
|
||||
onNeedLayer={requestLayer}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function WaitingNote({ copy }: { copy: string }) {
|
||||
return <p className="chart-page-note">{copy}</p>;
|
||||
}
|
||||
|
||||
export function ChartPageView({
|
||||
view,
|
||||
pendingLayers,
|
||||
onNeedLayer,
|
||||
initialTab = "vedic",
|
||||
}: {
|
||||
view: ChartViewResponse;
|
||||
view: ChartViewResponse | null;
|
||||
pendingLayers?: ReadonlySet<ChartViewLayer>;
|
||||
onNeedLayer?: (layer: ChartViewLayer) => void;
|
||||
initialTab?: ChartViewTabId;
|
||||
}) {
|
||||
const [tab, setTab] = useState<ChartViewTabId>(initialTab);
|
||||
const [vargaId, setVargaId] = useState(
|
||||
view.status === "ok" ? view.vedic.vargas[0]?.id ?? "D1" : "D1",
|
||||
);
|
||||
const [vargaId, setVargaId] = useState("D1");
|
||||
|
||||
function selectTab(id: ChartViewTabId) {
|
||||
setTab(id);
|
||||
if (id === "dasha") onNeedLayer?.("chara");
|
||||
if (id === "western") onNeedLayer?.("western");
|
||||
if (id === "qizheng") onNeedLayer?.("qizheng");
|
||||
}
|
||||
|
||||
function selectVarga(id: string) {
|
||||
setVargaId(id);
|
||||
if (id !== "D1") onNeedLayer?.("varga");
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="chart-page-shell">
|
||||
@@ -36,16 +68,20 @@ export function ChartPageView({
|
||||
</Link>
|
||||
</header>
|
||||
|
||||
{view.status !== "ok" ? (
|
||||
{view == null ? (
|
||||
<section className="chart-page-hero">
|
||||
<h1>星盘</h1>
|
||||
<p>{CHART_VIEW_COPY.waitingChart}</p>
|
||||
</section>
|
||||
) : view.status !== "ok" ? (
|
||||
<section className="chart-page-hero">
|
||||
<p className="chart-page-eyebrow">直接计算 · 打开即有 · 不消耗点数</p>
|
||||
<h1>星盘</h1>
|
||||
<p>{view.message}</p>
|
||||
</section>
|
||||
) : (
|
||||
<>
|
||||
<section className="chart-page-hero">
|
||||
<p className="chart-page-eyebrow">直接计算 · 打开即有 · 不消耗点数</p>
|
||||
<p className="chart-page-eyebrow">{CHART_VIEW_COPY.eyebrow}</p>
|
||||
<h1>星盘</h1>
|
||||
<p className="chart-page-birthline">
|
||||
{view.profile.name} · {view.profile.date} {view.profile.time} · {view.profile.placeLabel}
|
||||
@@ -59,7 +95,7 @@ export function ChartPageView({
|
||||
role="tab"
|
||||
aria-selected={tab === item.id}
|
||||
className={tab === item.id ? "chart-page-tab is-current" : "chart-page-tab"}
|
||||
onClick={() => setTab(item.id)}
|
||||
onClick={() => selectTab(item.id)}
|
||||
>
|
||||
{item.label}
|
||||
</button>
|
||||
@@ -69,14 +105,28 @@ export function ChartPageView({
|
||||
{tab === "vedic" ? (
|
||||
<ChartVedicTab
|
||||
view={view}
|
||||
varga={view.vedic.vargas.find((item) => item.id === vargaId) ?? view.vedic.vargas[0]!}
|
||||
onSelectVarga={setVargaId}
|
||||
vargaId={vargaId}
|
||||
onSelectVarga={selectVarga}
|
||||
/>
|
||||
) : null}
|
||||
{tab === "basics" ? <ChartBasicsTab view={view} /> : null}
|
||||
{tab === "dasha" ? <ChartDashaTab view={view} /> : null}
|
||||
{tab === "western" ? <ChartWesternTab view={view} /> : null}
|
||||
{tab === "qizheng" ? <ChartQizhengTab view={view} /> : null}
|
||||
{tab === "dasha" ? (
|
||||
<ChartDashaTab view={view} charaPending={pendingLayers?.has("chara") ?? false} />
|
||||
) : null}
|
||||
{tab === "western" ? (
|
||||
pendingLayers?.has("western") ? (
|
||||
<WaitingNote copy={CHART_VIEW_COPY.waitingLayer} />
|
||||
) : (
|
||||
<ChartWesternTab view={view} />
|
||||
)
|
||||
) : null}
|
||||
{tab === "qizheng" ? (
|
||||
pendingLayers?.has("qizheng") ? (
|
||||
<WaitingNote copy={CHART_VIEW_COPY.waitingLayer} />
|
||||
) : (
|
||||
<ChartQizhengTab view={view} />
|
||||
)
|
||||
) : null}
|
||||
</section>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,76 +1,89 @@
|
||||
"use client";
|
||||
|
||||
import { VedicChartSvg } from "@/components/personal-report/vedic-chart-svg";
|
||||
import type { ChartViewOk, ChartViewVarga } from "@/lib/chart-view-contract";
|
||||
import type { ChartViewOk } from "@/lib/chart-view-contract";
|
||||
import { CHART_VIEW_COPY, VARGA_CHIP_ORDER } from "@/lib/chart-view-labels";
|
||||
import { formatDegree } from "./chart-format";
|
||||
import { ChartParamLines } from "./chart-param-lines";
|
||||
|
||||
export function ChartVedicTab({
|
||||
view,
|
||||
varga,
|
||||
vargaId,
|
||||
onSelectVarga,
|
||||
}: {
|
||||
view: ChartViewOk;
|
||||
varga: ChartViewVarga;
|
||||
vargaId: string;
|
||||
onSelectVarga: (id: string) => void;
|
||||
}) {
|
||||
const selected = view.vedic.vargas.find((item) => item.id === vargaId);
|
||||
const natal = view.vedic.vargas.find((item) => item.id === "D1") ?? view.vedic.vargas[0];
|
||||
const varga = selected ?? (vargaId === "D1" ? natal : undefined);
|
||||
const waiting = vargaId !== "D1" && !selected;
|
||||
|
||||
return (
|
||||
<div className="chart-page-vedic">
|
||||
<div className="chart-page-varga-chips" role="tablist" aria-label="分盘">
|
||||
{view.vedic.vargas.map((item) => (
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={item.id === varga.id}
|
||||
className={item.id === varga.id ? "chart-page-chip is-current" : "chart-page-chip"}
|
||||
onClick={() => onSelectVarga(item.id)}
|
||||
>
|
||||
{item.id}
|
||||
</button>
|
||||
))}
|
||||
{VARGA_CHIP_ORDER.map((division) => {
|
||||
const id = `D${division}`;
|
||||
return (
|
||||
<button
|
||||
key={id}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={id === vargaId}
|
||||
className={id === vargaId ? "chart-page-chip is-current" : "chart-page-chip"}
|
||||
onClick={() => onSelectVarga(id)}
|
||||
>
|
||||
{id}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="chart-page-vedic-layout">
|
||||
<div className="chart-page-vedic-stage">
|
||||
<VedicChartSvg chart={varga.chart} ariaLabel={`${varga.id} 北印度盘 ${varga.meaning}`} />
|
||||
<div className="chart-page-center-card">
|
||||
<div className="chart-page-center-compact">
|
||||
<ChartParamLines view={view} compact />
|
||||
</div>
|
||||
<div className="chart-page-center-extra">
|
||||
<ChartParamLines view={view} />
|
||||
{waiting || !varga ? (
|
||||
<p className="chart-page-note">{CHART_VIEW_COPY.waitingVarga}</p>
|
||||
) : (
|
||||
<div className="chart-page-vedic-layout">
|
||||
<div className="chart-page-vedic-stage">
|
||||
<VedicChartSvg chart={varga.chart} ariaLabel={`${varga.id} 北印度盘 ${varga.meaning}`} />
|
||||
<div className="chart-page-center-card">
|
||||
<div className="chart-page-center-compact">
|
||||
<ChartParamLines view={view} compact />
|
||||
</div>
|
||||
<div className="chart-page-center-extra">
|
||||
<ChartParamLines view={view} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="chart-page-params-below">
|
||||
<ChartParamLines view={view} />
|
||||
<p className="chart-page-boundary">{view.vedic.boundary}</p>
|
||||
</div>
|
||||
<div className="chart-page-planet-table-wrap">
|
||||
<table className="chart-page-planet-table">
|
||||
<caption>{varga.id} · {varga.meaning}</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">星体</th>
|
||||
<th scope="col">星座与度数</th>
|
||||
<th scope="col">宫位</th>
|
||||
<th scope="col">状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{view.vedic.planets.map((planet) => (
|
||||
<tr key={planet.name}>
|
||||
<th scope="row">{planet.label}</th>
|
||||
<td>{planet.signLabel} {formatDegree(planet.degreeInSign)}</td>
|
||||
<td>{planet.house}</td>
|
||||
<td>{planet.status}</td>
|
||||
<div className="chart-page-params-below">
|
||||
<ChartParamLines view={view} />
|
||||
<p className="chart-page-boundary">{view.vedic.boundary}</p>
|
||||
</div>
|
||||
<div className="chart-page-planet-table-wrap">
|
||||
<table className="chart-page-planet-table">
|
||||
<caption>{varga.id} · {varga.meaning}</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">星体</th>
|
||||
<th scope="col">星座与度数</th>
|
||||
<th scope="col">宫位</th>
|
||||
<th scope="col">状态</th>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
{view.vedic.planets.map((planet) => (
|
||||
<tr key={planet.name}>
|
||||
<th scope="row">{planet.label}</th>
|
||||
<td>{planet.signLabel} {formatDegree(planet.degreeInSign)}</td>
|
||||
<td>{planet.house}</td>
|
||||
<td>{planet.status}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="chart-page-boundary chart-page-boundary-desktop">{view.vedic.boundary}</p>
|
||||
)}
|
||||
{varga ? <p className="chart-page-boundary chart-page-boundary-desktop">{view.vedic.boundary}</p> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user