mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 10:45:44 +00:00
feat(reports): header actions, single export dialog, date-range presets
- ReportPage drops its own PageHeader (and the back arrow); ReportView now optionally renders the header itself (pageHeader prop) with export/refresh as its actions. Embedded ReportSection usage is unaffected (keeps the inline toolbar next to filters). - Replace the two xlsx/pdf icon buttons with one Export button opening a dialog: format as large icon radio cards, fields as checkboxes (select-all toggle), record count (default all, capped per format). Export applies the report's current filters and sort. - Backend: export route accepts fields (whitelisted against the report's own columns) and limit; ReportExportService takes an optional column subset instead of always dumping every column. - Fixed a real bug found while wiring this up: runAll() ignored the caller's sortBy/sortOrder and always used the report's default sort, so exports silently didn't match whatever order was on screen. - Report daterange filters now use DatePickerInput + the shared getDateRangePresets() (Today/Last 7 days/This month/...) instead of two bare DateInputs, matching every other date-range filter in the app. - Removed the reports hub grid page. /dashboard/reports now redirects to the first report the caller has access to, or /dashboard if they have none.
This commit is contained in:
@@ -1,23 +1,14 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
import { ReportView } from "@/components/reports/ReportView";
|
||||
import { PageContainer, PageHeader } from "@/components/page";
|
||||
import { api } from "@/services/api";
|
||||
import { PageContainer } from "@/components/page";
|
||||
|
||||
export default function ReportPage() {
|
||||
const { reportKey = "" } = useParams<{ reportKey: string }>();
|
||||
const { data: catalog } = useQuery(api.reports.catalog.queryOptions());
|
||||
const def = catalog?.find((r) => r.key === reportKey);
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageHeader
|
||||
title={def?.title ?? "Report"}
|
||||
subtitle={def?.description}
|
||||
backTo="/dashboard/reports"
|
||||
/>
|
||||
<ReportView reportKey={reportKey} />
|
||||
<ReportView reportKey={reportKey} pageHeader />
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import { Alert, Card, SimpleGrid, Skeleton, Stack, Text, Title } from "@mantine/core";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { PageContainer, PageHeader } from "@/components/page";
|
||||
import { api } from "@/services/api";
|
||||
import type { ReportCatalogEntry } from "@/types/reports";
|
||||
|
||||
const GROUP_ORDER: ReportCatalogEntry["group"][] = ["Commercial", "Operations", "Finance"];
|
||||
|
||||
export default function ReportsHubPage() {
|
||||
const navigate = useNavigate();
|
||||
const { data: catalog, isLoading, isError } = useQuery(api.reports.catalog.queryOptions());
|
||||
|
||||
const groups = GROUP_ORDER.map((group) => ({
|
||||
group,
|
||||
reports: (catalog ?? []).filter((r) => r.group === group),
|
||||
})).filter((g) => g.reports.length);
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageHeader title="Reports" subtitle="Every report you have access to, grouped by area." />
|
||||
|
||||
{isError ? <Alert color="red">Failed to load the report catalog.</Alert> : null}
|
||||
|
||||
{isLoading ? (
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="md">
|
||||
{Array.from({ length: 6 }).map((_, i) => (
|
||||
<Skeleton key={i} height={96} radius="md" />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
) : null}
|
||||
|
||||
{!isLoading && !isError && !groups.length ? (
|
||||
<Text c="dimmed">You don't have access to any reports yet.</Text>
|
||||
) : null}
|
||||
|
||||
{groups.map(({ group, reports }) => (
|
||||
<Stack key={group} gap="sm">
|
||||
<Title order={4}>{group}</Title>
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="md">
|
||||
{reports.map((report) => (
|
||||
<Card
|
||||
key={report.key}
|
||||
withBorder
|
||||
radius="md"
|
||||
p="md"
|
||||
className="cursor-pointer transition-colors hover:bg-gray-50"
|
||||
onClick={() => navigate(`/dashboard/reports/${report.key}`)}
|
||||
>
|
||||
<Text fw={600} c="edr-text">
|
||||
{report.title}
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" mt={4}>
|
||||
{report.description}
|
||||
</Text>
|
||||
</Card>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
))}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Navigate } from "react-router-dom";
|
||||
|
||||
import { api } from "@/services/api";
|
||||
|
||||
/**
|
||||
* `/dashboard/reports` has no page of its own — it forwards to the first
|
||||
* report the caller has access to (catalog order = registration order,
|
||||
* already permission-filtered server-side), or home if they have none.
|
||||
*/
|
||||
export default function ReportsIndexRedirect() {
|
||||
const { data: catalog, isLoading } = useQuery(api.reports.catalog.queryOptions());
|
||||
|
||||
if (isLoading) return null;
|
||||
const first = catalog?.[0];
|
||||
return <Navigate to={first ? `/dashboard/reports/${first.key}` : "/dashboard"} replace />;
|
||||
}
|
||||
Reference in New Issue
Block a user