@databricks/appkit-ui 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +3 -0
- package/DCO +25 -0
- package/LICENSE +203 -0
- package/NOTICE.md +73 -0
- package/README.md +35 -0
- package/bin/setup-claude.js +190 -0
- package/dist/js/arrow/arrow-client.d.ts +64 -0
- package/dist/js/arrow/arrow-client.d.ts.map +1 -0
- package/dist/js/arrow/arrow-client.js +181 -0
- package/dist/js/arrow/arrow-client.js.map +1 -0
- package/dist/js/arrow/index.js +3 -0
- package/dist/js/arrow/lazy-arrow.d.ts +23 -0
- package/dist/js/arrow/lazy-arrow.d.ts.map +1 -0
- package/dist/js/arrow/lazy-arrow.js +86 -0
- package/dist/js/arrow/lazy-arrow.js.map +1 -0
- package/dist/js/constants.d.ts +10 -0
- package/dist/js/constants.d.ts.map +1 -0
- package/dist/js/constants.js +30 -0
- package/dist/js/constants.js.map +1 -0
- package/dist/js/index.d.ts +8 -0
- package/dist/js/index.js +8 -0
- package/dist/js/sse/connect-sse.d.ts +14 -0
- package/dist/js/sse/connect-sse.d.ts.map +1 -0
- package/dist/js/sse/connect-sse.js +128 -0
- package/dist/js/sse/connect-sse.js.map +1 -0
- package/dist/js/sse/types.d.ts +34 -0
- package/dist/js/sse/types.d.ts.map +1 -0
- package/dist/react/charts/area/index.d.ts +33 -0
- package/dist/react/charts/area/index.d.ts.map +1 -0
- package/dist/react/charts/area/index.js +29 -0
- package/dist/react/charts/area/index.js.map +1 -0
- package/dist/react/charts/bar/index.d.ts +43 -0
- package/dist/react/charts/bar/index.d.ts.map +1 -0
- package/dist/react/charts/bar/index.js +39 -0
- package/dist/react/charts/bar/index.js.map +1 -0
- package/dist/react/charts/base.d.ts +89 -0
- package/dist/react/charts/base.d.ts.map +1 -0
- package/dist/react/charts/base.js +123 -0
- package/dist/react/charts/base.js.map +1 -0
- package/dist/react/charts/chart-error-boundary.js +37 -0
- package/dist/react/charts/chart-error-boundary.js.map +1 -0
- package/dist/react/charts/constants.d.ts +22 -0
- package/dist/react/charts/constants.d.ts.map +1 -0
- package/dist/react/charts/constants.js +86 -0
- package/dist/react/charts/constants.js.map +1 -0
- package/dist/react/charts/create-chart.d.ts +26 -0
- package/dist/react/charts/create-chart.d.ts.map +1 -0
- package/dist/react/charts/create-chart.js +55 -0
- package/dist/react/charts/create-chart.js.map +1 -0
- package/dist/react/charts/empty.js +16 -0
- package/dist/react/charts/empty.js.map +1 -0
- package/dist/react/charts/error.js +16 -0
- package/dist/react/charts/error.js.map +1 -0
- package/dist/react/charts/heatmap/index.d.ts +42 -0
- package/dist/react/charts/heatmap/index.d.ts.map +1 -0
- package/dist/react/charts/heatmap/index.js +38 -0
- package/dist/react/charts/heatmap/index.js.map +1 -0
- package/dist/react/charts/index.js +18 -0
- package/dist/react/charts/line/index.d.ts +34 -0
- package/dist/react/charts/line/index.d.ts.map +1 -0
- package/dist/react/charts/line/index.js +30 -0
- package/dist/react/charts/line/index.js.map +1 -0
- package/dist/react/charts/loading.js +13 -0
- package/dist/react/charts/loading.js.map +1 -0
- package/dist/react/charts/normalize.d.ts +37 -0
- package/dist/react/charts/normalize.d.ts.map +1 -0
- package/dist/react/charts/normalize.js +256 -0
- package/dist/react/charts/normalize.js.map +1 -0
- package/dist/react/charts/options.d.ts +39 -0
- package/dist/react/charts/options.d.ts.map +1 -0
- package/dist/react/charts/options.js +212 -0
- package/dist/react/charts/options.js.map +1 -0
- package/dist/react/charts/pie/index.d.ts +57 -0
- package/dist/react/charts/pie/index.d.ts.map +1 -0
- package/dist/react/charts/pie/index.js +50 -0
- package/dist/react/charts/pie/index.js.map +1 -0
- package/dist/react/charts/radar/index.d.ts +32 -0
- package/dist/react/charts/radar/index.d.ts.map +1 -0
- package/dist/react/charts/radar/index.js +28 -0
- package/dist/react/charts/radar/index.js.map +1 -0
- package/dist/react/charts/scatter/index.d.ts +32 -0
- package/dist/react/charts/scatter/index.d.ts.map +1 -0
- package/dist/react/charts/scatter/index.js +28 -0
- package/dist/react/charts/scatter/index.js.map +1 -0
- package/dist/react/charts/theme.d.ts +23 -0
- package/dist/react/charts/theme.d.ts.map +1 -0
- package/dist/react/charts/theme.js +96 -0
- package/dist/react/charts/theme.js.map +1 -0
- package/dist/react/charts/types.d.ts +160 -0
- package/dist/react/charts/types.d.ts.map +1 -0
- package/dist/react/charts/types.js +17 -0
- package/dist/react/charts/types.js.map +1 -0
- package/dist/react/charts/utils.d.ts +36 -0
- package/dist/react/charts/utils.d.ts.map +1 -0
- package/dist/react/charts/utils.js +77 -0
- package/dist/react/charts/utils.js.map +1 -0
- package/dist/react/charts/wrapper.d.ts +65 -0
- package/dist/react/charts/wrapper.d.ts.map +1 -0
- package/dist/react/charts/wrapper.js +94 -0
- package/dist/react/charts/wrapper.js.map +1 -0
- package/dist/react/hooks/index.js +2 -0
- package/dist/react/hooks/types.d.ts +101 -0
- package/dist/react/hooks/types.d.ts.map +1 -0
- package/dist/react/hooks/use-analytics-query.d.ts +33 -0
- package/dist/react/hooks/use-analytics-query.d.ts.map +1 -0
- package/dist/react/hooks/use-analytics-query.js +146 -0
- package/dist/react/hooks/use-analytics-query.js.map +1 -0
- package/dist/react/hooks/use-chart-data.d.ts +54 -0
- package/dist/react/hooks/use-chart-data.d.ts.map +1 -0
- package/dist/react/hooks/use-chart-data.js +80 -0
- package/dist/react/hooks/use-chart-data.js.map +1 -0
- package/dist/react/hooks/use-mobile.js +21 -0
- package/dist/react/hooks/use-mobile.js.map +1 -0
- package/dist/react/hooks/use-query-hmr.js +19 -0
- package/dist/react/hooks/use-query-hmr.js.map +1 -0
- package/dist/react/index.d.ts +75 -0
- package/dist/react/index.js +79 -0
- package/dist/react/lib/format.js +42 -0
- package/dist/react/lib/format.js.map +1 -0
- package/dist/react/lib/utils.js +11 -0
- package/dist/react/lib/utils.js.map +1 -0
- package/dist/react/table/data-table.d.ts +47 -0
- package/dist/react/table/data-table.d.ts.map +1 -0
- package/dist/react/table/data-table.js +205 -0
- package/dist/react/table/data-table.js.map +1 -0
- package/dist/react/table/empty.js +16 -0
- package/dist/react/table/empty.js.map +1 -0
- package/dist/react/table/error.js +16 -0
- package/dist/react/table/error.js.map +1 -0
- package/dist/react/table/index.js +1 -0
- package/dist/react/table/loading.js +50 -0
- package/dist/react/table/loading.js.map +1 -0
- package/dist/react/table/table-wrapper.js +143 -0
- package/dist/react/table/table-wrapper.js.map +1 -0
- package/dist/react/table/types.d.ts +55 -0
- package/dist/react/table/types.d.ts.map +1 -0
- package/dist/react/ui/accordion.d.ts +25 -0
- package/dist/react/ui/accordion.d.ts.map +1 -0
- package/dist/react/ui/accordion.js +45 -0
- package/dist/react/ui/accordion.js.map +1 -0
- package/dist/react/ui/alert-dialog.d.ts +49 -0
- package/dist/react/ui/alert-dialog.d.ts.map +1 -0
- package/dist/react/ui/alert-dialog.js +82 -0
- package/dist/react/ui/alert-dialog.js.map +1 -0
- package/dist/react/ui/alert.d.ts +25 -0
- package/dist/react/ui/alert.d.ts.map +1 -0
- package/dist/react/ui/alert.js +38 -0
- package/dist/react/ui/alert.js.map +1 -0
- package/dist/react/ui/aspect-ratio.d.ts +10 -0
- package/dist/react/ui/aspect-ratio.d.ts.map +1 -0
- package/dist/react/ui/aspect-ratio.js +16 -0
- package/dist/react/ui/aspect-ratio.js.map +1 -0
- package/dist/react/ui/avatar.d.ts +20 -0
- package/dist/react/ui/avatar.d.ts.map +1 -0
- package/dist/react/ui/avatar.js +30 -0
- package/dist/react/ui/avatar.js.map +1 -0
- package/dist/react/ui/badge.d.ts +20 -0
- package/dist/react/ui/badge.d.ts.map +1 -0
- package/dist/react/ui/badge.js +26 -0
- package/dist/react/ui/badge.js.map +1 -0
- package/dist/react/ui/breadcrumb.d.ts +38 -0
- package/dist/react/ui/breadcrumb.d.ts.map +1 -0
- package/dist/react/ui/breadcrumb.js +71 -0
- package/dist/react/ui/breadcrumb.js.map +1 -0
- package/dist/react/ui/button-group.d.ts +29 -0
- package/dist/react/ui/button-group.d.ts.map +1 -0
- package/dist/react/ui/button-group.js +41 -0
- package/dist/react/ui/button-group.js.map +1 -0
- package/dist/react/ui/button.d.ts +22 -0
- package/dist/react/ui/button.d.ts.map +1 -0
- package/dist/react/ui/button.js +45 -0
- package/dist/react/ui/button.js.map +1 -0
- package/dist/react/ui/calendar.d.ts +27 -0
- package/dist/react/ui/calendar.d.ts.map +1 -0
- package/dist/react/ui/calendar.js +109 -0
- package/dist/react/ui/calendar.js.map +1 -0
- package/dist/react/ui/card.d.ts +35 -0
- package/dist/react/ui/card.d.ts.map +1 -0
- package/dist/react/ui/card.js +57 -0
- package/dist/react/ui/card.js.map +1 -0
- package/dist/react/ui/carousel.d.ts +48 -0
- package/dist/react/ui/carousel.d.ts.map +1 -0
- package/dist/react/ui/carousel.js +134 -0
- package/dist/react/ui/carousel.js.map +1 -0
- package/dist/react/ui/chart.d.ts +80 -0
- package/dist/react/ui/chart.d.ts.map +1 -0
- package/dist/react/ui/chart.js +143 -0
- package/dist/react/ui/chart.js.map +1 -0
- package/dist/react/ui/checkbox.d.ts +12 -0
- package/dist/react/ui/checkbox.d.ts.map +1 -0
- package/dist/react/ui/checkbox.js +24 -0
- package/dist/react/ui/checkbox.js.map +1 -0
- package/dist/react/ui/collapsible.d.ts +16 -0
- package/dist/react/ui/collapsible.d.ts.map +1 -0
- package/dist/react/ui/collapsible.js +26 -0
- package/dist/react/ui/collapsible.js.map +1 -0
- package/dist/react/ui/command.d.ts +53 -0
- package/dist/react/ui/command.d.ts.map +1 -0
- package/dist/react/ui/command.js +87 -0
- package/dist/react/ui/command.js.map +1 -0
- package/dist/react/ui/context-menu.d.ts +77 -0
- package/dist/react/ui/context-menu.d.ts.map +1 -0
- package/dist/react/ui/context-menu.js +125 -0
- package/dist/react/ui/context-menu.js.map +1 -0
- package/dist/react/ui/dialog.d.ts +48 -0
- package/dist/react/ui/dialog.d.ts.map +1 -0
- package/dist/react/ui/dialog.js +87 -0
- package/dist/react/ui/dialog.js.map +1 -0
- package/dist/react/ui/drawer.d.ts +45 -0
- package/dist/react/ui/drawer.d.ts.map +1 -0
- package/dist/react/ui/drawer.js +81 -0
- package/dist/react/ui/drawer.js.map +1 -0
- package/dist/react/ui/dropdown-menu.d.ts +78 -0
- package/dist/react/ui/dropdown-menu.d.ts.map +1 -0
- package/dist/react/ui/dropdown-menu.js +124 -0
- package/dist/react/ui/dropdown-menu.js.map +1 -0
- package/dist/react/ui/empty.d.ts +36 -0
- package/dist/react/ui/empty.d.ts.map +1 -0
- package/dist/react/ui/empty.js +62 -0
- package/dist/react/ui/empty.js.map +1 -0
- package/dist/react/ui/field.d.ts +65 -0
- package/dist/react/ui/field.d.ts.map +1 -0
- package/dist/react/ui/field.js +120 -0
- package/dist/react/ui/field.js.map +1 -0
- package/dist/react/ui/form.d.ts +46 -0
- package/dist/react/ui/form.d.ts.map +1 -0
- package/dist/react/ui/form.js +92 -0
- package/dist/react/ui/form.js.map +1 -0
- package/dist/react/ui/hover-card.d.ts +20 -0
- package/dist/react/ui/hover-card.d.ts.map +1 -0
- package/dist/react/ui/hover-card.js +35 -0
- package/dist/react/ui/hover-card.js.map +1 -0
- package/dist/react/ui/index.js +53 -0
- package/dist/react/ui/input-group.d.ts +44 -0
- package/dist/react/ui/input-group.d.ts.map +1 -0
- package/dist/react/ui/input-group.js +82 -0
- package/dist/react/ui/input-group.js.map +1 -0
- package/dist/react/ui/input-otp.d.ts +29 -0
- package/dist/react/ui/input-otp.d.ts.map +1 -0
- package/dist/react/ui/input-otp.js +47 -0
- package/dist/react/ui/input-otp.js.map +1 -0
- package/dist/react/ui/input.d.ts +12 -0
- package/dist/react/ui/input.d.ts.map +1 -0
- package/dist/react/ui/input.js +16 -0
- package/dist/react/ui/input.js.map +1 -0
- package/dist/react/ui/item.d.ts +63 -0
- package/dist/react/ui/item.d.ts.map +1 -0
- package/dist/react/ui/item.js +118 -0
- package/dist/react/ui/item.js.map +1 -0
- package/dist/react/ui/kbd.d.ts +14 -0
- package/dist/react/ui/kbd.d.ts.map +1 -0
- package/dist/react/ui/kbd.js +22 -0
- package/dist/react/ui/kbd.js.map +1 -0
- package/dist/react/ui/label.d.ts +12 -0
- package/dist/react/ui/label.d.ts.map +1 -0
- package/dist/react/ui/label.js +18 -0
- package/dist/react/ui/label.js.map +1 -0
- package/dist/react/ui/menubar.d.ts +85 -0
- package/dist/react/ui/menubar.d.ts.map +1 -0
- package/dist/react/ui/menubar.js +134 -0
- package/dist/react/ui/menubar.js.map +1 -0
- package/dist/react/ui/navigation-menu.d.ts +47 -0
- package/dist/react/ui/navigation-menu.d.ts.map +1 -0
- package/dist/react/ui/navigation-menu.js +82 -0
- package/dist/react/ui/navigation-menu.js.map +1 -0
- package/dist/react/ui/pagination.d.ts +40 -0
- package/dist/react/ui/pagination.d.ts.map +1 -0
- package/dist/react/ui/pagination.js +80 -0
- package/dist/react/ui/pagination.js.map +1 -0
- package/dist/react/ui/popover.d.ts +23 -0
- package/dist/react/ui/popover.d.ts.map +1 -0
- package/dist/react/ui/popover.js +38 -0
- package/dist/react/ui/popover.js.map +1 -0
- package/dist/react/ui/progress.d.ts +13 -0
- package/dist/react/ui/progress.d.ts.map +1 -0
- package/dist/react/ui/progress.js +21 -0
- package/dist/react/ui/progress.js.map +1 -0
- package/dist/react/ui/radio-group.d.ts +16 -0
- package/dist/react/ui/radio-group.d.ts.map +1 -0
- package/dist/react/ui/radio-group.js +31 -0
- package/dist/react/ui/radio-group.js.map +1 -0
- package/dist/react/ui/resizable.d.ts +22 -0
- package/dist/react/ui/resizable.d.ts.map +1 -0
- package/dist/react/ui/resizable.js +34 -0
- package/dist/react/ui/resizable.js.map +1 -0
- package/dist/react/ui/scroll-area.d.ts +18 -0
- package/dist/react/ui/scroll-area.d.ts.map +1 -0
- package/dist/react/ui/scroll-area.js +39 -0
- package/dist/react/ui/scroll-area.js.map +1 -0
- package/dist/react/ui/select.d.ts +53 -0
- package/dist/react/ui/select.d.ts.map +1 -0
- package/dist/react/ui/select.js +98 -0
- package/dist/react/ui/select.js.map +1 -0
- package/dist/react/ui/separator.d.ts +14 -0
- package/dist/react/ui/separator.d.ts.map +1 -0
- package/dist/react/ui/separator.js +20 -0
- package/dist/react/ui/separator.js.map +1 -0
- package/dist/react/ui/sheet.d.ts +41 -0
- package/dist/react/ui/sheet.d.ts.map +1 -0
- package/dist/react/ui/sheet.js +83 -0
- package/dist/react/ui/sheet.js.map +1 -0
- package/dist/react/ui/sidebar.d.ts +167 -0
- package/dist/react/ui/sidebar.d.ts.map +1 -0
- package/dist/react/ui/sidebar.js +379 -0
- package/dist/react/ui/sidebar.js.map +1 -0
- package/dist/react/ui/skeleton.d.ts +10 -0
- package/dist/react/ui/skeleton.d.ts.map +1 -0
- package/dist/react/ui/skeleton.js +15 -0
- package/dist/react/ui/skeleton.js.map +1 -0
- package/dist/react/ui/slider.d.ts +16 -0
- package/dist/react/ui/slider.d.ts.map +1 -0
- package/dist/react/ui/slider.js +40 -0
- package/dist/react/ui/slider.js.map +1 -0
- package/dist/react/ui/sonner.d.ts +10 -0
- package/dist/react/ui/sonner.d.ts.map +1 -0
- package/dist/react/ui/sonner.js +31 -0
- package/dist/react/ui/sonner.js.map +1 -0
- package/dist/react/ui/spinner.d.ts +10 -0
- package/dist/react/ui/spinner.d.ts.map +1 -0
- package/dist/react/ui/spinner.js +17 -0
- package/dist/react/ui/spinner.js.map +1 -0
- package/dist/react/ui/switch.d.ts +12 -0
- package/dist/react/ui/switch.d.ts.map +1 -0
- package/dist/react/ui/switch.js +22 -0
- package/dist/react/ui/switch.js.map +1 -0
- package/dist/react/ui/table.d.ts +39 -0
- package/dist/react/ui/table.d.ts.map +1 -0
- package/dist/react/ui/table.js +68 -0
- package/dist/react/ui/table.js.map +1 -0
- package/dist/react/ui/tabs.d.ts +24 -0
- package/dist/react/ui/tabs.d.ts.map +1 -0
- package/dist/react/ui/tabs.js +39 -0
- package/dist/react/ui/tabs.js.map +1 -0
- package/dist/react/ui/textarea.d.ts +11 -0
- package/dist/react/ui/textarea.d.ts.map +1 -0
- package/dist/react/ui/textarea.js +15 -0
- package/dist/react/ui/textarea.js.map +1 -0
- package/dist/react/ui/toggle-group.d.ts +27 -0
- package/dist/react/ui/toggle-group.d.ts.map +1 -0
- package/dist/react/ui/toggle-group.js +50 -0
- package/dist/react/ui/toggle-group.js.map +1 -0
- package/dist/react/ui/toggle.d.ts +20 -0
- package/dist/react/ui/toggle.d.ts.map +1 -0
- package/dist/react/ui/toggle.js +38 -0
- package/dist/react/ui/toggle.js.map +1 -0
- package/dist/react/ui/tooltip.d.ts +24 -0
- package/dist/react/ui/tooltip.d.ts.map +1 -0
- package/dist/react/ui/tooltip.js +39 -0
- package/dist/react/ui/tooltip.js.map +1 -0
- package/dist/shared/src/sql/helpers.d.ts +160 -0
- package/dist/shared/src/sql/helpers.d.ts.map +1 -0
- package/dist/shared/src/sql/helpers.js +103 -0
- package/dist/shared/src/sql/helpers.js.map +1 -0
- package/dist/shared/src/sql/types.d.ts +34 -0
- package/dist/shared/src/sql/types.d.ts.map +1 -0
- package/dist/styles.css +425 -0
- package/llms.txt +193 -0
- package/package.json +98 -0
- package/scripts/postinstall.js +6 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//#region src/react/lib/format.ts
|
|
2
|
+
/**
|
|
3
|
+
* Formats numeric values based on field name context
|
|
4
|
+
* @param value - The numeric value to format
|
|
5
|
+
* @param fieldName - The field name to determine formatting
|
|
6
|
+
* @returns Formatted string representation
|
|
7
|
+
* @example
|
|
8
|
+
* formatChartValue(1234.56, "cost") // "$1,234.56"
|
|
9
|
+
* formatChartValue(5000, "users") // "5.0k"
|
|
10
|
+
* formatChartValue(42.7, "percentage") // "42.7"
|
|
11
|
+
*/
|
|
12
|
+
function formatChartValue(value, fieldName) {
|
|
13
|
+
if (fieldName.toLowerCase().includes("cost") || fieldName.toLowerCase().includes("price") || fieldName.toLowerCase().includes("spend") || fieldName.toLowerCase().includes("revenue") || fieldName.toLowerCase().includes("usd")) return new Intl.NumberFormat("en-US", {
|
|
14
|
+
style: "currency",
|
|
15
|
+
currency: "USD",
|
|
16
|
+
minimumFractionDigits: 2,
|
|
17
|
+
maximumFractionDigits: 2
|
|
18
|
+
}).format(value);
|
|
19
|
+
if (value >= 1e3) return new Intl.NumberFormat("en-US", {
|
|
20
|
+
notation: "compact",
|
|
21
|
+
maximumFractionDigits: 1
|
|
22
|
+
}).format(value);
|
|
23
|
+
return value.toLocaleString("en-US", { maximumFractionDigits: 2 });
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Converts field names to human-readable labels
|
|
27
|
+
* @param field - Field name in camelCase or snake_case
|
|
28
|
+
* @returns Formatted label with proper capitalization
|
|
29
|
+
* @example
|
|
30
|
+
* formatFieldLabel("totalCost") // "Total Cost"
|
|
31
|
+
* formatFieldLabel("user_name") // "User Name"
|
|
32
|
+
* formatFieldLabel("revenue") // "Revenue"
|
|
33
|
+
*/
|
|
34
|
+
function formatFieldLabel(field) {
|
|
35
|
+
return field.replace(/[^a-zA-Z0-9_-]/g, "").replace(/([A-Z])/g, " $1").replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()).trim();
|
|
36
|
+
}
|
|
37
|
+
/** Regex for validating field names */
|
|
38
|
+
const SAFE_KEY_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
export { SAFE_KEY_REGEX, formatChartValue, formatFieldLabel };
|
|
42
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","names":[],"sources":["../../../src/react/lib/format.ts"],"sourcesContent":["/**\n * Formats numeric values based on field name context\n * @param value - The numeric value to format\n * @param fieldName - The field name to determine formatting\n * @returns Formatted string representation\n * @example\n * formatChartValue(1234.56, \"cost\") // \"$1,234.56\"\n * formatChartValue(5000, \"users\") // \"5.0k\"\n * formatChartValue(42.7, \"percentage\") // \"42.7\"\n */\nexport function formatChartValue(value: number, fieldName: string): string {\n if (\n fieldName.toLowerCase().includes(\"cost\") ||\n fieldName.toLowerCase().includes(\"price\") ||\n fieldName.toLowerCase().includes(\"spend\") ||\n fieldName.toLowerCase().includes(\"revenue\") ||\n fieldName.toLowerCase().includes(\"usd\")\n ) {\n return new Intl.NumberFormat(\"en-US\", {\n style: \"currency\",\n currency: \"USD\",\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n }).format(value);\n }\n\n if (value >= 1000) {\n return new Intl.NumberFormat(\"en-US\", {\n notation: \"compact\",\n maximumFractionDigits: 1,\n }).format(value);\n }\n\n return value.toLocaleString(\"en-US\", {\n maximumFractionDigits: 2,\n });\n}\n\n/**\n * Converts field names to human-readable labels\n * @param field - Field name in camelCase or snake_case\n * @returns Formatted label with proper capitalization\n * @example\n * formatFieldLabel(\"totalCost\") // \"Total Cost\"\n * formatFieldLabel(\"user_name\") // \"User Name\"\n * formatFieldLabel(\"revenue\") // \"Revenue\"\n */\nexport function formatFieldLabel(field: string): string {\n const safe = field.replace(/[^a-zA-Z0-9_-]/g, \"\");\n return safe\n .replace(/([A-Z])/g, \" $1\")\n .replace(/_/g, \" \")\n .replace(/\\b\\w/g, (l) => l.toUpperCase())\n .trim();\n}\n\n/** Regex for validating field names */\nexport const SAFE_KEY_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n"],"mappings":";;;;;;;;;;;AAUA,SAAgB,iBAAiB,OAAe,WAA2B;AACzE,KACE,UAAU,aAAa,CAAC,SAAS,OAAO,IACxC,UAAU,aAAa,CAAC,SAAS,QAAQ,IACzC,UAAU,aAAa,CAAC,SAAS,QAAQ,IACzC,UAAU,aAAa,CAAC,SAAS,UAAU,IAC3C,UAAU,aAAa,CAAC,SAAS,MAAM,CAEvC,QAAO,IAAI,KAAK,aAAa,SAAS;EACpC,OAAO;EACP,UAAU;EACV,uBAAuB;EACvB,uBAAuB;EACxB,CAAC,CAAC,OAAO,MAAM;AAGlB,KAAI,SAAS,IACX,QAAO,IAAI,KAAK,aAAa,SAAS;EACpC,UAAU;EACV,uBAAuB;EACxB,CAAC,CAAC,OAAO,MAAM;AAGlB,QAAO,MAAM,eAAe,SAAS,EACnC,uBAAuB,GACxB,CAAC;;;;;;;;;;;AAYJ,SAAgB,iBAAiB,OAAuB;AAEtD,QADa,MAAM,QAAQ,mBAAmB,GAAG,CAE9C,QAAQ,YAAY,MAAM,CAC1B,QAAQ,MAAM,IAAI,CAClB,QAAQ,UAAU,MAAM,EAAE,aAAa,CAAC,CACxC,MAAM;;;AAIX,MAAa,iBAAiB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","names":[],"sources":["../../../src/react/lib/utils.ts"],"sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n"],"mappings":";;;;AAGA,SAAgB,GAAG,GAAG,QAAsB;AAC1C,QAAO,QAAQ,KAAK,OAAO,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { DataTableProps } from "./types.js";
|
|
2
|
+
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
3
|
+
|
|
4
|
+
//#region src/react/table/data-table.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Production-ready data table with automatic data fetching and state management
|
|
8
|
+
* Features:
|
|
9
|
+
* - Automatic column generation from data structure
|
|
10
|
+
* - Integrated with useAnalyticsQuery for data fetching
|
|
11
|
+
* - Built-in loading, error, and empty states
|
|
12
|
+
* - Dynamic filtering, sorting and pagination
|
|
13
|
+
* - Column visibility controls
|
|
14
|
+
* - Responsive design
|
|
15
|
+
* @param props - Props for the DataTable component
|
|
16
|
+
* @param props.queryKey - The query key to fetch the data
|
|
17
|
+
* @param props.parameters - The parameters to pass to the query
|
|
18
|
+
* @param props.filterColumn - The column to filter by
|
|
19
|
+
* @param props.filterPlaceholder - The placeholder for the filter input
|
|
20
|
+
* @param props.children - Optional children for full control mode
|
|
21
|
+
* @returns - The rendered data table component
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* // Opinionated mode
|
|
25
|
+
* <DataTable
|
|
26
|
+
* queryKey="users-list"
|
|
27
|
+
* parameters={{ status: "active" }}
|
|
28
|
+
* filterColumn="email"
|
|
29
|
+
* filterPlaceholder="Filter by email..."
|
|
30
|
+
* />
|
|
31
|
+
* @example
|
|
32
|
+
* // full control mode
|
|
33
|
+
* <DataTable queryKey="users-list" parameters={{ status: "active" }}>
|
|
34
|
+
* {(table) => (
|
|
35
|
+
* <div>
|
|
36
|
+
* <h2>Custom Table UI</h2>
|
|
37
|
+
* {table.getRowModel().rows.map(row => (
|
|
38
|
+
* <div key={row.id}>{row.original.name}</div>
|
|
39
|
+
* ))}
|
|
40
|
+
* </div>
|
|
41
|
+
* )}
|
|
42
|
+
* </DataTable>
|
|
43
|
+
*/
|
|
44
|
+
declare function DataTable(props: DataTableProps): react_jsx_runtime0.JSX.Element;
|
|
45
|
+
//#endregion
|
|
46
|
+
export { DataTable };
|
|
47
|
+
//# sourceMappingURL=data-table.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-table.d.ts","names":[],"sources":["../../../src/react/table/data-table.tsx"],"sourcesContent":[],"mappings":";;;;;;;;AAmEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,SAAA,QAAiB,iBAAc,kBAAA,CAAA,GAAA,CAAA"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { formatFieldLabel } from "../lib/format.js";
|
|
2
|
+
import { Button } from "../ui/button.js";
|
|
3
|
+
import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuTrigger } from "../ui/dropdown-menu.js";
|
|
4
|
+
import { Input } from "../ui/input.js";
|
|
5
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select.js";
|
|
6
|
+
import { Table as Table$1, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../ui/table.js";
|
|
7
|
+
import { TableWrapper } from "./table-wrapper.js";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { flexRender } from "@tanstack/react-table";
|
|
10
|
+
import { ChevronDown } from "lucide-react";
|
|
11
|
+
|
|
12
|
+
//#region src/react/table/data-table.tsx
|
|
13
|
+
/**
|
|
14
|
+
* Production-ready data table with automatic data fetching and state management
|
|
15
|
+
* Features:
|
|
16
|
+
* - Automatic column generation from data structure
|
|
17
|
+
* - Integrated with useAnalyticsQuery for data fetching
|
|
18
|
+
* - Built-in loading, error, and empty states
|
|
19
|
+
* - Dynamic filtering, sorting and pagination
|
|
20
|
+
* - Column visibility controls
|
|
21
|
+
* - Responsive design
|
|
22
|
+
* @param props - Props for the DataTable component
|
|
23
|
+
* @param props.queryKey - The query key to fetch the data
|
|
24
|
+
* @param props.parameters - The parameters to pass to the query
|
|
25
|
+
* @param props.filterColumn - The column to filter by
|
|
26
|
+
* @param props.filterPlaceholder - The placeholder for the filter input
|
|
27
|
+
* @param props.children - Optional children for full control mode
|
|
28
|
+
* @returns - The rendered data table component
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // Opinionated mode
|
|
32
|
+
* <DataTable
|
|
33
|
+
* queryKey="users-list"
|
|
34
|
+
* parameters={{ status: "active" }}
|
|
35
|
+
* filterColumn="email"
|
|
36
|
+
* filterPlaceholder="Filter by email..."
|
|
37
|
+
* />
|
|
38
|
+
* @example
|
|
39
|
+
* // full control mode
|
|
40
|
+
* <DataTable queryKey="users-list" parameters={{ status: "active" }}>
|
|
41
|
+
* {(table) => (
|
|
42
|
+
* <div>
|
|
43
|
+
* <h2>Custom Table UI</h2>
|
|
44
|
+
* {table.getRowModel().rows.map(row => (
|
|
45
|
+
* <div key={row.id}>{row.original.name}</div>
|
|
46
|
+
* ))}
|
|
47
|
+
* </div>
|
|
48
|
+
* )}
|
|
49
|
+
* </DataTable>
|
|
50
|
+
*/
|
|
51
|
+
function DataTable(props) {
|
|
52
|
+
const { parameters, queryKey, filterColumn, filterPlaceholder, transform, labels, ariaLabel, testId, className, enableRowSelection, onRowSelectionChange, children, pageSize = 10, pageSizeOptions = [
|
|
53
|
+
10,
|
|
54
|
+
25,
|
|
55
|
+
50,
|
|
56
|
+
100
|
|
57
|
+
] } = props;
|
|
58
|
+
const finalLabels = {
|
|
59
|
+
columnsButton: "Columns",
|
|
60
|
+
noResults: "No results found.",
|
|
61
|
+
rowsFound: `\${count} row(s) found`,
|
|
62
|
+
previousButton: "Previous",
|
|
63
|
+
nextButton: "Next",
|
|
64
|
+
rowsPerPage: "Rows per page",
|
|
65
|
+
showing: `Showing \${from} to \${to} of \${total}`,
|
|
66
|
+
...labels
|
|
67
|
+
};
|
|
68
|
+
return /* @__PURE__ */ jsx(TableWrapper, {
|
|
69
|
+
queryKey,
|
|
70
|
+
parameters,
|
|
71
|
+
ariaLabel,
|
|
72
|
+
testId,
|
|
73
|
+
className,
|
|
74
|
+
transformer: transform,
|
|
75
|
+
enableRowSelection,
|
|
76
|
+
onRowSelectionChange,
|
|
77
|
+
pageSize,
|
|
78
|
+
children: (table) => {
|
|
79
|
+
if (children) return children(table);
|
|
80
|
+
const data = table.options.data;
|
|
81
|
+
const defaultFilterColumn = filterColumn || (data && data.length > 0 ? Object.keys(data[0]).find((key) => typeof data[0][key] === "string") : null);
|
|
82
|
+
const totalRows = table.getFilteredRowModel().rows.length;
|
|
83
|
+
const currentPage = table.getState().pagination.pageIndex + 1;
|
|
84
|
+
const currentPageSize = table.getState().pagination.pageSize;
|
|
85
|
+
const fromRow = totalRows === 0 ? 0 : (currentPage - 1) * currentPageSize + 1;
|
|
86
|
+
const toRow = Math.min(currentPage * currentPageSize, totalRows);
|
|
87
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
88
|
+
className: "w-full",
|
|
89
|
+
children: [
|
|
90
|
+
/* @__PURE__ */ jsxs("div", {
|
|
91
|
+
className: "flex items-center py-4 gap-2",
|
|
92
|
+
children: [defaultFilterColumn && /* @__PURE__ */ jsx(Input, {
|
|
93
|
+
placeholder: filterPlaceholder || `Filter by ${formatFieldLabel(defaultFilterColumn)}...`,
|
|
94
|
+
value: table.getColumn(defaultFilterColumn)?.getFilterValue() ?? "",
|
|
95
|
+
onChange: (event) => table.getColumn(defaultFilterColumn)?.setFilterValue(event.target.value),
|
|
96
|
+
className: "max-w-sm"
|
|
97
|
+
}), /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
|
|
98
|
+
asChild: true,
|
|
99
|
+
children: /* @__PURE__ */ jsxs(Button, {
|
|
100
|
+
variant: "outline",
|
|
101
|
+
className: "ml-auto",
|
|
102
|
+
children: [
|
|
103
|
+
finalLabels.columnsButton,
|
|
104
|
+
" ",
|
|
105
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: "ml-2 h-4 w-4" })
|
|
106
|
+
]
|
|
107
|
+
})
|
|
108
|
+
}), /* @__PURE__ */ jsx(DropdownMenuContent, {
|
|
109
|
+
align: "end",
|
|
110
|
+
children: table.getAllColumns().filter((column) => column.getCanHide()).map((column) => {
|
|
111
|
+
return /* @__PURE__ */ jsx(DropdownMenuCheckboxItem, {
|
|
112
|
+
className: "capitalize",
|
|
113
|
+
checked: column.getIsVisible(),
|
|
114
|
+
onCheckedChange: (value) => column.toggleVisibility(!!value),
|
|
115
|
+
children: formatFieldLabel(column.id)
|
|
116
|
+
}, column.id);
|
|
117
|
+
})
|
|
118
|
+
})] })]
|
|
119
|
+
}),
|
|
120
|
+
/* @__PURE__ */ jsx("div", {
|
|
121
|
+
className: "overflow-hidden rounded-md border border-border",
|
|
122
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
123
|
+
className: "max-h-[600px] overflow-y-auto",
|
|
124
|
+
children: /* @__PURE__ */ jsxs(Table$1, { children: [/* @__PURE__ */ jsx(TableHeader, {
|
|
125
|
+
className: "sticky top-0 bg-background z-10",
|
|
126
|
+
children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx(TableRow, { children: headerGroup.headers.map((header) => {
|
|
127
|
+
const isSelectColumn = header.column.id === "select";
|
|
128
|
+
return /* @__PURE__ */ jsx(TableHead, {
|
|
129
|
+
style: {
|
|
130
|
+
width: header.getSize(),
|
|
131
|
+
position: "relative"
|
|
132
|
+
},
|
|
133
|
+
className: isSelectColumn ? "w-[40px] p-0" : void 0,
|
|
134
|
+
children: header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())
|
|
135
|
+
}, header.id);
|
|
136
|
+
}) }, headerGroup.id))
|
|
137
|
+
}), /* @__PURE__ */ jsx(TableBody, { children: table.getRowModel().rows?.length ? table.getRowModel().rows.map((row) => /* @__PURE__ */ jsx(TableRow, {
|
|
138
|
+
"data-state": row.getIsSelected() && "selected",
|
|
139
|
+
children: row.getVisibleCells().map((cell) => {
|
|
140
|
+
const isSelectColumn = cell.column.id === "select";
|
|
141
|
+
return /* @__PURE__ */ jsx(TableCell, {
|
|
142
|
+
style: { width: cell.column.getSize() },
|
|
143
|
+
className: isSelectColumn ? "w-[40px] p-0" : void 0,
|
|
144
|
+
children: flexRender(cell.column.columnDef.cell, cell.getContext())
|
|
145
|
+
}, cell.id);
|
|
146
|
+
})
|
|
147
|
+
}, row.id)) : /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, {
|
|
148
|
+
colSpan: table.getAllColumns().length,
|
|
149
|
+
className: "h-24 text-center",
|
|
150
|
+
children: finalLabels.noResults
|
|
151
|
+
}) }) })] })
|
|
152
|
+
})
|
|
153
|
+
}),
|
|
154
|
+
/* @__PURE__ */ jsxs("div", {
|
|
155
|
+
className: "flex items-center justify-between py-4",
|
|
156
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
157
|
+
className: "flex items-center gap-6",
|
|
158
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
159
|
+
className: "text-foreground text-sm",
|
|
160
|
+
children: totalRows > 0 ? finalLabels.showing.replace(`\${from}`, fromRow.toString()).replace(`\${to}`, toRow.toString()).replace(`\${total}`, totalRows.toString()) : finalLabels.noResults
|
|
161
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
162
|
+
className: "flex items-center gap-2",
|
|
163
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
164
|
+
className: "text-sm text-foreground",
|
|
165
|
+
children: finalLabels.rowsPerPage
|
|
166
|
+
}), /* @__PURE__ */ jsxs(Select, {
|
|
167
|
+
value: currentPageSize.toString(),
|
|
168
|
+
onValueChange: (value) => {
|
|
169
|
+
table.setPageSize(Number(value));
|
|
170
|
+
},
|
|
171
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
172
|
+
className: "w-[70px]",
|
|
173
|
+
children: /* @__PURE__ */ jsx(SelectValue, {})
|
|
174
|
+
}), /* @__PURE__ */ jsx(SelectContent, { children: pageSizeOptions.map((size) => /* @__PURE__ */ jsx(SelectItem, {
|
|
175
|
+
value: size.toString(),
|
|
176
|
+
children: size
|
|
177
|
+
}, size)) })]
|
|
178
|
+
})]
|
|
179
|
+
})]
|
|
180
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
181
|
+
className: "space-x-2",
|
|
182
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
183
|
+
variant: "outline",
|
|
184
|
+
size: "sm",
|
|
185
|
+
onClick: () => table.previousPage(),
|
|
186
|
+
disabled: !table.getCanPreviousPage(),
|
|
187
|
+
children: finalLabels.previousButton
|
|
188
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
189
|
+
variant: "outline",
|
|
190
|
+
size: "sm",
|
|
191
|
+
onClick: () => table.nextPage(),
|
|
192
|
+
disabled: !table.getCanNextPage(),
|
|
193
|
+
children: finalLabels.nextButton
|
|
194
|
+
})]
|
|
195
|
+
})]
|
|
196
|
+
})
|
|
197
|
+
]
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
//#endregion
|
|
204
|
+
export { DataTable };
|
|
205
|
+
//# sourceMappingURL=data-table.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-table.js","names":["Table"],"sources":["../../../src/react/table/data-table.tsx"],"sourcesContent":["import { flexRender } from \"@tanstack/react-table\";\nimport { ChevronDown } from \"lucide-react\";\nimport { formatFieldLabel } from \"../lib/format\";\nimport { Button } from \"../ui/button\";\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuTrigger,\n} from \"../ui/dropdown-menu\";\nimport { Input } from \"../ui/input\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"../ui/select\";\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from \"../ui/table\";\nimport { TableWrapper } from \"./table-wrapper\";\nimport type { DataTableLabels, DataTableProps } from \"./types\";\n\n/**\n * Production-ready data table with automatic data fetching and state management\n * Features:\n * - Automatic column generation from data structure\n * - Integrated with useAnalyticsQuery for data fetching\n * - Built-in loading, error, and empty states\n * - Dynamic filtering, sorting and pagination\n * - Column visibility controls\n * - Responsive design\n * @param props - Props for the DataTable component\n * @param props.queryKey - The query key to fetch the data\n * @param props.parameters - The parameters to pass to the query\n * @param props.filterColumn - The column to filter by\n * @param props.filterPlaceholder - The placeholder for the filter input\n * @param props.children - Optional children for full control mode\n * @returns - The rendered data table component\n *\n * @example\n * // Opinionated mode\n * <DataTable\n * queryKey=\"users-list\"\n * parameters={{ status: \"active\" }}\n * filterColumn=\"email\"\n * filterPlaceholder=\"Filter by email...\"\n * />\n * @example\n * // full control mode\n * <DataTable queryKey=\"users-list\" parameters={{ status: \"active\" }}>\n * {(table) => (\n * <div>\n * <h2>Custom Table UI</h2>\n * {table.getRowModel().rows.map(row => (\n * <div key={row.id}>{row.original.name}</div>\n * ))}\n * </div>\n * )}\n * </DataTable>\n */\nexport function DataTable(props: DataTableProps) {\n const {\n parameters,\n queryKey,\n filterColumn,\n filterPlaceholder,\n transform,\n labels,\n ariaLabel,\n testId,\n className,\n enableRowSelection,\n onRowSelectionChange,\n children,\n pageSize = 10,\n pageSizeOptions = [10, 25, 50, 100],\n } = props;\n\n const defaultLabels: Required<DataTableLabels> = {\n columnsButton: \"Columns\",\n noResults: \"No results found.\",\n rowsFound: `\\${count} row(s) found`,\n previousButton: \"Previous\",\n nextButton: \"Next\",\n rowsPerPage: \"Rows per page\",\n showing: `Showing \\${from} to \\${to} of \\${total}`,\n };\n\n const finalLabels = { ...defaultLabels, ...labels };\n\n return (\n <TableWrapper\n queryKey={queryKey}\n parameters={parameters}\n ariaLabel={ariaLabel}\n testId={testId}\n className={className}\n transformer={transform}\n enableRowSelection={enableRowSelection}\n onRowSelectionChange={onRowSelectionChange}\n pageSize={pageSize}\n >\n {(table) => {\n if (children) {\n return children(table);\n }\n\n const data = table.options.data;\n\n const defaultFilterColumn =\n filterColumn ||\n (data && data.length > 0\n ? Object.keys(data[0] as Record<string, any>).find(\n (key) =>\n typeof (data[0] as Record<string, any>)[key] === \"string\",\n )\n : null);\n\n const totalRows = table.getFilteredRowModel().rows.length;\n const currentPage = table.getState().pagination.pageIndex + 1;\n const currentPageSize = table.getState().pagination.pageSize;\n const fromRow =\n totalRows === 0 ? 0 : (currentPage - 1) * currentPageSize + 1;\n const toRow = Math.min(currentPage * currentPageSize, totalRows);\n\n return (\n <div className=\"w-full\">\n <div className=\"flex items-center py-4 gap-2\">\n {defaultFilterColumn && (\n <Input\n placeholder={\n filterPlaceholder ||\n `Filter by ${formatFieldLabel(defaultFilterColumn)}...`\n }\n value={\n (table\n .getColumn(defaultFilterColumn)\n ?.getFilterValue() as string) ?? \"\"\n }\n onChange={(event) =>\n table\n .getColumn(defaultFilterColumn)\n ?.setFilterValue(event.target.value)\n }\n className=\"max-w-sm\"\n />\n )}\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" className=\"ml-auto\">\n {finalLabels.columnsButton}{\" \"}\n <ChevronDown className=\"ml-2 h-4 w-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {table\n .getAllColumns()\n .filter((column) => column.getCanHide())\n .map((column) => {\n return (\n <DropdownMenuCheckboxItem\n key={column.id}\n className=\"capitalize\"\n checked={column.getIsVisible()}\n onCheckedChange={(value) =>\n column.toggleVisibility(!!value)\n }\n >\n {formatFieldLabel(column.id)}\n </DropdownMenuCheckboxItem>\n );\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n <div className=\"overflow-hidden rounded-md border border-border\">\n <div className=\"max-h-[600px] overflow-y-auto\">\n <Table>\n <TableHeader className=\"sticky top-0 bg-background z-10\">\n {table.getHeaderGroups().map((headerGroup) => (\n <TableRow key={headerGroup.id}>\n {headerGroup.headers.map((header) => {\n const isSelectColumn = header.column.id === \"select\";\n return (\n <TableHead\n key={header.id}\n style={{\n width: header.getSize(),\n position: \"relative\",\n }}\n className={\n isSelectColumn ? \"w-[40px] p-0\" : undefined\n }\n >\n {header.isPlaceholder\n ? null\n : flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n </TableHead>\n );\n })}\n </TableRow>\n ))}\n </TableHeader>\n <TableBody>\n {table.getRowModel().rows?.length ? (\n table.getRowModel().rows.map((row) => (\n <TableRow\n key={row.id}\n data-state={row.getIsSelected() && \"selected\"}\n >\n {row.getVisibleCells().map((cell) => {\n const isSelectColumn = cell.column.id === \"select\";\n return (\n <TableCell\n key={cell.id}\n style={{\n width: cell.column.getSize(),\n }}\n className={\n isSelectColumn ? \"w-[40px] p-0\" : undefined\n }\n >\n {flexRender(\n cell.column.columnDef.cell,\n cell.getContext(),\n )}\n </TableCell>\n );\n })}\n </TableRow>\n ))\n ) : (\n <TableRow>\n <TableCell\n colSpan={table.getAllColumns().length}\n className=\"h-24 text-center\"\n >\n {finalLabels.noResults}\n </TableCell>\n </TableRow>\n )}\n </TableBody>\n </Table>\n </div>\n </div>\n <div className=\"flex items-center justify-between py-4\">\n <div className=\"flex items-center gap-6\">\n <div className=\"text-foreground text-sm\">\n {totalRows > 0\n ? finalLabels.showing\n .replace(`\\${from}`, fromRow.toString())\n .replace(`\\${to}`, toRow.toString())\n .replace(`\\${total}`, totalRows.toString())\n : finalLabels.noResults}\n </div>\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-foreground\">\n {finalLabels.rowsPerPage}\n </span>\n <Select\n value={currentPageSize.toString()}\n onValueChange={(value) => {\n table.setPageSize(Number(value));\n }}\n >\n <SelectTrigger className=\"w-[70px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {pageSizeOptions.map((size) => (\n <SelectItem key={size} value={size.toString()}>\n {size}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n <div className=\"space-x-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => table.previousPage()}\n disabled={!table.getCanPreviousPage()}\n >\n {finalLabels.previousButton}\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => table.nextPage()}\n disabled={!table.getCanNextPage()}\n >\n {finalLabels.nextButton}\n </Button>\n </div>\n </div>\n </div>\n );\n }}\n </TableWrapper>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmEA,SAAgB,UAAU,OAAuB;CAC/C,MAAM,EACJ,YACA,UACA,cACA,mBACA,WACA,QACA,WACA,QACA,WACA,oBACA,sBACA,UACA,WAAW,IACX,kBAAkB;EAAC;EAAI;EAAI;EAAI;EAAI,KACjC;CAYJ,MAAM,cAAc;EATlB,eAAe;EACf,WAAW;EACX,WAAW;EACX,gBAAgB;EAChB,YAAY;EACZ,aAAa;EACb,SAAS;EAG6B,GAAG;EAAQ;AAEnD,QACE,oBAAC;EACW;EACE;EACD;EACH;EACG;EACX,aAAa;EACO;EACE;EACZ;aAER,UAAU;AACV,OAAI,SACF,QAAO,SAAS,MAAM;GAGxB,MAAM,OAAO,MAAM,QAAQ;GAE3B,MAAM,sBACJ,iBACC,QAAQ,KAAK,SAAS,IACnB,OAAO,KAAK,KAAK,GAA0B,CAAC,MACzC,QACC,OAAQ,KAAK,GAA2B,SAAS,SACpD,GACD;GAEN,MAAM,YAAY,MAAM,qBAAqB,CAAC,KAAK;GACnD,MAAM,cAAc,MAAM,UAAU,CAAC,WAAW,YAAY;GAC5D,MAAM,kBAAkB,MAAM,UAAU,CAAC,WAAW;GACpD,MAAM,UACJ,cAAc,IAAI,KAAK,cAAc,KAAK,kBAAkB;GAC9D,MAAM,QAAQ,KAAK,IAAI,cAAc,iBAAiB,UAAU;AAEhE,UACE,qBAAC;IAAI,WAAU;;KACb,qBAAC;MAAI,WAAU;iBACZ,uBACC,oBAAC;OACC,aACE,qBACA,aAAa,iBAAiB,oBAAoB,CAAC;OAErD,OACG,MACE,UAAU,oBAAoB,EAC7B,gBAAgB,IAAe;OAErC,WAAW,UACT,MACG,UAAU,oBAAoB,EAC7B,eAAe,MAAM,OAAO,MAAM;OAExC,WAAU;QACV,EAEJ,qBAAC,2BACC,oBAAC;OAAoB;iBACnB,qBAAC;QAAO,SAAQ;QAAU,WAAU;;SACjC,YAAY;SAAe;SAC5B,oBAAC,eAAY,WAAU,iBAAiB;;SACjC;QACW,EACtB,oBAAC;OAAoB,OAAM;iBACxB,MACE,eAAe,CACf,QAAQ,WAAW,OAAO,YAAY,CAAC,CACvC,KAAK,WAAW;AACf,eACE,oBAAC;SAEC,WAAU;SACV,SAAS,OAAO,cAAc;SAC9B,kBAAkB,UAChB,OAAO,iBAAiB,CAAC,CAAC,MAAM;mBAGjC,iBAAiB,OAAO,GAAG;WAPvB,OAAO,GAQa;SAE7B;QACgB,IACT;OACX;KACN,oBAAC;MAAI,WAAU;gBACb,oBAAC;OAAI,WAAU;iBACb,qBAACA,sBACC,oBAAC;QAAY,WAAU;kBACpB,MAAM,iBAAiB,CAAC,KAAK,gBAC5B,oBAAC,sBACE,YAAY,QAAQ,KAAK,WAAW;SACnC,MAAM,iBAAiB,OAAO,OAAO,OAAO;AAC5C,gBACE,oBAAC;UAEC,OAAO;WACL,OAAO,OAAO,SAAS;WACvB,UAAU;WACX;UACD,WACE,iBAAiB,iBAAiB;oBAGnC,OAAO,gBACJ,OACA,WACE,OAAO,OAAO,UAAU,QACxB,OAAO,YAAY,CACpB;YAdA,OAAO,GAeF;UAEd,IAtBW,YAAY,GAuBhB,CACX;SACU,EACd,oBAAC,uBACE,MAAM,aAAa,CAAC,MAAM,SACzB,MAAM,aAAa,CAAC,KAAK,KAAK,QAC5B,oBAAC;QAEC,cAAY,IAAI,eAAe,IAAI;kBAElC,IAAI,iBAAiB,CAAC,KAAK,SAAS;SACnC,MAAM,iBAAiB,KAAK,OAAO,OAAO;AAC1C,gBACE,oBAAC;UAEC,OAAO,EACL,OAAO,KAAK,OAAO,SAAS,EAC7B;UACD,WACE,iBAAiB,iBAAiB;oBAGnC,WACC,KAAK,OAAO,UAAU,MACtB,KAAK,YAAY,CAClB;YAXI,KAAK,GAYA;UAEd;UArBG,IAAI,GAsBA,CACX,GAEF,oBAAC,sBACC,oBAAC;QACC,SAAS,MAAM,eAAe,CAAC;QAC/B,WAAU;kBAET,YAAY;SACH,GACH,GAEH,IACN;QACJ;OACF;KACN,qBAAC;MAAI,WAAU;iBACb,qBAAC;OAAI,WAAU;kBACb,oBAAC;QAAI,WAAU;kBACZ,YAAY,IACT,YAAY,QACT,QAAQ,YAAY,QAAQ,UAAU,CAAC,CACvC,QAAQ,UAAU,MAAM,UAAU,CAAC,CACnC,QAAQ,aAAa,UAAU,UAAU,CAAC,GAC7C,YAAY;SACZ,EACN,qBAAC;QAAI,WAAU;mBACb,oBAAC;SAAK,WAAU;mBACb,YAAY;UACR,EACP,qBAAC;SACC,OAAO,gBAAgB,UAAU;SACjC,gBAAgB,UAAU;AACxB,gBAAM,YAAY,OAAO,MAAM,CAAC;;oBAGlC,oBAAC;UAAc,WAAU;oBACvB,oBAAC,gBAAc;WACD,EAChB,oBAAC,2BACE,gBAAgB,KAAK,SACpB,oBAAC;UAAsB,OAAO,KAAK,UAAU;oBAC1C;YADc,KAEJ,CACb,GACY;UACT;SACL;QACF,EACN,qBAAC;OAAI,WAAU;kBACb,oBAAC;QACC,SAAQ;QACR,MAAK;QACL,eAAe,MAAM,cAAc;QACnC,UAAU,CAAC,MAAM,oBAAoB;kBAEpC,YAAY;SACN,EACT,oBAAC;QACC,SAAQ;QACR,MAAK;QACL,eAAe,MAAM,UAAU;QAC/B,UAAU,CAAC,MAAM,gBAAgB;kBAEhC,YAAY;SACN;QACL;OACF;;KACF;;GAGG"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
|
|
3
|
+
//#region src/react/table/empty.tsx
|
|
4
|
+
function EmptyState() {
|
|
5
|
+
return /* @__PURE__ */ jsx("div", {
|
|
6
|
+
className: "w-full p-8 text-center",
|
|
7
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
8
|
+
className: "text-sm text-muted-foreground",
|
|
9
|
+
children: "No data available"
|
|
10
|
+
})
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
15
|
+
export { EmptyState };
|
|
16
|
+
//# sourceMappingURL=empty.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"empty.js","names":[],"sources":["../../../src/react/table/empty.tsx"],"sourcesContent":["export function EmptyState() {\n return (\n <div className=\"w-full p-8 text-center\">\n <p className=\"text-sm text-muted-foreground\">No data available</p>\n </div>\n );\n}\n"],"mappings":";;;AAAA,SAAgB,aAAa;AAC3B,QACE,oBAAC;EAAI,WAAU;YACb,oBAAC;GAAE,WAAU;aAAgC;IAAqB;GAC9D"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
|
|
3
|
+
//#region src/react/table/error.tsx
|
|
4
|
+
function ErrorState({ error }) {
|
|
5
|
+
return /* @__PURE__ */ jsx("div", {
|
|
6
|
+
className: "w-full p-8 text-center",
|
|
7
|
+
children: /* @__PURE__ */ jsxs("p", {
|
|
8
|
+
className: "text-sm text-destructive",
|
|
9
|
+
children: ["Error loading table: ", error]
|
|
10
|
+
})
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
15
|
+
export { ErrorState };
|
|
16
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.js","names":[],"sources":["../../../src/react/table/error.tsx"],"sourcesContent":["export function ErrorState({ error }: { error: string }) {\n return (\n <div className=\"w-full p-8 text-center\">\n <p className=\"text-sm text-destructive\">Error loading table: {error}</p>\n </div>\n );\n}\n"],"mappings":";;;AAAA,SAAgB,WAAW,EAAE,SAA4B;AACvD,QACE,oBAAC;EAAI,WAAU;YACb,qBAAC;GAAE,WAAU;cAA2B,yBAAsB;IAAU;GACpE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { DataTable } from "./data-table.js";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
|
|
3
|
+
//#region src/react/table/loading.tsx
|
|
4
|
+
function LoadingSkeleton() {
|
|
5
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
6
|
+
className: "w-full space-y-3 mt-2",
|
|
7
|
+
children: [
|
|
8
|
+
/* @__PURE__ */ jsxs("div", {
|
|
9
|
+
className: "flex items-center gap-2 mt-4",
|
|
10
|
+
children: [/* @__PURE__ */ jsx("div", { className: "h-10 bg-muted rounded animate-pulse flex-1 max-w-sm" }), /* @__PURE__ */ jsx("div", { className: "h-10 bg-muted rounded animate-pulse w-24 ml-auto" })]
|
|
11
|
+
}),
|
|
12
|
+
/* @__PURE__ */ jsxs("div", {
|
|
13
|
+
className: "rounded-md border overflow-hidden",
|
|
14
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
15
|
+
className: "border-b bg-secondary p-4",
|
|
16
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
17
|
+
className: "flex gap-4",
|
|
18
|
+
children: [
|
|
19
|
+
/* @__PURE__ */ jsx("div", { className: "h-8 bg-muted rounded animate-pulse flex-1" }),
|
|
20
|
+
/* @__PURE__ */ jsx("div", { className: "h-8 bg-muted rounded animate-pulse flex-1" }),
|
|
21
|
+
/* @__PURE__ */ jsx("div", { className: "h-8 bg-muted rounded animate-pulse flex-1" })
|
|
22
|
+
]
|
|
23
|
+
})
|
|
24
|
+
}), Array.from({ length: 5 }).map(() => /* @__PURE__ */ jsx("div", {
|
|
25
|
+
className: "border-b p-4 last:border-b-0",
|
|
26
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
27
|
+
className: "flex gap-4",
|
|
28
|
+
children: [
|
|
29
|
+
/* @__PURE__ */ jsx("div", { className: "h-6 bg-muted rounded animate-pulse flex-1" }),
|
|
30
|
+
/* @__PURE__ */ jsx("div", { className: "h-6 bg-muted rounded animate-pulse flex-1" }),
|
|
31
|
+
/* @__PURE__ */ jsx("div", { className: "h-6 bg-muted rounded animate-pulse flex-1" })
|
|
32
|
+
]
|
|
33
|
+
})
|
|
34
|
+
}, `loading-row-${Math.random()}`))]
|
|
35
|
+
}),
|
|
36
|
+
/* @__PURE__ */ jsxs("div", {
|
|
37
|
+
className: "flex items-center justify-end gap-2",
|
|
38
|
+
children: [
|
|
39
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 bg-muted rounded animate-pulse w-32" }),
|
|
40
|
+
/* @__PURE__ */ jsx("div", { className: "h-9 bg-muted rounded animate-pulse w-20" }),
|
|
41
|
+
/* @__PURE__ */ jsx("div", { className: "h-9 bg-muted rounded animate-pulse w-20" })
|
|
42
|
+
]
|
|
43
|
+
})
|
|
44
|
+
]
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
export { LoadingSkeleton };
|
|
50
|
+
//# sourceMappingURL=loading.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loading.js","names":[],"sources":["../../../src/react/table/loading.tsx"],"sourcesContent":["export function LoadingSkeleton() {\n return (\n <div className=\"w-full space-y-3 mt-2\">\n <div className=\"flex items-center gap-2 mt-4\">\n <div className=\"h-10 bg-muted rounded animate-pulse flex-1 max-w-sm\" />\n <div className=\"h-10 bg-muted rounded animate-pulse w-24 ml-auto\" />\n </div>\n\n <div className=\"rounded-md border overflow-hidden\">\n <div className=\"border-b bg-secondary p-4\">\n <div className=\"flex gap-4\">\n <div className=\"h-8 bg-muted rounded animate-pulse flex-1\" />\n <div className=\"h-8 bg-muted rounded animate-pulse flex-1\" />\n <div className=\"h-8 bg-muted rounded animate-pulse flex-1\" />\n </div>\n </div>\n\n {Array.from({ length: 5 }).map(() => (\n <div\n key={`loading-row-${Math.random()}`}\n className=\"border-b p-4 last:border-b-0\"\n >\n <div className=\"flex gap-4\">\n <div className=\"h-6 bg-muted rounded animate-pulse flex-1\" />\n <div className=\"h-6 bg-muted rounded animate-pulse flex-1\" />\n <div className=\"h-6 bg-muted rounded animate-pulse flex-1\" />\n </div>\n </div>\n ))}\n </div>\n\n <div className=\"flex items-center justify-end gap-2\">\n <div className=\"h-4 bg-muted rounded animate-pulse w-32\" />\n <div className=\"h-9 bg-muted rounded animate-pulse w-20\" />\n <div className=\"h-9 bg-muted rounded animate-pulse w-20\" />\n </div>\n </div>\n );\n}\n"],"mappings":";;;AAAA,SAAgB,kBAAkB;AAChC,QACE,qBAAC;EAAI,WAAU;;GACb,qBAAC;IAAI,WAAU;eACb,oBAAC,SAAI,WAAU,wDAAwD,EACvE,oBAAC,SAAI,WAAU,qDAAqD;KAChE;GAEN,qBAAC;IAAI,WAAU;eACb,oBAAC;KAAI,WAAU;eACb,qBAAC;MAAI,WAAU;;OACb,oBAAC,SAAI,WAAU,8CAA8C;OAC7D,oBAAC,SAAI,WAAU,8CAA8C;OAC7D,oBAAC,SAAI,WAAU,8CAA8C;;OACzD;MACF,EAEL,MAAM,KAAK,EAAE,QAAQ,GAAG,CAAC,CAAC,UACzB,oBAAC;KAEC,WAAU;eAEV,qBAAC;MAAI,WAAU;;OACb,oBAAC,SAAI,WAAU,8CAA8C;OAC7D,oBAAC,SAAI,WAAU,8CAA8C;OAC7D,oBAAC,SAAI,WAAU,8CAA8C;;OACzD;OAPD,eAAe,KAAK,QAAQ,GAQ7B,CACN;KACE;GAEN,qBAAC;IAAI,WAAU;;KACb,oBAAC,SAAI,WAAU,4CAA4C;KAC3D,oBAAC,SAAI,WAAU,4CAA4C;KAC3D,oBAAC,SAAI,WAAU,4CAA4C;;KACvD;;GACF"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { useAnalyticsQuery } from "../hooks/use-analytics-query.js";
|
|
2
|
+
import { SAFE_KEY_REGEX, formatChartValue, formatFieldLabel } from "../lib/format.js";
|
|
3
|
+
import { Button } from "../ui/button.js";
|
|
4
|
+
import { Checkbox } from "../ui/checkbox.js";
|
|
5
|
+
import { EmptyState } from "./empty.js";
|
|
6
|
+
import { ErrorState } from "./error.js";
|
|
7
|
+
import { LoadingSkeleton } from "./loading.js";
|
|
8
|
+
import "../index.js";
|
|
9
|
+
import { useEffect, useMemo, useState } from "react";
|
|
10
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
|
|
12
|
+
import { ArrowUpDown } from "lucide-react";
|
|
13
|
+
|
|
14
|
+
//#region src/react/table/table-wrapper.tsx
|
|
15
|
+
const CHECKBOX_COLUMN_WIDTH = 40;
|
|
16
|
+
/**
|
|
17
|
+
* Wrapper component for tables with automatic data fetching and state management
|
|
18
|
+
* This component handles:
|
|
19
|
+
* - Data fetching via useAnalyticsQuery
|
|
20
|
+
* - Loading, error, and empty states with proper UI components
|
|
21
|
+
* - Data transformation (optional)
|
|
22
|
+
* - Dynamic column generation from data structure
|
|
23
|
+
* - TanStack Table instance creation with all features (sorting, filtering, pagination, etc.)
|
|
24
|
+
*
|
|
25
|
+
* @template TRaw - The raw data type returned by the analytics query
|
|
26
|
+
* @template TProcessed - The processed data type after transformation
|
|
27
|
+
*
|
|
28
|
+
* @param props - Props for the TableWrapper component
|
|
29
|
+
* @param props.queryKey - The query key to fetch the data
|
|
30
|
+
* @param props.parameters - The parameters to pass to the query
|
|
31
|
+
* @param props.transformer - Optional function to transform raw data before creating table
|
|
32
|
+
* @param props.children - Render function that receives the TanStack Table instance
|
|
33
|
+
* @param props.className - Optional CSS class name for the wrapper
|
|
34
|
+
* @param props.ariaLabel - Optional accessibility label
|
|
35
|
+
* @param props.testId - Optional test ID for testing
|
|
36
|
+
*
|
|
37
|
+
* @returns The rendered table with state management
|
|
38
|
+
*/
|
|
39
|
+
function TableWrapper(props) {
|
|
40
|
+
const { queryKey, parameters, transformer, children, className, ariaLabel, testId, enableRowSelection = false, onRowSelectionChange, pageSize = 10 } = props;
|
|
41
|
+
const [sorting, setSorting] = useState([]);
|
|
42
|
+
const [columnFilters, setColumnFilters] = useState([]);
|
|
43
|
+
const [columnVisibility, setColumnVisibility] = useState({});
|
|
44
|
+
const [rowSelection, setRowSelection] = useState({});
|
|
45
|
+
const { data, loading, error } = useAnalyticsQuery(queryKey, parameters);
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (onRowSelectionChange && enableRowSelection) onRowSelectionChange(rowSelection);
|
|
48
|
+
}, [
|
|
49
|
+
rowSelection,
|
|
50
|
+
onRowSelectionChange,
|
|
51
|
+
enableRowSelection
|
|
52
|
+
]);
|
|
53
|
+
const hasData = data && data.length > 0;
|
|
54
|
+
const processedData = hasData ? transformer ? transformer(data) : data : [];
|
|
55
|
+
const table = useReactTable({
|
|
56
|
+
data: processedData,
|
|
57
|
+
columns: useMemo(() => {
|
|
58
|
+
if (!hasData) return [];
|
|
59
|
+
if (!processedData[0] || typeof processedData[0] !== "object") {
|
|
60
|
+
console.warn("Invalid data format for DataTable");
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
const dataColumns = Object.keys(processedData[0]).filter((key) => SAFE_KEY_REGEX.test(key)).map((key) => {
|
|
64
|
+
const formattedLabel = formatFieldLabel(key);
|
|
65
|
+
return {
|
|
66
|
+
accessorKey: key,
|
|
67
|
+
header: ({ column }) => {
|
|
68
|
+
return /* @__PURE__ */ jsxs(Button, {
|
|
69
|
+
variant: "ghost",
|
|
70
|
+
onClick: () => column.toggleSorting(column.getIsSorted() === "asc"),
|
|
71
|
+
className: "h-8 px-2",
|
|
72
|
+
children: [formattedLabel, /* @__PURE__ */ jsx(ArrowUpDown, { className: "ml-2 h-4 w-4" })]
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
cell: ({ row }) => {
|
|
76
|
+
const value = row.getValue(key);
|
|
77
|
+
if (typeof value === "number" || Number.isFinite(Number(value))) return /* @__PURE__ */ jsx("div", {
|
|
78
|
+
className: "text-right font-mono",
|
|
79
|
+
children: formatChartValue(Number(value), key)
|
|
80
|
+
});
|
|
81
|
+
return /* @__PURE__ */ jsx("div", { children: String(value) });
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
if (enableRowSelection) return [{
|
|
86
|
+
id: "select",
|
|
87
|
+
maxSize: CHECKBOX_COLUMN_WIDTH,
|
|
88
|
+
minSize: CHECKBOX_COLUMN_WIDTH,
|
|
89
|
+
header: ({ table: table$1 }) => /* @__PURE__ */ jsx("div", {
|
|
90
|
+
className: "flex items-center justify-center",
|
|
91
|
+
children: /* @__PURE__ */ jsx(Checkbox, {
|
|
92
|
+
checked: table$1.getIsAllPageRowsSelected() || table$1.getIsSomePageRowsSelected() && "indeterminate",
|
|
93
|
+
onCheckedChange: (value) => table$1.toggleAllPageRowsSelected(!!value),
|
|
94
|
+
"aria-label": "Select all"
|
|
95
|
+
})
|
|
96
|
+
}),
|
|
97
|
+
cell: ({ row }) => /* @__PURE__ */ jsx("div", {
|
|
98
|
+
className: "flex items-center justify-center",
|
|
99
|
+
children: /* @__PURE__ */ jsx(Checkbox, {
|
|
100
|
+
checked: row.getIsSelected(),
|
|
101
|
+
onCheckedChange: (value) => row.toggleSelected(!!value),
|
|
102
|
+
"aria-label": "Select row"
|
|
103
|
+
})
|
|
104
|
+
}),
|
|
105
|
+
enableSorting: false,
|
|
106
|
+
enableHiding: false
|
|
107
|
+
}, ...dataColumns];
|
|
108
|
+
return dataColumns;
|
|
109
|
+
}, [
|
|
110
|
+
hasData,
|
|
111
|
+
processedData,
|
|
112
|
+
enableRowSelection
|
|
113
|
+
]),
|
|
114
|
+
onSortingChange: setSorting,
|
|
115
|
+
onColumnFiltersChange: setColumnFilters,
|
|
116
|
+
getCoreRowModel: getCoreRowModel(),
|
|
117
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
118
|
+
getSortedRowModel: getSortedRowModel(),
|
|
119
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
120
|
+
onColumnVisibilityChange: setColumnVisibility,
|
|
121
|
+
onRowSelectionChange: setRowSelection,
|
|
122
|
+
state: {
|
|
123
|
+
sorting,
|
|
124
|
+
columnFilters,
|
|
125
|
+
columnVisibility,
|
|
126
|
+
rowSelection
|
|
127
|
+
},
|
|
128
|
+
initialState: { pagination: { pageSize } }
|
|
129
|
+
});
|
|
130
|
+
if (loading) return /* @__PURE__ */ jsx(LoadingSkeleton, {});
|
|
131
|
+
if (error) return /* @__PURE__ */ jsx(ErrorState, { error: typeof error === "string" ? error : "Unknown error" });
|
|
132
|
+
if (!hasData) return /* @__PURE__ */ jsx(EmptyState, {});
|
|
133
|
+
return /* @__PURE__ */ jsx("section", {
|
|
134
|
+
className,
|
|
135
|
+
"aria-label": ariaLabel,
|
|
136
|
+
"data-testid": testId,
|
|
137
|
+
children: children(table)
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
//#endregion
|
|
142
|
+
export { TableWrapper };
|
|
143
|
+
//# sourceMappingURL=table-wrapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table-wrapper.js","names":["table"],"sources":["../../../src/react/table/table-wrapper.tsx"],"sourcesContent":["import {\n type Column,\n type ColumnFiltersState,\n getCoreRowModel,\n getFilteredRowModel,\n getPaginationRowModel,\n getSortedRowModel,\n type Row,\n type RowSelectionState,\n type SortingState,\n type Table,\n useReactTable,\n type VisibilityState,\n} from \"@tanstack/react-table\";\nimport { ArrowUpDown } from \"lucide-react\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport { useAnalyticsQuery } from \"..\";\nimport {\n formatChartValue,\n formatFieldLabel,\n SAFE_KEY_REGEX,\n} from \"../lib/format\";\nimport { Button } from \"../ui/button\";\nimport { Checkbox } from \"../ui/checkbox\";\nimport { EmptyState } from \"./empty\";\nimport { ErrorState } from \"./error\";\nimport { LoadingSkeleton } from \"./loading\";\nimport type { TableWrapperProps } from \"./types\";\n\nconst CHECKBOX_COLUMN_WIDTH = 40;\n\n/**\n * Wrapper component for tables with automatic data fetching and state management\n * This component handles:\n * - Data fetching via useAnalyticsQuery\n * - Loading, error, and empty states with proper UI components\n * - Data transformation (optional)\n * - Dynamic column generation from data structure\n * - TanStack Table instance creation with all features (sorting, filtering, pagination, etc.)\n *\n * @template TRaw - The raw data type returned by the analytics query\n * @template TProcessed - The processed data type after transformation\n *\n * @param props - Props for the TableWrapper component\n * @param props.queryKey - The query key to fetch the data\n * @param props.parameters - The parameters to pass to the query\n * @param props.transformer - Optional function to transform raw data before creating table\n * @param props.children - Render function that receives the TanStack Table instance\n * @param props.className - Optional CSS class name for the wrapper\n * @param props.ariaLabel - Optional accessibility label\n * @param props.testId - Optional test ID for testing\n *\n * @returns The rendered table with state management\n */\nexport function TableWrapper<TRaw = any, TProcessed = any>(\n props: TableWrapperProps<TRaw, TProcessed>,\n) {\n const {\n queryKey,\n parameters,\n transformer,\n children,\n className,\n ariaLabel,\n testId,\n enableRowSelection = false,\n onRowSelectionChange,\n pageSize = 10,\n } = props;\n\n const [sorting, setSorting] = useState<SortingState>([]);\n const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);\n const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});\n const [rowSelection, setRowSelection] = useState<RowSelectionState>({});\n\n const { data, loading, error } = useAnalyticsQuery<TRaw[]>(\n queryKey,\n parameters,\n );\n\n useEffect(() => {\n if (onRowSelectionChange && enableRowSelection) {\n onRowSelectionChange(rowSelection);\n }\n }, [rowSelection, onRowSelectionChange, enableRowSelection]);\n\n const hasData = data && data.length > 0;\n\n const processedData = hasData\n ? transformer\n ? transformer(data)\n : (data as unknown as TProcessed[])\n : [];\n\n const tableColumns = useMemo(() => {\n if (!hasData) return [];\n\n if (!processedData[0] || typeof processedData[0] !== \"object\") {\n console.warn(\"Invalid data format for DataTable\");\n return [];\n }\n\n const dataColumns = Object.keys(processedData[0] as object)\n .filter((key) => SAFE_KEY_REGEX.test(key))\n .map((key) => {\n const formattedLabel = formatFieldLabel(key);\n return {\n accessorKey: key,\n header: ({ column }: { column: Column<TProcessed> }) => {\n return (\n <Button\n variant=\"ghost\"\n onClick={() =>\n column.toggleSorting(column.getIsSorted() === \"asc\")\n }\n className=\"h-8 px-2\"\n >\n {formattedLabel}\n <ArrowUpDown className=\"ml-2 h-4 w-4\" />\n </Button>\n );\n },\n cell: ({ row }: { row: Row<TProcessed> }) => {\n const value = row.getValue(key);\n if (typeof value === \"number\" || Number.isFinite(Number(value))) {\n return (\n <div className=\"text-right font-mono\">\n {formatChartValue(Number(value), key)}\n </div>\n );\n }\n return <div>{String(value)}</div>;\n },\n };\n });\n\n if (enableRowSelection) {\n return [\n {\n id: \"select\",\n maxSize: CHECKBOX_COLUMN_WIDTH,\n minSize: CHECKBOX_COLUMN_WIDTH,\n header: ({ table }: { table: Table<TProcessed> }) => (\n <div className=\"flex items-center justify-center\">\n <Checkbox\n checked={\n table.getIsAllPageRowsSelected() ||\n (table.getIsSomePageRowsSelected() && \"indeterminate\")\n }\n onCheckedChange={(value) =>\n table.toggleAllPageRowsSelected(!!value)\n }\n aria-label=\"Select all\"\n />\n </div>\n ),\n cell: ({ row }: { row: Row<TProcessed> }) => (\n <div className=\"flex items-center justify-center\">\n <Checkbox\n checked={row.getIsSelected()}\n onCheckedChange={(value) => row.toggleSelected(!!value)}\n aria-label=\"Select row\"\n />\n </div>\n ),\n enableSorting: false,\n enableHiding: false,\n },\n ...dataColumns,\n ];\n }\n\n return dataColumns;\n }, [hasData, processedData, enableRowSelection]);\n\n const table = useReactTable({\n data: processedData,\n columns: tableColumns,\n onSortingChange: setSorting,\n onColumnFiltersChange: setColumnFilters,\n getCoreRowModel: getCoreRowModel(),\n getPaginationRowModel: getPaginationRowModel(),\n getSortedRowModel: getSortedRowModel(),\n getFilteredRowModel: getFilteredRowModel(),\n onColumnVisibilityChange: setColumnVisibility,\n onRowSelectionChange: setRowSelection,\n state: {\n sorting,\n columnFilters,\n columnVisibility,\n rowSelection,\n },\n initialState: {\n pagination: {\n pageSize: pageSize,\n },\n },\n });\n\n if (loading) return <LoadingSkeleton />;\n if (error)\n return (\n <ErrorState error={typeof error === \"string\" ? error : \"Unknown error\"} />\n );\n\n if (!hasData) return <EmptyState />;\n\n return (\n <section className={className} aria-label={ariaLabel} data-testid={testId}>\n {children(table)}\n </section>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AA6BA,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;AAyB9B,SAAgB,aACd,OACA;CACA,MAAM,EACJ,UACA,YACA,aACA,UACA,WACA,WACA,QACA,qBAAqB,OACrB,sBACA,WAAW,OACT;CAEJ,MAAM,CAAC,SAAS,cAAc,SAAuB,EAAE,CAAC;CACxD,MAAM,CAAC,eAAe,oBAAoB,SAA6B,EAAE,CAAC;CAC1E,MAAM,CAAC,kBAAkB,uBAAuB,SAA0B,EAAE,CAAC;CAC7E,MAAM,CAAC,cAAc,mBAAmB,SAA4B,EAAE,CAAC;CAEvE,MAAM,EAAE,MAAM,SAAS,UAAU,kBAC/B,UACA,WACD;AAED,iBAAgB;AACd,MAAI,wBAAwB,mBAC1B,sBAAqB,aAAa;IAEnC;EAAC;EAAc;EAAsB;EAAmB,CAAC;CAE5D,MAAM,UAAU,QAAQ,KAAK,SAAS;CAEtC,MAAM,gBAAgB,UAClB,cACE,YAAY,KAAK,GAChB,OACH,EAAE;CAmFN,MAAM,QAAQ,cAAc;EAC1B,MAAM;EACN,SAnFmB,cAAc;AACjC,OAAI,CAAC,QAAS,QAAO,EAAE;AAEvB,OAAI,CAAC,cAAc,MAAM,OAAO,cAAc,OAAO,UAAU;AAC7D,YAAQ,KAAK,oCAAoC;AACjD,WAAO,EAAE;;GAGX,MAAM,cAAc,OAAO,KAAK,cAAc,GAAa,CACxD,QAAQ,QAAQ,eAAe,KAAK,IAAI,CAAC,CACzC,KAAK,QAAQ;IACZ,MAAM,iBAAiB,iBAAiB,IAAI;AAC5C,WAAO;KACL,aAAa;KACb,SAAS,EAAE,aAA6C;AACtD,aACE,qBAAC;OACC,SAAQ;OACR,eACE,OAAO,cAAc,OAAO,aAAa,KAAK,MAAM;OAEtD,WAAU;kBAET,gBACD,oBAAC,eAAY,WAAU,iBAAiB;QACjC;;KAGb,OAAO,EAAE,UAAoC;MAC3C,MAAM,QAAQ,IAAI,SAAS,IAAI;AAC/B,UAAI,OAAO,UAAU,YAAY,OAAO,SAAS,OAAO,MAAM,CAAC,CAC7D,QACE,oBAAC;OAAI,WAAU;iBACZ,iBAAiB,OAAO,MAAM,EAAE,IAAI;QACjC;AAGV,aAAO,oBAAC,mBAAK,OAAO,MAAM,GAAO;;KAEpC;KACD;AAEJ,OAAI,mBACF,QAAO,CACL;IACE,IAAI;IACJ,SAAS;IACT,SAAS;IACT,SAAS,EAAE,qBACT,oBAAC;KAAI,WAAU;eACb,oBAAC;MACC,SACEA,QAAM,0BAA0B,IAC/BA,QAAM,2BAA2B,IAAI;MAExC,kBAAkB,UAChBA,QAAM,0BAA0B,CAAC,CAAC,MAAM;MAE1C,cAAW;OACX;MACE;IAER,OAAO,EAAE,UACP,oBAAC;KAAI,WAAU;eACb,oBAAC;MACC,SAAS,IAAI,eAAe;MAC5B,kBAAkB,UAAU,IAAI,eAAe,CAAC,CAAC,MAAM;MACvD,cAAW;OACX;MACE;IAER,eAAe;IACf,cAAc;IACf,EACD,GAAG,YACJ;AAGH,UAAO;KACN;GAAC;GAAS;GAAe;GAAmB,CAAC;EAK9C,iBAAiB;EACjB,uBAAuB;EACvB,iBAAiB,iBAAiB;EAClC,uBAAuB,uBAAuB;EAC9C,mBAAmB,mBAAmB;EACtC,qBAAqB,qBAAqB;EAC1C,0BAA0B;EAC1B,sBAAsB;EACtB,OAAO;GACL;GACA;GACA;GACA;GACD;EACD,cAAc,EACZ,YAAY,EACA,UACX,EACF;EACF,CAAC;AAEF,KAAI,QAAS,QAAO,oBAAC,oBAAkB;AACvC,KAAI,MACF,QACE,oBAAC,cAAW,OAAO,OAAO,UAAU,WAAW,QAAQ,kBAAmB;AAG9E,KAAI,CAAC,QAAS,QAAO,oBAAC,eAAa;AAEnC,QACE,oBAAC;EAAmB;EAAW,cAAY;EAAW,eAAa;YAChE,SAAS,MAAM;GACR"}
|