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 { 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 type { PivotConfig, PivotResult } from "../../shared/types";
import DynamicReportPanel from "./DynamicReportPanel"; import DynamicReportPanel from "./DynamicReportPanel";
import DynamicReportTable from "./DynamicReportTable"; import DynamicReportTable from "./DynamicReportTable";
@ -19,6 +19,19 @@ interface DynamicReportProps {
export default function DynamicReport({ config, result, onConfigChange, dateFrom, dateTo }: DynamicReportProps) { export default function DynamicReport({ config, result, onConfigChange, dateFrom, dateTo }: DynamicReportProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const [viewMode, setViewMode] = useState<ViewMode>("table"); 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; 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 ( return (
<div
className={
fullscreen
? "fixed inset-0 z-50 bg-[var(--background)] overflow-auto p-6"
: ""
}
>
<div className="flex gap-4 items-start"> <div className="flex gap-4 items-start">
{/* Content area */} {/* Content area */}
<div className="flex-1 min-w-0 space-y-4"> <div className="flex-1 min-w-0 space-y-4">
{/* View toggle */} {/* Toolbar */}
{hasConfig && ( <div className="flex items-center gap-1">
<div className="flex gap-1"> {hasConfig && viewButtons.map(({ mode, icon, label }) => (
{viewButtons.map(({ mode, icon, label }) => (
<button <button
key={mode} key={mode}
onClick={() => setViewMode(mode)} onClick={() => setViewMode(mode)}
@ -49,8 +68,16 @@ export default function DynamicReport({ config, result, onConfigChange, dateFrom
{label} {label}
</button> </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> </div>
)}
{/* Empty state */} {/* Empty state */}
{!hasConfig && ( {!hasConfig && (
@ -78,5 +105,6 @@ export default function DynamicReport({ config, result, onConfigChange, dateFrom
dateTo={dateTo} dateTo={dateTo}
/> />
</div> </div>
</div>
); );
} }

View file

@ -378,7 +378,9 @@
"viewChart": "Chart", "viewChart": "Chart",
"viewBoth": "Both", "viewBoth": "Both",
"noConfig": "Add fields to generate the report", "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": { "help": {
"title": "How to use Reports", "title": "How to use Reports",

View file

@ -378,7 +378,9 @@
"viewChart": "Graphique", "viewChart": "Graphique",
"viewBoth": "Les deux", "viewBoth": "Les deux",
"noConfig": "Ajoutez des champs pour générer le rapport", "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": { "help": {
"title": "Comment utiliser les Rapports", "title": "Comment utiliser les Rapports",