feat(FN-3786): add reports dashboard view and comparison tooling

Adds a Reports dashboard view (`ReportsView`) to the reports plugin with a full supporting stack of components (`ReportComparisonDrawer`, `ReportDetailPanel`, `ReportFiltersBar`, `ReportListItem`, `ReportEmptyState`), hooks (`useReports`, `useReportPreview`, `useReportSectionDiff`, `useViewportMode`

Fusion-Task-Id: FN-3786
This commit is contained in:
Fusion
2026-05-10 12:27:36 -07:00
committed by gsxdsm
parent 097653eceb
commit 43d3ac2c18
33 changed files with 753 additions and 6 deletions

View File

@@ -0,0 +1,72 @@
import type { PluginContext, PluginRouteDefinition, PluginRouteResponse } from "@fusion/core";
import { ReportStore } from "../store/report-store.js";
interface RouteRequest {
params: Record<string, string>;
query?: Record<string, string | string[] | undefined>;
}
const reportStoreCache = new WeakMap<object, ReportStore>();
function getStore(ctx: PluginContext): ReportStore {
const key = ctx.taskStore as object;
const cached = reportStoreCache.get(key);
if (cached) return cached;
const store = new ReportStore(ctx.taskStore.getDatabase());
reportStoreCache.set(key, store);
return store;
}
function badRequest(message: string): PluginRouteResponse {
return { status: 400, body: { error: message } };
}
export function createReportListRoutes(): PluginRouteDefinition[] {
return [
{
method: "GET",
path: "/reports",
handler: async (req: unknown, ctx: PluginContext): Promise<PluginRouteResponse> => {
const request = req as RouteRequest;
const query = request.query ?? {};
const cadence = typeof query.cadence === "string" && query.cadence.length > 0 ? query.cadence : undefined;
const status = typeof query.status === "string" && query.status.length > 0 ? query.status : undefined;
const periodStartFrom = typeof query.from === "string" && query.from.length > 0 ? query.from : undefined;
const periodStartTo = typeof query.to === "string" && query.to.length > 0 ? query.to : undefined;
const q = typeof query.q === "string" && query.q.length > 0 ? query.q.toLowerCase() : undefined;
const agent = typeof query.agentId === "string" && query.agentId.length > 0 ? query.agentId.toLowerCase() : undefined;
const store = getStore(ctx);
const reports = store.listReports({
cadence: cadence as never,
status: status as never,
periodStartFrom,
periodStartTo,
orderBy: "periodStart",
orderDir: "desc",
limit: 500,
});
const filtered = reports.filter((report) => {
if (q && !report.title.toLowerCase().includes(q)) return false;
if (agent) {
const agentIds = ((report.metadata?.agentIds as string[] | undefined) ?? []).map((id) => id.toLowerCase());
if (!agentIds.some((id) => id.includes(agent))) return false;
}
return true;
});
return { status: 200, body: { reports: filtered } };
},
},
{
method: "GET",
path: "/reports/:id",
handler: async (req: unknown, ctx: PluginContext): Promise<PluginRouteResponse> => {
const request = req as RouteRequest;
const report = getStore(ctx).getReport(request.params.id);
if (!report) return { status: 404, body: { error: `Report ${request.params.id} not found` } };
return { status: 200, body: { report } };
},
},
];
}