feat: add fullscreen toggle to dynamic report

Overlay covers the full viewport while keeping the config panel
accessible. Press Escape or click the button to exit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
le king fu 2026-02-22 09:00:42 -05:00
parent 438b72cba2
commit bcf7f0a2d0
3 changed files with 66 additions and 34 deletions

View file

@ -1,6 +1,6 @@
import { useState } from "react";
import { useState, useCallback, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { Table, BarChart3, Columns } from "lucide-react";
import { Table, BarChart3, Columns, Maximize2, Minimize2 } from "lucide-react";
import type { PivotConfig, PivotResult } from "../../shared/types";
import DynamicReportPanel from "./DynamicReportPanel";
import DynamicReportTable from "./DynamicReportTable";
@ -19,6 +19,19 @@ interface DynamicReportProps {
export default function DynamicReport({ config, result, onConfigChange, dateFrom, dateTo }: DynamicReportProps) {
const { t } = useTranslation();
const [viewMode, setViewMode] = useState<ViewMode>("table");
const [fullscreen, setFullscreen] = useState(false);
const toggleFullscreen = useCallback(() => setFullscreen((prev) => !prev), []);
// Escape key exits fullscreen
useEffect(() => {
if (!fullscreen) return;
const handler = (e: KeyboardEvent) => {
if (e.key === "Escape") setFullscreen(false);
};
document.addEventListener("keydown", handler);
return () => document.removeEventListener("keydown", handler);
}, [fullscreen]);
const hasConfig = (config.rows.length > 0 || config.columns.length > 0) && config.values.length > 0;
@ -29,13 +42,19 @@ export default function DynamicReport({ config, result, onConfigChange, dateFrom
];
return (
<div className="flex gap-4 items-start">
{/* Content area */}
<div className="flex-1 min-w-0 space-y-4">
{/* View toggle */}
{hasConfig && (
<div className="flex gap-1">
{viewButtons.map(({ mode, icon, label }) => (
<div
className={
fullscreen
? "fixed inset-0 z-50 bg-[var(--background)] overflow-auto p-6"
: ""
}
>
<div className="flex gap-4 items-start">
{/* Content area */}
<div className="flex-1 min-w-0 space-y-4">
{/* Toolbar */}
<div className="flex items-center gap-1">
{hasConfig && viewButtons.map(({ mode, icon, label }) => (
<button
key={mode}
onClick={() => setViewMode(mode)}
@ -49,34 +68,43 @@ export default function DynamicReport({ config, result, onConfigChange, dateFrom
{label}
</button>
))}
<div className="flex-1" />
<button
onClick={toggleFullscreen}
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium bg-[var(--card)] border border-[var(--border)] text-[var(--foreground)] hover:bg-[var(--muted)] transition-colors"
title={fullscreen ? t("reports.pivot.exitFullscreen") : t("reports.pivot.fullscreen")}
>
{fullscreen ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
{fullscreen ? t("reports.pivot.exitFullscreen") : t("reports.pivot.fullscreen")}
</button>
</div>
)}
{/* Empty state */}
{!hasConfig && (
<div className="bg-[var(--card)] border border-[var(--border)] rounded-xl p-12 text-center text-[var(--muted-foreground)]">
{t("reports.pivot.noConfig")}
</div>
)}
{/* Empty state */}
{!hasConfig && (
<div className="bg-[var(--card)] border border-[var(--border)] rounded-xl p-12 text-center text-[var(--muted-foreground)]">
{t("reports.pivot.noConfig")}
</div>
)}
{/* Table */}
{hasConfig && (viewMode === "table" || viewMode === "both") && (
<DynamicReportTable config={config} result={result} />
)}
{/* Table */}
{hasConfig && (viewMode === "table" || viewMode === "both") && (
<DynamicReportTable config={config} result={result} />
)}
{/* Chart */}
{hasConfig && (viewMode === "chart" || viewMode === "both") && (
<DynamicReportChart config={config} result={result} />
)}
{/* Chart */}
{hasConfig && (viewMode === "chart" || viewMode === "both") && (
<DynamicReportChart config={config} result={result} />
)}
</div>
{/* Side panel */}
<DynamicReportPanel
config={config}
onChange={onConfigChange}
dateFrom={dateFrom}
dateTo={dateTo}
/>
</div>
{/* Side panel */}
<DynamicReportPanel
config={config}
onChange={onConfigChange}
dateFrom={dateFrom}
dateTo={dateTo}
/>
</div>
);
}

View file

@ -378,7 +378,9 @@
"viewChart": "Chart",
"viewBoth": "Both",
"noConfig": "Add fields to generate the report",
"noData": "No data for this configuration"
"noData": "No data for this configuration",
"fullscreen": "Full screen",
"exitFullscreen": "Exit full screen"
},
"help": {
"title": "How to use Reports",

View file

@ -378,7 +378,9 @@
"viewChart": "Graphique",
"viewBoth": "Les deux",
"noConfig": "Ajoutez des champs pour générer le rapport",
"noData": "Aucune donnée pour cette configuration"
"noData": "Aucune donnée pour cette configuration",
"fullscreen": "Plein écran",
"exitFullscreen": "Quitter plein écran"
},
"help": {
"title": "Comment utiliser les Rapports",