-
Notifications
You must be signed in to change notification settings - Fork 0
feat: app features — CSV export, GFM tables, param badges, data transforms #188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 15 commits
62bd3f7
a432924
18802be
79b9bfb
61c662c
0b93c4c
cb25f4f
337688a
4cfed8a
61c6a85
b962834
0066bb1
e0cbea8
96f773f
19b32ea
c8848f3
50e9a7e
2aafdd5
ea0304f
8119ef4
025dbba
ad81d64
646edd3
387250e
00f9e18
3e2b66f
bb42d9a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,13 +4,19 @@ import { useState, useMemo, useCallback } from "react"; | |
| import { useQueryClient } from "@tanstack/react-query"; | ||
| import { CardContainer } from "./card-container"; | ||
| import { getChartConfig } from "@/lib/chart-registry"; | ||
| import { | ||
| buildCsvString, | ||
| triggerDownload, | ||
| buildExportFilename, | ||
| } from "@neoboard/components"; | ||
| import { interpolateTitle } from "@/lib/interpolate-title"; | ||
| import type { | ||
| DashboardPage, | ||
| DashboardWidget, | ||
| GridLayoutItem, | ||
| WidgetTemplate, | ||
| } from "@/lib/db/schema"; | ||
| import type { ParameterSourceMap } from "@/lib/collect-parameter-names"; | ||
| import { useParameterStore } from "@/stores/parameter-store"; | ||
| import { | ||
| formatParameterValue, | ||
|
|
@@ -47,7 +53,9 @@ export interface WidgetActions { | |
| widgetId: string, | ||
| settings: Record<string, unknown>, | ||
| ) => void; | ||
| onNavigateToPage?: (pageId: string) => void; | ||
| /** Called when a click action navigates to a different page. Optionally scrolls to a widget. */ | ||
| onNavigateToPage?: (pageId: string, scrollToWidgetId?: string) => void; | ||
| /** Called when the user chooses "Save to Widget Lab" for a widget. */ | ||
| onSaveAsTemplate?: (widget: DashboardWidget) => void; | ||
| onSyncWidget?: (widget: DashboardWidget) => void; | ||
| onDetachWidget?: (widgetId: string) => void; | ||
|
|
@@ -60,6 +68,8 @@ interface DashboardContainerProps { | |
| refetchInterval?: number | false; | ||
| templateMap?: Record<string, WidgetTemplate>; | ||
| showParameterBar?: boolean; | ||
| /** Maps parameter names to the widgets that set them (for clickable badges). */ | ||
| parameterSourceMap?: ParameterSourceMap; | ||
| } | ||
|
|
||
| function getWidgetTitle(widget: DashboardWidget): string { | ||
|
|
@@ -75,6 +85,7 @@ export function DashboardContainer({ | |
| refetchInterval, | ||
| templateMap, | ||
| showParameterBar = true, | ||
| parameterSourceMap, | ||
| }: DashboardContainerProps) { | ||
| const { | ||
| onRemoveWidget, | ||
|
|
@@ -139,9 +150,39 @@ export function DashboardContainer({ | |
| return new Date(tmpl.updatedAt) > new Date(widget.templateSyncedAt); | ||
| } | ||
|
|
||
| function exportWidgetCsv(widget: DashboardWidget) { | ||
| const cached = queryClient.getQueryData<{ data: unknown }>([ | ||
| "widget-query", | ||
| widget.connectionId, | ||
| widget.query, | ||
| widget.params, | ||
| ]); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the same query key shape as Rendered widgets are cached under 🤖 Prompt for AI Agents |
||
| const rawData = cached?.data; | ||
| if (!Array.isArray(rawData) || rawData.length === 0) return; | ||
| const csv = buildCsvString(rawData as Record<string, unknown>[]); | ||
| const title = (widget.settings?.title as string) || widget.chartType; | ||
| const filename = buildExportFilename(title, "csv", page.title); | ||
|
Comment on lines
+177
to
+178
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prefix exports with the dashboard name, not the page title. The requirement for this feature is dashboard name + widget title, but this passes 🤖 Prompt for AI Agents |
||
| triggerDownload(csv, filename); | ||
| } | ||
|
|
||
| const buildActions = (widget: DashboardWidget) => { | ||
| if (!editable) return undefined; | ||
| const actions = []; | ||
|
|
||
| // Export CSV — available for data-producing widgets in both edit and view mode | ||
| const isDataWidget = ![ | ||
| "markdown", | ||
| "iframe", | ||
| "form", | ||
| "parameter-select", | ||
| ].includes(widget.chartType); | ||
| if (isDataWidget) { | ||
| actions.push({ | ||
| label: "Export CSV", | ||
| onClick: () => exportWidgetCsv(widget), | ||
| }); | ||
| } | ||
|
|
||
| if (!editable) return actions.length > 0 ? actions : undefined; | ||
| if (onEditWidget) { | ||
| actions.push({ | ||
| label: "Edit", | ||
|
|
@@ -285,6 +326,7 @@ export function DashboardContainer({ | |
| } | ||
| refetchInterval={refetchInterval} | ||
| onNavigateToPage={onNavigateToPage} | ||
| parameterSourceMap={parameterSourceMap} | ||
| /> | ||
| </WidgetCard> | ||
| </div> | ||
|
|
@@ -312,6 +354,7 @@ export function DashboardContainer({ | |
| widget={fullscreenWidget} | ||
| refetchInterval={refetchInterval} | ||
| onNavigateToPage={onNavigateToPage} | ||
| parameterSourceMap={parameterSourceMap} | ||
| autoFit | ||
| /> | ||
| ) : ( | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.