From aeb6852aa7dff86fa6eca673685661baa0661f08 Mon Sep 17 00:00:00 2001 From: 732642856 <732642856@qq.com> Date: Fri, 17 Jul 2026 18:42:39 +0800 Subject: [PATCH] Persist local synastry report history --- frontend/src/app/globals.css | 4 +++ frontend/src/app/page.tsx | 41 +++++++++++++++++++++-- tests/test_supabase_user_data_contract.py | 3 ++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 779e7f45..9284a830 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -397,6 +397,10 @@ button:disabled { cursor: default; opacity: .45; } .synastry-report-card strong, .synastry-report-card p { color: var(--color-ink); } .synastry-report-card strong { display: block; margin-top: 4px; font-size: var(--type-body); font-weight: 500; } .synastry-report-card p, .synastry-report-card ul { margin: 0; } +.synastry-history-list { display: grid; gap: var(--space-2); } +.synastry-history-list > b { color: var(--color-ink); font-size: var(--type-caption); font-weight: 600; } +.synastry-history-item { display: grid; gap: 3px; padding: var(--space-3); border: 1px solid var(--color-border); border-radius: var(--radius-md); background: var(--color-canvas); color: var(--color-ink); text-align: left; } +.synastry-history-item small { color: var(--color-ink-secondary); font-size: var(--type-caption); } input, select { width: 100%; min-height: 44px; padding: 0 12px; border: 1px solid var(--color-border-strong); color: var(--color-ink); border-color: var(--color-border-strong); border-radius: var(--radius-md); background: var(--color-canvas); font-size: 14px; } input:disabled, select:disabled { color: var(--color-ink-tertiary); background: var(--color-canvas-muted); } diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 8b015c25..ed7c546c 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -46,6 +46,7 @@ type ChartLibraryApiRecord = { updated_at?: string; }; type SynastryReportCard = { + id: string; partnerName: string; score?: number; maxScore?: number; @@ -55,6 +56,7 @@ type SynastryReportCard = { strengths?: string[]; risks?: string[]; nextEvidence?: string[]; + createdAt: number; }; type ChatSession = { id: string; title: string; theme: Theme; modelId: string; messages: Message[]; updatedAt: number }; type RequestError = { sessionId: string; message: string }; @@ -192,6 +194,9 @@ function selectedBirthPlace(profile: Profile): BirthPlace | null { function chartLibraryStorageKey(accountId: string) { return `jyotisha_chart_library:${accountId}`; } +function synastryHistoryStorageKey(accountId: string) { + return `jyotisha_synastry_history:${accountId}`; +} function profileReadyForLibrary(profile: Profile) { return !missingProfileStep(profile); @@ -215,6 +220,14 @@ function readChartLibrary(accountId: string): ChartLibraryRecord[] { return []; } } +function readSynastryHistory(accountId: string): SynastryReportCard[] { + try { + const parsed = JSON.parse(localStorage.getItem(synastryHistoryStorageKey(accountId)) || "[]") as SynastryReportCard[]; + return Array.isArray(parsed) ? parsed.filter((record) => record?.id && record?.partnerName).slice(0, 10) : []; + } catch { + return []; + } +} function normalizeChartLibraryApiRecord(record: ChartLibraryApiRecord): ChartLibraryRecord { return { @@ -531,6 +544,7 @@ export default function Home() { const [chartLibraryOpen, setChartLibraryOpen] = useState(false); const [otherProfileDraft, setOtherProfileDraft] = useState(emptyProfile); const [synastryReportCard, setSynastryReportCard] = useState(null); + const [synastryHistory, setSynastryHistory] = useState([]); const [profileOpen, setProfileOpen] = useState(false); const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false); const [profileNotice, setProfileNotice] = useState(""); @@ -599,12 +613,14 @@ export default function Home() { useEffect(() => { if (!accountId) { setChartLibrary([]); + setSynastryHistory([]); chartLibraryLoadedAccount.current = ""; return; } if (chartLibraryLoadedAccount.current === accountId) return; chartLibraryLoadedAccount.current = accountId; setChartLibrary(upsertSelfChart(readChartLibrary(accountId), profile)); + setSynastryHistory(readSynastryHistory(accountId)); void fetchCloudChartLibrary() .then((cloudLibrary) => { setChartLibrary((current) => { @@ -1274,7 +1290,8 @@ export default function Home() { const max = payload.synastry?.max_score; const assessment = payload.synastry?.assessment; const layers = (payload.evidenceLayers || []).join(" / ") || "Ashtakoot / Moon / D9"; - setSynastryReportCard({ + const reportCard: SynastryReportCard = { + id: `${record.id}-${Date.now()}`, partnerName: record.profile.name || "对方", score, maxScore: max, @@ -1284,7 +1301,16 @@ export default function Home() { strengths: payload.relationshipReport?.strengths, risks: payload.relationshipReport?.risks, nextEvidence: payload.relationshipReport?.nextEvidence, - }); + createdAt: Date.now(), + }; + setSynastryReportCard(reportCard); + if (accountId) { + setSynastryHistory((current) => { + const next = [reportCard, ...current].slice(0, 10); + localStorage.setItem(synastryHistoryStorageKey(accountId), JSON.stringify(next)); + return next; + }); + } chooseSuggestedQuestion([ baseQuestion, "", @@ -2024,6 +2050,17 @@ export default function Home() { )} + {synastryHistory.length > 0 && ( +
+ 合盘历史 + {synastryHistory.slice(0, 5).map((item) => ( + + ))} +
+ )}
添加其他星盘用于合盘、亲友盘或客户盘。
diff --git a/tests/test_supabase_user_data_contract.py b/tests/test_supabase_user_data_contract.py index 87083c01..97c6e842 100644 --- a/tests/test_supabase_user_data_contract.py +++ b/tests/test_supabase_user_data_contract.py @@ -144,7 +144,10 @@ def test_chart_profile_library_has_cloud_table_api_and_local_fallback() -> None: "buildSynastryQuestion", "draftSynastryQuestionFromChart", "synastryReportCard", + "synastryHistory", + "synastryHistoryStorageKey", "synastry-report-card", + "synastry-history-list", 'fetch("/api/synastry"', "Ashtakoot", "chartLibraryStorageKey",