Add zoom and fullscreen controls to the meeting schedule table

Add zoom and fullscreen controls to the executive meeting schedule page, including zoom in/out buttons, a fullscreen toggle, and Escape key handling for exiting fullscreen. The heading height calculation has been removed and hardcoded to 0px.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 608173c1-6210-41cf-be7d-3a913963059f
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/jwG0a8d
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
riyadhafraa
2026-05-04 17:55:36 +00:00
parent 406f4ef693
commit 00bc3dd374
2 changed files with 64 additions and 26 deletions
@@ -43,6 +43,10 @@ import {
ChevronLeft,
ChevronRight,
Search,
Maximize,
Minimize2,
ZoomIn,
ZoomOut,
} from "lucide-react";
import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "@/components/ui/collapsible";
import { Button } from "@/components/ui/button";
@@ -1145,6 +1149,14 @@ function ScheduleSection({
// matched against title, attendee names/titles, location, notes, and
// any merge text on the row. Empty needle → pass-through.
const [searchQuery, setSearchQuery] = useState("");
const [tableZoom, setTableZoom] = useState(90);
const [isFullscreen, setIsFullscreen] = useState(false);
useEffect(() => {
if (!isFullscreen) return;
const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") setIsFullscreen(false); };
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [isFullscreen]);
// Reset whenever the active day changes — a search is "scoped to the
// currently visible date" per the spec, so jumping days clears it.
useEffect(() => {
@@ -2351,28 +2363,12 @@ function ScheduleSection({
[reorderRows],
);
// #267: Same dynamic-measurement trick as the page header — the title
// row wraps on narrow widths (heading + edit toggle + date picker), so
// we publish its live height so the table thead can sit flush below.
const headingRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
const heading = headingRef.current;
if (!heading) return;
const root = heading.closest<HTMLElement>("[data-em-root]");
if (!root) return;
const update = () => {
root.style.setProperty("--em-heading-h", `${heading.offsetHeight}px`);
};
update();
const ro = new ResizeObserver(update);
ro.observe(heading);
window.addEventListener("resize", update);
const root = document.querySelector<HTMLElement>("[data-em-root]");
if (root) root.style.setProperty("--em-heading-h", "0px");
return () => {
ro.disconnect();
window.removeEventListener("resize", update);
// Reset so other sections (which don't render this heading) don't
// inherit a stale offset for any sticky descendants.
root.style.setProperty("--em-heading-h", "0px");
if (root) root.style.setProperty("--em-heading-h", "0px");
};
}, []);
@@ -2478,6 +2474,35 @@ function ScheduleSection({
clearLabel={t("executiveMeetings.schedule.searchClear")}
isRtl={isRtl}
/>
<div className="flex items-center gap-0.5 border border-gray-300 rounded-md bg-white">
<button
type="button"
onClick={() => setTableZoom((z) => Math.max(50, z - 10))}
disabled={tableZoom <= 50}
className="inline-flex items-center justify-center w-7 h-7 text-[#0B1E3F] hover:bg-gray-100 rounded-s-md disabled:opacity-30 transition-colors"
title={`${tableZoom - 10}%`}
>
<ZoomOut className="w-3.5 h-3.5" />
</button>
<span className="text-[11px] font-medium text-[#0B1E3F] w-9 text-center tabular-nums select-none">{tableZoom}%</span>
<button
type="button"
onClick={() => setTableZoom((z) => Math.min(150, z + 10))}
disabled={tableZoom >= 150}
className="inline-flex items-center justify-center w-7 h-7 text-[#0B1E3F] hover:bg-gray-100 rounded-e-md disabled:opacity-30 transition-colors"
title={`${tableZoom + 10}%`}
>
<ZoomIn className="w-3.5 h-3.5" />
</button>
</div>
<button
type="button"
onClick={() => setIsFullscreen((f) => !f)}
className="inline-flex items-center justify-center rounded-md border border-gray-300 bg-white text-[#0B1E3F] w-8 h-8 hover:bg-gray-50 transition-colors focus:outline-none focus:ring-2 focus:ring-[#0B1E3F]/40"
title={isFullscreen ? "Exit fullscreen" : "Fullscreen"}
>
{isFullscreen ? <Minimize2 className="w-4 h-4" /> : <Maximize className="w-4 h-4" />}
</button>
<label className="flex items-center gap-2 text-sm">
<span className="text-muted-foreground">{t("executiveMeetings.date")}</span>
<input
@@ -2491,14 +2516,26 @@ function ScheduleSection({
);
return (
<div className="space-y-4 print:space-y-2">
<div className={
isFullscreen
? "fixed inset-0 z-[9999] bg-[#f4f6fb] overflow-auto p-4 print:static print:p-0 print:overflow-visible"
: "space-y-4 print:space-y-2"
}>
{portalTarget && createPortal(toolbarControls, portalTarget)}
<div
ref={headingRef}
data-testid="em-schedule-heading-bar"
className="print:hidden"
style={{ top: "var(--em-header-h, 0px)" }}
/>
{isFullscreen && (
<div className="flex items-center justify-between mb-3 print:hidden">
<h2 className="text-lg font-bold text-[#0B1E3F]">{t("executiveMeetings.scheduleHeading")} {date}</h2>
<button
type="button"
onClick={() => setIsFullscreen(false)}
className="inline-flex items-center gap-1.5 rounded-md border border-gray-300 bg-white text-[#0B1E3F] px-3 py-1.5 text-sm hover:bg-gray-50 transition-colors"
>
<Minimize2 className="w-4 h-4" />
ESC
</button>
</div>
)}
<div className="hidden print:block text-center mb-2">
<div className="font-bold text-lg">{t("executiveMeetings.title")}</div>
@@ -2666,6 +2703,7 @@ function ScheduleSection({
ref={tableWrapperRef}
className="bg-white border-2 border-[#0B1E3F] rounded-md overflow-x-auto shadow-sm print:shadow-none print:border-black print:overflow-hidden"
id="executive-schedule-printable"
style={{ transform: `scale(${tableZoom / 100})`, transformOrigin: isRtl ? "top right" : "top left" }}
>
<table
className="border-collapse text-sm md:text-[15px] w-full table-auto xl:table-fixed print:table-fixed"
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB