Files
hope-events/frontend/src/components/reports/ReportViewerModal.tsx
T
joshuaandClaude Sonnet 5 424141f216 Fix Reports mobile layout overflow and desktop date range overflow
Report popup buttons and the page's search/Back controls overflowed
off-screen on mobile instead of wrapping, and the sidebar's custom
date range inputs spilled outside the filter box on desktop.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 14:57:09 +02:00

92 lines
3.7 KiB
TypeScript

"use client";
import React from "react";
import { X, Info, RefreshCw, type LucideIcon } from "lucide-react";
export default function ReportViewerModal({
title, description, icon: Icon, onClose, actions, filters, onRefresh, busy, children,
}: {
title: string;
description?: string;
icon?: LucideIcon;
onClose: () => void;
actions?: React.ReactNode;
filters?: React.ReactNode;
onRefresh?: () => void;
busy?: boolean;
children: React.ReactNode;
}) {
return (
// Above the site header (Navbar is `sticky top-0 z-50`) so the popup never sits behind it.
<div className="fixed inset-0 z-[60]">
<div className="absolute inset-0 bg-black/40" onClick={onClose} />
<div className="absolute inset-0 flex items-start justify-center p-4 overflow-auto">
<div className="w-full max-w-6xl bg-white rounded-xl shadow-xl my-8" onClick={e => e.stopPropagation()}>
<div className="border-b">
<div className="flex items-start justify-between gap-3 sm:gap-4 px-5 pt-4 pb-4">
<div className="flex items-start gap-3 min-w-0">
{Icon && (
<div className="w-11 h-11 rounded-xl bg-indigo-50 flex items-center justify-center shrink-0">
<Icon className="w-5 h-5 text-indigo-600" />
</div>
)}
<div className="min-w-0">
<h2 className="text-lg font-semibold text-gray-900">{title}</h2>
{description && <p className="text-sm text-gray-500 mt-0.5">{description}</p>}
</div>
</div>
<div className="hidden sm:flex items-center gap-2 flex-wrap shrink-0 ml-auto">
{actions}
<button className="p-2 rounded-lg hover:bg-gray-100 ml-1" onClick={onClose} aria-label="Close">
<X className="w-5 h-5 text-gray-500" />
</button>
</div>
<button className="sm:hidden p-2 rounded-lg hover:bg-gray-100 shrink-0" onClick={onClose} aria-label="Close">
<X className="w-5 h-5 text-gray-500" />
</button>
</div>
{actions && (
<div className="flex sm:hidden items-center gap-2 flex-wrap px-5 pb-4">
{actions}
</div>
)}
</div>
<div className="flex flex-wrap items-center justify-between gap-3 px-5 py-3 border-b bg-indigo-50/50">
<div className="flex items-center gap-2 text-sm text-indigo-900 flex-1 min-w-0">
<Info className="w-4 h-4 text-indigo-400 shrink-0" />
{filters || <span>This report has no extra filters beyond Events and Date range in the sidebar.</span>}
</div>
{onRefresh && (
<button
onClick={onRefresh}
disabled={busy}
className="shrink-0 flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-lg bg-indigo-600 text-white hover:bg-indigo-700 disabled:opacity-50"
>
<RefreshCw className={"w-3.5 h-3.5 " + (busy ? "animate-spin" : "")} /> {busy ? "Loading…" : "Refresh"}
</button>
)}
</div>
<div className="p-5">
{children}
</div>
</div>
</div>
</div>
);
}
export function ReportActionButton({ icon: Icon, label, onClick }: { icon: LucideIcon; label: string; onClick: () => void }) {
return (
<button
type="button"
onClick={onClick}
className="flex items-center gap-1.5 px-3 py-2 text-sm rounded-lg border border-gray-200 text-gray-700 hover:bg-gray-50 whitespace-nowrap"
>
<Icon className="w-4 h-4 text-gray-500" />
{label}
</button>
);
}