Files
fusion/packages/dashboard/app/components/ReportActionMenu.tsx
gsxdsm 365874f7f9 FN-8406: consolidate Command Center report actions
Move the guided report flow to System while ensuring its menu reliably overlays dashboard content.

- Portal the shared ReportActionMenu with a defined dropdown/modal stacking scale.
- Make System the sole Command Center report home and retain Copy diagnostics as a local action.
- Update regression coverage, documentation, and the published package changeset.

Files changed:
 .changeset/fn-8406-report-home-zindex.md           |   7 ++
 docs/dashboard-guide.md                            |   4 +-
 .../dashboard/app/components/ReportActionMenu.css  |   5 +-
 .../dashboard/app/components/ReportActionMenu.tsx  |  72 ++++++++++++-
 .../components/__tests__/ReportActionMenu.test.tsx |  12 +++
 .../components/command-center/CommandCenter.css    |  32 ------
 .../components/command-center/CommandCenter.tsx    |  59 ++++-------
 .../__tests__/CommandCenter.test.tsx               |   8 +-
 .../__tests__/SystemControlsArea.test.tsx          |  94 +----------------
 .../command-center/areas/SystemControlsArea.tsx    | 112 ++++-----------------
 packages/dashboard/app/styles.css                  |  11 ++
 11 files changed, 144 insertions(+), 272 deletions(-)

Fusion-Task-Id: FN-8406

Fusion-Task-Lineage: 957a77b0-fb04-4024-9506-225fec562f5d

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-19 17:39:43 -07:00

85 lines
3.5 KiB
TypeScript

import { useCallback, useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { Bug, Lightbulb, LifeBuoy, MessageSquare } from "lucide-react";
import type { ReportActionType } from "@fusion/core";
import "./ReportActionMenu.css";
const actions: Array<{ type: ReportActionType; label: string; Icon: typeof Bug }> = [
{ type: "bug", label: "Report bug", Icon: Bug },
{ type: "feedback", label: "Send feedback", Icon: MessageSquare },
{ type: "idea", label: "Share idea", Icon: Lightbulb },
{ type: "help", label: "Get help", Icon: LifeBuoy },
];
/** Four guided entry points share the same report pipeline rather than issue textboxes. */
export function ReportActionMenu({ onSelect }: { onSelect: (action: ReportActionType) => void }) {
const [open, setOpen] = useState(false);
const [position, setPosition] = useState<{ top: number; left: number; right: number }>();
const triggerRef = useRef<HTMLButtonElement>(null);
const menuRef = useRef<HTMLDivElement>(null);
const reposition = useCallback(() => {
const rect = triggerRef.current?.getBoundingClientRect();
if (!rect) return;
setPosition({ top: rect.bottom, left: rect.left, right: window.innerWidth - rect.right });
}, []);
/*
FNXC:ReportPipeline 2026-07-19-14:00:
FN-8406 requires the shared Report menu to escape Command Center and Settings
scroll owners. Portal it to document.body and keep it fixed to the trigger so
the guided actions cannot be clipped beneath surrounding dashboard content.
*/
useEffect(() => {
if (!open) return;
reposition();
const handlePositionChange = () => reposition();
window.addEventListener("resize", handlePositionChange);
window.addEventListener("scroll", handlePositionChange, true);
return () => {
window.removeEventListener("resize", handlePositionChange);
window.removeEventListener("scroll", handlePositionChange, true);
};
}, [open, reposition]);
useEffect(() => {
if (!open) return;
const closeOnOutsideClick = (event: MouseEvent) => {
const target = event.target as Node;
if (triggerRef.current?.contains(target) || menuRef.current?.contains(target)) return;
setOpen(false);
};
const closeOnEscape = (event: KeyboardEvent) => {
if (event.key === "Escape") {
setOpen(false);
triggerRef.current?.focus();
}
};
document.addEventListener("mousedown", closeOnOutsideClick);
document.addEventListener("keydown", closeOnEscape);
return () => {
document.removeEventListener("mousedown", closeOnOutsideClick);
document.removeEventListener("keydown", closeOnEscape);
};
}, [open]);
const menu = open && position && typeof document !== "undefined"
? createPortal(
<div
ref={menuRef}
className="report-action-menu__list report-action-menu__list--portal"
role="menu"
style={{ top: position.top, left: position.left, right: position.right }}
>
{actions.map(({ type, label, Icon }) => <button className="report-action-menu__item" type="button" role="menuitem" key={type} onClick={() => { setOpen(false); onSelect(type); }}><Icon aria-hidden="true" />{label}</button>)}
</div>,
document.body,
)
: null;
return <div className="report-action-menu">
<button ref={triggerRef} className="btn btn-secondary" type="button" aria-haspopup="menu" aria-expanded={open} onClick={() => setOpen((value) => !value)}>Report</button>
{menu}
</div>;
}