hide report PDF action
Staging Backend Quality Gate / validate (push) Successful in 21m7s
Staging Backend Quality Gate / publish (push) Successful in 12m9s

This commit is contained in:
Jesse_Chen
2026-08-09 23:20:36 +08:00
parent a77fb514cb
commit ac0b218c90
3 changed files with 11 additions and 82 deletions
@@ -169,7 +169,7 @@ export function PersonalReportPage({ reportId }: { reportId: string }) {
</p>
{generating && (
<p className="max-w-md text-sm text-ink-secondary">
</p>
)}
</main>
@@ -242,11 +242,7 @@ export function PersonalReportPage({ reportId }: { reportId: string }) {
const document = state.document;
return (
<main className="min-h-svh bg-canvas pb-12">
<ReportActions
reportId={document.reportId}
ready
reportTitle={`${document.subject.displayName} · 个人报告`}
/>
<ReportActions />
<div className="px-4 pt-6">
<PersonalReportDocumentView document={document} />
</div>
@@ -1,66 +1,16 @@
/**
* Header action bar for the personal report page.
*
* The print button stays disabled until the report document is ready; printing
* goes through client-report-export (window.print only, no server PDF).
* Environments that cannot print reliably (WeChat in-app browser) get a
* friendly hint instead of a broken printout.
*/
"use client";
import { useState } from "react";
/** Header action bar for the personal report page. */
import Link from "next/link";
import { Printer, ArrowLeft, TriangleAlert } from "lucide-react";
import {
detectPrintRestriction,
isPrintSupported,
printPersonalReport,
safeReportFilename,
} from "@/lib/client-report-export";
import { Button } from "@/components/ui/button";
interface ReportActionsProps {
reportId: string;
ready: boolean;
reportTitle?: string;
}
export function ReportActions({ reportId, ready, reportTitle }: ReportActionsProps) {
const [busy, setBusy] = useState(false);
const [notice, setNotice] = useState<string | null>(null);
const restriction = detectPrintRestriction();
const disabled = !ready || busy || !isPrintSupported();
async function handlePrint() {
if (disabled) {
return;
}
if (restriction.restricted) {
setNotice(restriction.message ?? null);
return;
}
setBusy(true);
setNotice(null);
try {
await printPersonalReport({
title: reportTitle?.trim() || safeReportFilename(reportId),
});
} finally {
setBusy(false);
}
}
import { ArrowLeft } from "lucide-react";
export function ReportActions() {
return (
<nav
aria-label="报告操作"
className="personal-report-screen-only sticky top-0 z-10 border-b border-border bg-canvas/95 px-4 py-3 backdrop-blur"
>
<div className="mx-auto flex max-w-4xl flex-wrap items-center justify-between gap-3">
<div className="mx-auto flex max-w-4xl items-center">
<Link
href="/"
className="inline-flex items-center gap-1.5 text-sm text-ink-secondary transition-colors hover:text-ink"
@@ -68,24 +18,7 @@ export function ReportActions({ reportId, ready, reportTitle }: ReportActionsPro
<ArrowLeft aria-hidden="true" className="size-4" />
</Link>
<div className="flex flex-wrap items-center gap-3">
{notice !== null && (
<span className="inline-flex items-center gap-1.5 text-sm text-warning" role="status">
<TriangleAlert aria-hidden="true" className="size-4" />
{notice}
</span>
)}
<Button type="button" variant="default" size="sm" onClick={handlePrint} disabled={disabled}>
<Printer aria-hidden="true" />
{busy ? "正在准备打印…" : "打印 / 保存为 PDF"}
</Button>
</div>
</div>
{!ready && (
<p className="mx-auto mt-2 max-w-4xl text-xs text-ink-tertiary">
</p>
)}
</nav>
);
}
+5 -5
View File
@@ -227,13 +227,13 @@ test("canonical contract rejects malformed documents", () => {
assert.equal(safeParseReportDocument(badRefs).ok, false, "dangling evidenceRefs must fail guards");
});
test("print button is gated on ready state (source contract)", () => {
test("PDF action is hidden while the export format is unavailable", () => {
const actionsSource = readFileSync(
new URL("../src/components/personal-report/report-actions.tsx", import.meta.url),
"utf8",
);
assert.match(actionsSource, /const disabled = !ready \|\| busy \|\| !isPrintSupported\(\)/);
assert.match(actionsSource, /disabled=\{disabled\}/);
assert.match(actionsSource, /printPersonalReport/);
assert.match(actionsSource, /personal-report-screen-only/);
assert.match(actionsSource, /返回对话/);
assert.doesNotMatch(actionsSource, /打印 \/ 保存为 PDF/);
assert.doesNotMatch(actionsSource, /printPersonalReport/);
assert.doesNotMatch(actionsSource, /Printer/);
});