@hexclave/dashboard-ui-components 1.0.30 → 1.0.32
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/dist/components/button.d.ts +2 -2
- package/dist/components/chart-legend.d.ts +1 -1
- package/dist/components/data-grid/data-grid-export-dialog.d.ts +23 -0
- package/dist/components/data-grid/data-grid-export-dialog.d.ts.map +1 -0
- package/dist/components/data-grid/data-grid-export-dialog.js +406 -0
- package/dist/components/data-grid/data-grid-export-dialog.js.map +1 -0
- package/dist/components/data-grid/data-grid.d.ts.map +1 -1
- package/dist/components/data-grid/data-grid.js +14 -14
- package/dist/components/data-grid/data-grid.js.map +1 -1
- package/dist/components/data-grid/index.d.ts +2 -2
- package/dist/components/data-grid/types.d.ts +31 -2
- package/dist/components/data-grid/types.d.ts.map +1 -1
- package/dist/dashboard-ui-components.global.js +904 -535
- package/dist/dashboard-ui-components.global.js.map +4 -4
- package/dist/esm/components/button.d.ts +2 -2
- package/dist/esm/components/chart-legend.d.ts +1 -1
- package/dist/esm/components/data-grid/data-grid-export-dialog.d.ts +23 -0
- package/dist/esm/components/data-grid/data-grid-export-dialog.d.ts.map +1 -0
- package/dist/esm/components/data-grid/data-grid-export-dialog.js +403 -0
- package/dist/esm/components/data-grid/data-grid-export-dialog.js.map +1 -0
- package/dist/esm/components/data-grid/data-grid.d.ts.map +1 -1
- package/dist/esm/components/data-grid/data-grid.js +16 -16
- package/dist/esm/components/data-grid/data-grid.js.map +1 -1
- package/dist/esm/components/data-grid/index.d.ts +2 -2
- package/dist/esm/components/data-grid/types.d.ts +31 -2
- package/dist/esm/components/data-grid/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/package.json +3 -3
- package/src/components/data-grid/data-grid-export-dialog.tsx +479 -0
- package/src/components/data-grid/data-grid.tsx +192 -193
- package/src/components/data-grid/index.ts +5 -0
- package/src/components/data-grid/types.ts +35 -0
|
@@ -4,7 +4,7 @@ import * as class_variance_authority_types0 from "class-variance-authority/types
|
|
|
4
4
|
|
|
5
5
|
//#region src/components/button.d.ts
|
|
6
6
|
declare const designButtonVariants: (props?: ({
|
|
7
|
-
variant?: "
|
|
7
|
+
variant?: "link" | "plain" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
|
|
8
8
|
size?: "default" | "sm" | "lg" | "icon" | null | undefined;
|
|
9
9
|
} & class_variance_authority_types0.ClassProp) | undefined) => string;
|
|
10
10
|
type DesignOriginalButtonProps = {
|
|
@@ -36,7 +36,7 @@ declare const DesignButton: React.FC<{
|
|
|
36
36
|
} & {
|
|
37
37
|
asChild?: boolean;
|
|
38
38
|
} & React.ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<(props?: ({
|
|
39
|
-
variant?: "
|
|
39
|
+
variant?: "link" | "plain" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
|
|
40
40
|
size?: "default" | "sm" | "lg" | "icon" | null | undefined;
|
|
41
41
|
} & class_variance_authority_types0.ClassProp) | undefined) => string> & {
|
|
42
42
|
ref?: React.Ref<HTMLButtonElement> | undefined;
|
|
@@ -2,7 +2,7 @@ import * as React$1 from "react";
|
|
|
2
2
|
import * as RechartsPrimitive from "recharts";
|
|
3
3
|
|
|
4
4
|
//#region src/components/chart-legend.d.ts
|
|
5
|
-
declare const DesignChartLegendContent: React$1.ForwardRefExoticComponent<Omit<React$1.ClassAttributes<HTMLDivElement> & React$1.HTMLAttributes<HTMLDivElement> & Pick<RechartsPrimitive.LegendProps, "
|
|
5
|
+
declare const DesignChartLegendContent: React$1.ForwardRefExoticComponent<Omit<React$1.ClassAttributes<HTMLDivElement> & React$1.HTMLAttributes<HTMLDivElement> & Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
|
|
6
6
|
hideIcon?: boolean;
|
|
7
7
|
nameKey?: string;
|
|
8
8
|
}, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
2
|
+
import { DataGridColumnDef, DataGridExportOptions } from "./types";
|
|
3
|
+
|
|
4
|
+
//#region src/components/data-grid/data-grid-export-dialog.d.ts
|
|
5
|
+
type DataGridExportDialogProps<TRow> = {
|
|
6
|
+
open: boolean;
|
|
7
|
+
onOpenChange: (open: boolean) => void;
|
|
8
|
+
rows: readonly TRow[];
|
|
9
|
+
columns: readonly DataGridColumnDef<TRow>[];
|
|
10
|
+
exportFilename: string;
|
|
11
|
+
exportOptions?: DataGridExportOptions<TRow>;
|
|
12
|
+
};
|
|
13
|
+
declare function DataGridExportDialog<TRow>({
|
|
14
|
+
open,
|
|
15
|
+
onOpenChange,
|
|
16
|
+
rows,
|
|
17
|
+
columns,
|
|
18
|
+
exportFilename,
|
|
19
|
+
exportOptions
|
|
20
|
+
}: DataGridExportDialogProps<TRow>): react_jsx_runtime0.JSX.Element;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { DataGridExportDialog };
|
|
23
|
+
//# sourceMappingURL=data-grid-export-dialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-grid-export-dialog.d.ts","names":[],"sources":["../../../../src/components/data-grid/data-grid-export-dialog.tsx"],"mappings":";;;;KA4BK,yBAAA;EACH,IAAA;EACA,YAAA,GAAe,IAAA;EACf,IAAA,WAAe,IAAA;EACf,OAAA,WAAkB,iBAAA,CAAkB,IAAA;EACpC,cAAA;EACA,aAAA,GAAgB,qBAAA,CAAsB,IAAA;AAAA;AAAA,iBASxB,oBAAA,MAAA,CAAA;EACd,IAAA;EACA,YAAA;EACA,IAAA;EACA,OAAA;EACA,cAAA;EACA;AAAA,GACC,yBAAA,CAA0B,IAAA,IAAK,kBAAA,CAAA,GAAA,CAAA,OAAA"}
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { DownloadSimpleIcon } from "@phosphor-icons/react";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
|
6
|
+
import { DesignButton } from "../button.js";
|
|
7
|
+
import { formatGridDate, resolveColumnValue } from "./state.js";
|
|
8
|
+
import { DesignDialog } from "../dialog.js";
|
|
9
|
+
|
|
10
|
+
//#region src/components/data-grid/data-grid-export-dialog.tsx
|
|
11
|
+
const idleExportProgress = {
|
|
12
|
+
phase: "idle",
|
|
13
|
+
fetched: 0
|
|
14
|
+
};
|
|
15
|
+
const exportCompletionDisplayMs = 800;
|
|
16
|
+
function DataGridExportDialog({ open, onOpenChange, rows, columns, exportFilename, exportOptions }) {
|
|
17
|
+
const hasServerExport = exportOptions?.fetchRows != null;
|
|
18
|
+
const resolvedFields = useMemo(() => exportOptions?.fields ?? buildColumnExportFields(columns), [exportOptions?.fields, columns]);
|
|
19
|
+
const [format, setFormat] = useState("csv");
|
|
20
|
+
const [scope, setScope] = useState("all");
|
|
21
|
+
const [fields, setFields] = useState(resolvedFields);
|
|
22
|
+
const [isExporting, setIsExporting] = useState(false);
|
|
23
|
+
const [progress, setProgress] = useState(idleExportProgress);
|
|
24
|
+
const [errorMessage, setErrorMessage] = useState(null);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!isExporting) setFields(resolvedFields);
|
|
27
|
+
}, [isExporting, resolvedFields]);
|
|
28
|
+
exportOptions?.entityName;
|
|
29
|
+
const entityNamePlural = exportOptions?.entityNamePlural ?? "rows";
|
|
30
|
+
const filenamePrefix = exportOptions?.filenamePrefix ?? exportFilename;
|
|
31
|
+
const title = exportOptions?.title ?? "Export data";
|
|
32
|
+
const description = exportOptions?.description ?? (hasServerExport ? "Configure and download data from this table" : "Configure and download the rows currently loaded in this table");
|
|
33
|
+
const allScopeLabel = exportOptions?.allScopeLabel ?? `Export all ${entityNamePlural} in the project`;
|
|
34
|
+
const filteredScopeLabel = exportOptions?.filteredScopeLabel ?? `Export only filtered/searched ${entityNamePlural}`;
|
|
35
|
+
const progressSubjectLabel = exportOptions?.progressSubjectLabel ?? entityNamePlural;
|
|
36
|
+
const progressTitle = progress.phase === "complete" ? "Export complete" : `Exporting ${progressSubjectLabel}`;
|
|
37
|
+
const fetchExportRows = exportOptions?.fetchRows;
|
|
38
|
+
const closeDialog = useCallback(() => {
|
|
39
|
+
onOpenChange(false);
|
|
40
|
+
setErrorMessage(null);
|
|
41
|
+
}, [onOpenChange]);
|
|
42
|
+
const handleOpenChange = useCallback((nextOpen) => {
|
|
43
|
+
if (isExporting && !nextOpen) return;
|
|
44
|
+
if (nextOpen) onOpenChange(true);
|
|
45
|
+
else closeDialog();
|
|
46
|
+
}, [
|
|
47
|
+
closeDialog,
|
|
48
|
+
isExporting,
|
|
49
|
+
onOpenChange
|
|
50
|
+
]);
|
|
51
|
+
const toggleField = useCallback((key) => {
|
|
52
|
+
setFields((prev) => prev.map((field) => field.key === key ? {
|
|
53
|
+
...field,
|
|
54
|
+
enabled: !field.enabled
|
|
55
|
+
} : field));
|
|
56
|
+
}, []);
|
|
57
|
+
const selectAllFields = useCallback(() => {
|
|
58
|
+
setFields((prev) => prev.map((field) => ({
|
|
59
|
+
...field,
|
|
60
|
+
enabled: true
|
|
61
|
+
})));
|
|
62
|
+
}, []);
|
|
63
|
+
const deselectAllFields = useCallback(() => {
|
|
64
|
+
setFields((prev) => prev.map((field) => ({
|
|
65
|
+
...field,
|
|
66
|
+
enabled: false
|
|
67
|
+
})));
|
|
68
|
+
}, []);
|
|
69
|
+
const fetchRows = useCallback(async () => {
|
|
70
|
+
if (fetchExportRows != null) return await fetchExportRows({
|
|
71
|
+
scope,
|
|
72
|
+
onProgress: (fetched) => setProgress({
|
|
73
|
+
phase: "fetching",
|
|
74
|
+
fetched
|
|
75
|
+
})
|
|
76
|
+
});
|
|
77
|
+
setProgress({
|
|
78
|
+
phase: "fetching",
|
|
79
|
+
fetched: rows.length
|
|
80
|
+
});
|
|
81
|
+
return rows;
|
|
82
|
+
}, [
|
|
83
|
+
fetchExportRows,
|
|
84
|
+
rows,
|
|
85
|
+
scope
|
|
86
|
+
]);
|
|
87
|
+
const handleExport = async () => {
|
|
88
|
+
const enabledFields = fields.filter((field) => field.enabled);
|
|
89
|
+
if (enabledFields.length === 0) {
|
|
90
|
+
setErrorMessage("Select at least one field to export.");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
setErrorMessage(null);
|
|
94
|
+
setIsExporting(true);
|
|
95
|
+
setProgress({
|
|
96
|
+
phase: "fetching",
|
|
97
|
+
fetched: 0
|
|
98
|
+
});
|
|
99
|
+
try {
|
|
100
|
+
const exportRows = await fetchRows();
|
|
101
|
+
if (exportRows.length === 0) {
|
|
102
|
+
setErrorMessage(exportOptions?.emptyExportDescription ?? `There are no ${entityNamePlural} to export.`);
|
|
103
|
+
setIsExporting(false);
|
|
104
|
+
setProgress(idleExportProgress);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
setProgress({
|
|
108
|
+
phase: "generating",
|
|
109
|
+
fetched: exportRows.length
|
|
110
|
+
});
|
|
111
|
+
const transformedData = buildExportTable(exportRows, enabledFields);
|
|
112
|
+
if (format === "csv") exportToCsv(transformedData, filenamePrefix);
|
|
113
|
+
else exportToJson(transformedData, filenamePrefix);
|
|
114
|
+
setProgress({
|
|
115
|
+
phase: "complete",
|
|
116
|
+
fetched: exportRows.length
|
|
117
|
+
});
|
|
118
|
+
await new Promise((resolve) => setTimeout(resolve, exportCompletionDisplayMs));
|
|
119
|
+
closeDialog();
|
|
120
|
+
} catch {
|
|
121
|
+
setErrorMessage("Something went wrong while exporting. Please try again.");
|
|
122
|
+
} finally {
|
|
123
|
+
setIsExporting(false);
|
|
124
|
+
setProgress(idleExportProgress);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
return /* @__PURE__ */ jsx(DesignDialog, {
|
|
128
|
+
open,
|
|
129
|
+
onOpenChange: handleOpenChange,
|
|
130
|
+
title: isExporting ? progressTitle : title,
|
|
131
|
+
description: isExporting ? `Preparing export for ${progressSubjectLabel}.` : description,
|
|
132
|
+
size: "2xl",
|
|
133
|
+
variant: "plain",
|
|
134
|
+
headerClassName: isExporting ? "sr-only" : void 0,
|
|
135
|
+
hideTopCloseButton: isExporting,
|
|
136
|
+
children: isExporting ? /* @__PURE__ */ jsx(ExportProgressContent, {
|
|
137
|
+
progress,
|
|
138
|
+
format,
|
|
139
|
+
subjectLabel: progressSubjectLabel
|
|
140
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
141
|
+
className: "space-y-6",
|
|
142
|
+
children: [
|
|
143
|
+
/* @__PURE__ */ jsxs("div", {
|
|
144
|
+
className: "space-y-2",
|
|
145
|
+
children: [/* @__PURE__ */ jsx("label", {
|
|
146
|
+
className: "text-sm font-medium",
|
|
147
|
+
htmlFor: `${filenamePrefix}-export-format`,
|
|
148
|
+
children: "Export Format"
|
|
149
|
+
}), /* @__PURE__ */ jsxs("select", {
|
|
150
|
+
id: `${filenamePrefix}-export-format`,
|
|
151
|
+
value: format,
|
|
152
|
+
onChange: (event) => setFormat(event.currentTarget.value === "json" ? "json" : "csv"),
|
|
153
|
+
className: "h-9 w-full rounded-md border border-input bg-white px-3 text-sm dark:bg-background",
|
|
154
|
+
children: [/* @__PURE__ */ jsx("option", {
|
|
155
|
+
value: "csv",
|
|
156
|
+
children: "CSV (Comma-separated values)"
|
|
157
|
+
}), /* @__PURE__ */ jsx("option", {
|
|
158
|
+
value: "json",
|
|
159
|
+
children: "JSON (JavaScript Object Notation)"
|
|
160
|
+
})]
|
|
161
|
+
})]
|
|
162
|
+
}),
|
|
163
|
+
hasServerExport ? /* @__PURE__ */ jsxs("fieldset", {
|
|
164
|
+
className: "space-y-2",
|
|
165
|
+
children: [
|
|
166
|
+
/* @__PURE__ */ jsx("legend", {
|
|
167
|
+
className: "text-sm font-medium",
|
|
168
|
+
children: "Export Scope"
|
|
169
|
+
}),
|
|
170
|
+
/* @__PURE__ */ jsxs("label", {
|
|
171
|
+
className: "flex cursor-pointer items-center gap-2 text-sm",
|
|
172
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
173
|
+
type: "radio",
|
|
174
|
+
name: `${filenamePrefix}-export-scope`,
|
|
175
|
+
value: "all",
|
|
176
|
+
checked: scope === "all",
|
|
177
|
+
onChange: () => setScope("all")
|
|
178
|
+
}), /* @__PURE__ */ jsx("span", { children: allScopeLabel })]
|
|
179
|
+
}),
|
|
180
|
+
/* @__PURE__ */ jsxs("label", {
|
|
181
|
+
className: "flex cursor-pointer items-center gap-2 text-sm",
|
|
182
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
183
|
+
type: "radio",
|
|
184
|
+
name: `${filenamePrefix}-export-scope`,
|
|
185
|
+
value: "filtered",
|
|
186
|
+
checked: scope === "filtered",
|
|
187
|
+
onChange: () => setScope("filtered")
|
|
188
|
+
}), /* @__PURE__ */ jsx("span", { children: filteredScopeLabel })]
|
|
189
|
+
})
|
|
190
|
+
]
|
|
191
|
+
}) : null,
|
|
192
|
+
/* @__PURE__ */ jsxs("div", {
|
|
193
|
+
className: "space-y-3",
|
|
194
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
195
|
+
className: "flex items-center justify-between gap-3",
|
|
196
|
+
children: [/* @__PURE__ */ jsx("label", {
|
|
197
|
+
className: "text-sm font-medium",
|
|
198
|
+
children: "Fields to Export"
|
|
199
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
200
|
+
className: "flex gap-2",
|
|
201
|
+
children: [/* @__PURE__ */ jsx(DesignButton, {
|
|
202
|
+
type: "button",
|
|
203
|
+
variant: "ghost",
|
|
204
|
+
size: "sm",
|
|
205
|
+
onClick: selectAllFields,
|
|
206
|
+
className: "h-7 text-xs",
|
|
207
|
+
children: "Select All"
|
|
208
|
+
}), /* @__PURE__ */ jsx(DesignButton, {
|
|
209
|
+
type: "button",
|
|
210
|
+
variant: "ghost",
|
|
211
|
+
size: "sm",
|
|
212
|
+
onClick: deselectAllFields,
|
|
213
|
+
className: "h-7 text-xs",
|
|
214
|
+
children: "Deselect All"
|
|
215
|
+
})]
|
|
216
|
+
})]
|
|
217
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
218
|
+
className: "grid max-h-[300px] grid-cols-1 gap-3 overflow-y-auto rounded-lg border border-border p-4 sm:grid-cols-2",
|
|
219
|
+
children: fields.map((field) => /* @__PURE__ */ jsxs("label", {
|
|
220
|
+
className: "flex cursor-pointer items-center gap-2 text-sm font-normal",
|
|
221
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
222
|
+
type: "checkbox",
|
|
223
|
+
checked: field.enabled,
|
|
224
|
+
onChange: () => toggleField(field.key)
|
|
225
|
+
}), /* @__PURE__ */ jsx("span", { children: field.label })]
|
|
226
|
+
}, field.key))
|
|
227
|
+
})]
|
|
228
|
+
}),
|
|
229
|
+
errorMessage != null ? /* @__PURE__ */ jsxs("div", {
|
|
230
|
+
className: "rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive",
|
|
231
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
232
|
+
className: "font-medium",
|
|
233
|
+
children: exportOptions?.emptyExportTitle ?? "Export unavailable"
|
|
234
|
+
}), /* @__PURE__ */ jsx("div", { children: errorMessage })]
|
|
235
|
+
}) : null,
|
|
236
|
+
/* @__PURE__ */ jsxs("div", {
|
|
237
|
+
className: "flex justify-end gap-3 pt-2",
|
|
238
|
+
children: [/* @__PURE__ */ jsx(DesignButton, {
|
|
239
|
+
variant: "outline",
|
|
240
|
+
onClick: closeDialog,
|
|
241
|
+
children: "Cancel"
|
|
242
|
+
}), /* @__PURE__ */ jsxs(DesignButton, {
|
|
243
|
+
onClick: handleExport,
|
|
244
|
+
children: [
|
|
245
|
+
/* @__PURE__ */ jsx(DownloadSimpleIcon, { className: "mr-2 h-4 w-4" }),
|
|
246
|
+
"Export ",
|
|
247
|
+
titleCase(entityNamePlural)
|
|
248
|
+
]
|
|
249
|
+
})]
|
|
250
|
+
})
|
|
251
|
+
]
|
|
252
|
+
})
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
function ExportProgressContent(props) {
|
|
256
|
+
const { progress, format, subjectLabel } = props;
|
|
257
|
+
const fileLabel = format.toUpperCase();
|
|
258
|
+
const isComplete = progress.phase === "complete";
|
|
259
|
+
const title = isComplete ? "Export complete" : `Exporting ${subjectLabel}`;
|
|
260
|
+
const description = isComplete ? `Your ${fileLabel} is ready and the download should begin automatically.` : `Your ${fileLabel} is being prepared from matching ${subjectLabel}.`;
|
|
261
|
+
const statusLabel = progress.phase === "complete" ? "Download ready" : progress.phase === "generating" ? `Preparing ${fileLabel}` : `Fetching ${subjectLabel}`;
|
|
262
|
+
const countLabel = `${progress.fetched.toLocaleString()} ${isComplete ? "exported" : "fetched"}`;
|
|
263
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
264
|
+
className: "space-y-4",
|
|
265
|
+
children: [
|
|
266
|
+
/* @__PURE__ */ jsxs("div", {
|
|
267
|
+
className: "space-y-1",
|
|
268
|
+
children: [/* @__PURE__ */ jsx("h2", {
|
|
269
|
+
className: "text-base font-semibold leading-snug",
|
|
270
|
+
children: title
|
|
271
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
272
|
+
className: "text-sm text-muted-foreground",
|
|
273
|
+
children: description
|
|
274
|
+
})]
|
|
275
|
+
}),
|
|
276
|
+
/* @__PURE__ */ jsxs("div", {
|
|
277
|
+
className: "rounded-xl border border-border bg-muted/35 p-4",
|
|
278
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
279
|
+
className: "mb-3 flex items-center justify-between gap-4 text-sm",
|
|
280
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
281
|
+
className: "font-medium text-foreground",
|
|
282
|
+
children: statusLabel
|
|
283
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
284
|
+
className: "shrink-0 tabular-nums text-muted-foreground",
|
|
285
|
+
children: countLabel
|
|
286
|
+
})]
|
|
287
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
288
|
+
className: "relative h-2 overflow-hidden rounded-full bg-foreground/10",
|
|
289
|
+
children: isComplete ? /* @__PURE__ */ jsx("div", { className: "h-full w-full rounded-full bg-emerald-500/80" }) : /* @__PURE__ */ jsx("div", { className: "data-grid-export-progress-shimmer absolute inset-y-0 left-0 w-2/5 rounded-full bg-gradient-to-r from-transparent via-foreground/65 to-transparent" })
|
|
290
|
+
})]
|
|
291
|
+
}),
|
|
292
|
+
/* @__PURE__ */ jsx("div", {
|
|
293
|
+
className: "rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-900 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-200",
|
|
294
|
+
children: "Do not reload this page until the export finishes. The download will start automatically."
|
|
295
|
+
}),
|
|
296
|
+
/* @__PURE__ */ jsx("div", {
|
|
297
|
+
className: "flex justify-end gap-3 pt-2",
|
|
298
|
+
children: /* @__PURE__ */ jsx(DesignButton, {
|
|
299
|
+
variant: "outline",
|
|
300
|
+
disabled: true,
|
|
301
|
+
children: "Cancel"
|
|
302
|
+
})
|
|
303
|
+
})
|
|
304
|
+
]
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
function buildColumnExportFields(columns) {
|
|
308
|
+
const fields = [];
|
|
309
|
+
for (const column of columns) {
|
|
310
|
+
const label = typeof column.header === "string" ? column.header.trim() : column.id;
|
|
311
|
+
if (label.length === 0) continue;
|
|
312
|
+
fields.push({
|
|
313
|
+
key: column.id,
|
|
314
|
+
label,
|
|
315
|
+
enabled: true,
|
|
316
|
+
getValue: (row) => formatColumnExportValue(column, row)
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
return fields;
|
|
320
|
+
}
|
|
321
|
+
function formatColumnExportValue(column, row) {
|
|
322
|
+
const value = resolveColumnValue(column, row);
|
|
323
|
+
if (column.formatValue != null) return column.formatValue(value, row);
|
|
324
|
+
if (column.type === "date" || column.type === "dateTime") return formatGridDate(value, "absolute", {
|
|
325
|
+
parseValue: column.parseValue,
|
|
326
|
+
dateFormat: column.dateFormat
|
|
327
|
+
}).display ?? "";
|
|
328
|
+
return value;
|
|
329
|
+
}
|
|
330
|
+
function buildExportTable(rows, enabledFields) {
|
|
331
|
+
return {
|
|
332
|
+
csvHeaders: enabledFields.map((field) => field.label),
|
|
333
|
+
jsonKeys: buildJsonKeys(enabledFields),
|
|
334
|
+
rows: rows.map((row) => enabledFields.map((field) => toExportCellValue(field.getValue(row))))
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
function buildJsonKeys(fields) {
|
|
338
|
+
const labelCounts = /* @__PURE__ */ new Map();
|
|
339
|
+
for (const field of fields) labelCounts.set(field.label, (labelCounts.get(field.label) ?? 0) + 1);
|
|
340
|
+
const usedKeys = /* @__PURE__ */ new Map();
|
|
341
|
+
const keys = [];
|
|
342
|
+
for (const field of fields) {
|
|
343
|
+
const baseKey = labelCounts.get(field.label) === 1 ? field.label : `${field.label} (${field.key})`;
|
|
344
|
+
let key = baseKey;
|
|
345
|
+
let suffix = 2;
|
|
346
|
+
while (usedKeys.has(key)) {
|
|
347
|
+
key = `${baseKey} ${suffix}`;
|
|
348
|
+
suffix++;
|
|
349
|
+
}
|
|
350
|
+
usedKeys.set(key, true);
|
|
351
|
+
keys.push(key);
|
|
352
|
+
}
|
|
353
|
+
return keys;
|
|
354
|
+
}
|
|
355
|
+
function toExportCellValue(value) {
|
|
356
|
+
if (value == null) return "";
|
|
357
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
|
358
|
+
if (typeof value === "bigint") return value.toString();
|
|
359
|
+
if (value instanceof Date) return value.toISOString();
|
|
360
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
361
|
+
return String(value);
|
|
362
|
+
}
|
|
363
|
+
function exportToCsv(data, filenamePrefix) {
|
|
364
|
+
downloadFile("" + [data.csvHeaders.map(escapeCsvCell).join(","), ...data.rows.map((row) => row.map(escapeCsvCell).join(","))].join("\n"), `${buildExportFilename(filenamePrefix)}.csv`, "text/csv;charset=utf-8;");
|
|
365
|
+
}
|
|
366
|
+
function escapeCsvCell(value) {
|
|
367
|
+
const rawText = String(value ?? "");
|
|
368
|
+
const text = typeof value === "string" && /^[=+\-@\t\r]/.test(rawText.trimStart()) ? `'${rawText}` : rawText;
|
|
369
|
+
if (text.includes(",") || text.includes("\"") || text.includes("\n") || text.includes("\r")) return `"${text.replace(/"/g, "\"\"")}"`;
|
|
370
|
+
return text;
|
|
371
|
+
}
|
|
372
|
+
function exportToJson(data, filenamePrefix) {
|
|
373
|
+
const rows = data.rows.map((row) => {
|
|
374
|
+
const jsonRow = {};
|
|
375
|
+
for (let i = 0; i < data.jsonKeys.length; i++) jsonRow[data.jsonKeys[i]] = row[i] ?? "";
|
|
376
|
+
return jsonRow;
|
|
377
|
+
});
|
|
378
|
+
downloadFile(JSON.stringify(rows, null, 2), `${buildExportFilename(filenamePrefix)}.json`, "application/json");
|
|
379
|
+
}
|
|
380
|
+
function downloadFile(content, filename, type) {
|
|
381
|
+
const blob = new Blob([content], { type });
|
|
382
|
+
const url = URL.createObjectURL(blob);
|
|
383
|
+
const link = document.createElement("a");
|
|
384
|
+
link.href = url;
|
|
385
|
+
link.download = filename;
|
|
386
|
+
document.body.appendChild(link);
|
|
387
|
+
try {
|
|
388
|
+
link.click();
|
|
389
|
+
} finally {
|
|
390
|
+
link.remove();
|
|
391
|
+
URL.revokeObjectURL(url);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
function buildExportFilename(prefix) {
|
|
395
|
+
return `${prefix}-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`;
|
|
396
|
+
}
|
|
397
|
+
function titleCase(value) {
|
|
398
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
//#endregion
|
|
402
|
+
export { DataGridExportDialog };
|
|
403
|
+
//# sourceMappingURL=data-grid-export-dialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-grid-export-dialog.js","names":[],"sources":["../../../../src/components/data-grid/data-grid-export-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { DownloadSimpleIcon } from \"@phosphor-icons/react\";\nimport React, { useCallback, useEffect, useMemo, useState } from \"react\";\n\nimport { DesignButton } from \"../button\";\nimport { DesignDialog } from \"../dialog\";\nimport { formatGridDate, resolveColumnValue } from \"./state\";\nimport type {\n DataGridColumnDef,\n DataGridExportField,\n DataGridExportFormat,\n DataGridExportOptions,\n DataGridExportScope,\n} from \"./types\";\n\ntype ExportProgress = {\n phase: \"idle\" | \"fetching\" | \"generating\" | \"complete\";\n fetched: number;\n};\n\ntype ExportCellValue = string | number | boolean | null | undefined;\ntype ExportTable = {\n csvHeaders: string[];\n jsonKeys: string[];\n rows: ExportCellValue[][];\n};\n\ntype DataGridExportDialogProps<TRow> = {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n rows: readonly TRow[];\n columns: readonly DataGridColumnDef<TRow>[];\n exportFilename: string;\n exportOptions?: DataGridExportOptions<TRow>;\n};\n\nconst idleExportProgress: ExportProgress = {\n phase: \"idle\",\n fetched: 0,\n};\nconst exportCompletionDisplayMs = 800;\n\nexport function DataGridExportDialog<TRow>({\n open,\n onOpenChange,\n rows,\n columns,\n exportFilename,\n exportOptions,\n}: DataGridExportDialogProps<TRow>) {\n const hasServerExport = exportOptions?.fetchRows != null;\n const resolvedFields = useMemo(\n () => exportOptions?.fields ?? buildColumnExportFields(columns),\n [exportOptions?.fields, columns],\n );\n const [format, setFormat] = useState<DataGridExportFormat>(\"csv\");\n const [scope, setScope] = useState<DataGridExportScope>(\"all\");\n const [fields, setFields] = useState<readonly DataGridExportField<TRow>[]>(resolvedFields);\n const [isExporting, setIsExporting] = useState(false);\n const [progress, setProgress] = useState<ExportProgress>(idleExportProgress);\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n\n useEffect(() => {\n if (!isExporting) {\n setFields(resolvedFields);\n }\n }, [isExporting, resolvedFields]);\n\n const entityName = exportOptions?.entityName ?? \"row\";\n const entityNamePlural = exportOptions?.entityNamePlural ?? \"rows\";\n const filenamePrefix = exportOptions?.filenamePrefix ?? exportFilename;\n const title = exportOptions?.title ?? \"Export data\";\n const description = exportOptions?.description ?? (\n hasServerExport\n ? \"Configure and download data from this table\"\n : \"Configure and download the rows currently loaded in this table\"\n );\n const allScopeLabel = exportOptions?.allScopeLabel ?? `Export all ${entityNamePlural} in the project`;\n const filteredScopeLabel = exportOptions?.filteredScopeLabel ?? `Export only filtered/searched ${entityNamePlural}`;\n const progressSubjectLabel = exportOptions?.progressSubjectLabel ?? entityNamePlural;\n const progressTitle = progress.phase === \"complete\" ? \"Export complete\" : `Exporting ${progressSubjectLabel}`;\n const fetchExportRows = exportOptions?.fetchRows;\n\n const closeDialog = useCallback(() => {\n onOpenChange(false);\n setErrorMessage(null);\n }, [onOpenChange]);\n\n const handleOpenChange = useCallback((nextOpen: boolean) => {\n if (isExporting && !nextOpen) {\n return;\n }\n if (nextOpen) {\n onOpenChange(true);\n } else {\n closeDialog();\n }\n }, [closeDialog, isExporting, onOpenChange]);\n\n const toggleField = useCallback((key: string) => {\n setFields((prev) =>\n prev.map((field) =>\n field.key === key ? { ...field, enabled: !field.enabled } : field\n )\n );\n }, []);\n\n const selectAllFields = useCallback(() => {\n setFields((prev) => prev.map((field) => ({ ...field, enabled: true })));\n }, []);\n\n const deselectAllFields = useCallback(() => {\n setFields((prev) => prev.map((field) => ({ ...field, enabled: false })));\n }, []);\n\n const fetchRows = useCallback(async () => {\n if (fetchExportRows != null) {\n return await fetchExportRows({\n scope,\n onProgress: (fetched) => setProgress({ phase: \"fetching\", fetched }),\n });\n }\n\n setProgress({ phase: \"fetching\", fetched: rows.length });\n return rows;\n }, [fetchExportRows, rows, scope]);\n\n const handleExport = async () => {\n const enabledFields = fields.filter((field) => field.enabled);\n if (enabledFields.length === 0) {\n setErrorMessage(\"Select at least one field to export.\");\n return;\n }\n\n setErrorMessage(null);\n setIsExporting(true);\n setProgress({ phase: \"fetching\", fetched: 0 });\n try {\n const exportRows = await fetchRows();\n\n if (exportRows.length === 0) {\n setErrorMessage(\n exportOptions?.emptyExportDescription\n ?? `There are no ${entityNamePlural} to export.`,\n );\n setIsExporting(false);\n setProgress(idleExportProgress);\n return;\n }\n\n setProgress({ phase: \"generating\", fetched: exportRows.length });\n const transformedData = buildExportTable(exportRows, enabledFields);\n\n if (format === \"csv\") {\n exportToCsv(transformedData, filenamePrefix);\n } else {\n exportToJson(transformedData, filenamePrefix);\n }\n\n setProgress({ phase: \"complete\", fetched: exportRows.length });\n await new Promise<void>((resolve) => setTimeout(resolve, exportCompletionDisplayMs));\n closeDialog();\n } catch {\n setErrorMessage(\"Something went wrong while exporting. Please try again.\");\n } finally {\n setIsExporting(false);\n setProgress(idleExportProgress);\n }\n };\n\n return (\n <DesignDialog\n open={open}\n onOpenChange={handleOpenChange}\n title={isExporting ? progressTitle : title}\n description={isExporting ? `Preparing export for ${progressSubjectLabel}.` : description}\n size=\"2xl\"\n variant=\"plain\"\n headerClassName={isExporting ? \"sr-only\" : undefined}\n hideTopCloseButton={isExporting}\n >\n {isExporting ? (\n <ExportProgressContent\n progress={progress}\n format={format}\n subjectLabel={progressSubjectLabel}\n />\n ) : (\n <div className=\"space-y-6\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor={`${filenamePrefix}-export-format`}>\n Export Format\n </label>\n <select\n id={`${filenamePrefix}-export-format`}\n value={format}\n onChange={(event) => setFormat(event.currentTarget.value === \"json\" ? \"json\" : \"csv\")}\n className=\"h-9 w-full rounded-md border border-input bg-white px-3 text-sm dark:bg-background\"\n >\n <option value=\"csv\">CSV (Comma-separated values)</option>\n <option value=\"json\">JSON (JavaScript Object Notation)</option>\n </select>\n </div>\n\n {hasServerExport ? (\n <fieldset className=\"space-y-2\">\n <legend className=\"text-sm font-medium\">Export Scope</legend>\n <label className=\"flex cursor-pointer items-center gap-2 text-sm\">\n <input\n type=\"radio\"\n name={`${filenamePrefix}-export-scope`}\n value=\"all\"\n checked={scope === \"all\"}\n onChange={() => setScope(\"all\")}\n />\n <span>{allScopeLabel}</span>\n </label>\n <label className=\"flex cursor-pointer items-center gap-2 text-sm\">\n <input\n type=\"radio\"\n name={`${filenamePrefix}-export-scope`}\n value=\"filtered\"\n checked={scope === \"filtered\"}\n onChange={() => setScope(\"filtered\")}\n />\n <span>{filteredScopeLabel}</span>\n </label>\n </fieldset>\n ) : null}\n\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between gap-3\">\n <label className=\"text-sm font-medium\">Fields to Export</label>\n <div className=\"flex gap-2\">\n <DesignButton type=\"button\" variant=\"ghost\" size=\"sm\" onClick={selectAllFields} className=\"h-7 text-xs\">\n Select All\n </DesignButton>\n <DesignButton type=\"button\" variant=\"ghost\" size=\"sm\" onClick={deselectAllFields} className=\"h-7 text-xs\">\n Deselect All\n </DesignButton>\n </div>\n </div>\n <div className=\"grid max-h-[300px] grid-cols-1 gap-3 overflow-y-auto rounded-lg border border-border p-4 sm:grid-cols-2\">\n {fields.map((field) => (\n <label key={field.key} className=\"flex cursor-pointer items-center gap-2 text-sm font-normal\">\n <input\n type=\"checkbox\"\n checked={field.enabled}\n onChange={() => toggleField(field.key)}\n />\n <span>{field.label}</span>\n </label>\n ))}\n </div>\n </div>\n\n {errorMessage != null ? (\n <div className=\"rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive\">\n <div className=\"font-medium\">{exportOptions?.emptyExportTitle ?? \"Export unavailable\"}</div>\n <div>{errorMessage}</div>\n </div>\n ) : null}\n\n <div className=\"flex justify-end gap-3 pt-2\">\n <DesignButton variant=\"outline\" onClick={closeDialog}>\n Cancel\n </DesignButton>\n <DesignButton onClick={handleExport}>\n <DownloadSimpleIcon className=\"mr-2 h-4 w-4\" />\n Export {titleCase(entityNamePlural)}\n </DesignButton>\n </div>\n </div>\n )}\n </DesignDialog>\n );\n}\n\nfunction ExportProgressContent(props: {\n progress: ExportProgress;\n format: DataGridExportFormat;\n subjectLabel: string;\n}) {\n const { progress, format, subjectLabel } = props;\n const fileLabel = format.toUpperCase();\n const isComplete = progress.phase === \"complete\";\n const title = isComplete ? \"Export complete\" : `Exporting ${subjectLabel}`;\n const description = isComplete\n ? `Your ${fileLabel} is ready and the download should begin automatically.`\n : `Your ${fileLabel} is being prepared from matching ${subjectLabel}.`;\n const statusLabel = progress.phase === \"complete\"\n ? \"Download ready\"\n : progress.phase === \"generating\"\n ? `Preparing ${fileLabel}`\n : `Fetching ${subjectLabel}`;\n const countLabel = `${progress.fetched.toLocaleString()} ${isComplete ? \"exported\" : \"fetched\"}`;\n\n return (\n <div className=\"space-y-4\">\n <div className=\"space-y-1\">\n <h2 className=\"text-base font-semibold leading-snug\">{title}</h2>\n <p className=\"text-sm text-muted-foreground\">{description}</p>\n </div>\n\n <div className=\"rounded-xl border border-border bg-muted/35 p-4\">\n <div className=\"mb-3 flex items-center justify-between gap-4 text-sm\">\n <span className=\"font-medium text-foreground\">{statusLabel}</span>\n <span className=\"shrink-0 tabular-nums text-muted-foreground\">\n {countLabel}\n </span>\n </div>\n <div className=\"relative h-2 overflow-hidden rounded-full bg-foreground/10\">\n {isComplete ? (\n <div className=\"h-full w-full rounded-full bg-emerald-500/80\" />\n ) : (\n <div className=\"data-grid-export-progress-shimmer absolute inset-y-0 left-0 w-2/5 rounded-full bg-gradient-to-r from-transparent via-foreground/65 to-transparent\" />\n )}\n </div>\n </div>\n\n <div className=\"rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-900 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-200\">\n Do not reload this page until the export finishes. The download will start automatically.\n </div>\n\n <div className=\"flex justify-end gap-3 pt-2\">\n <DesignButton variant=\"outline\" disabled>\n Cancel\n </DesignButton>\n </div>\n </div>\n );\n}\n\nfunction buildColumnExportFields<TRow>(\n columns: readonly DataGridColumnDef<TRow>[],\n): readonly DataGridExportField<TRow>[] {\n const fields: DataGridExportField<TRow>[] = [];\n\n for (const column of columns) {\n const label = typeof column.header === \"string\" ? column.header.trim() : column.id;\n if (label.length === 0) {\n continue;\n }\n\n fields.push({\n key: column.id,\n label,\n enabled: true,\n getValue: (row) => formatColumnExportValue(column, row),\n });\n }\n\n return fields;\n}\n\nfunction formatColumnExportValue<TRow>(\n column: DataGridColumnDef<TRow>,\n row: TRow,\n): unknown {\n const value = resolveColumnValue(column, row);\n if (column.formatValue != null) {\n return column.formatValue(value, row);\n }\n if (column.type === \"date\" || column.type === \"dateTime\") {\n return formatGridDate(value, \"absolute\", {\n parseValue: column.parseValue,\n dateFormat: column.dateFormat,\n }).display ?? \"\";\n }\n return value;\n}\n\nfunction buildExportTable<TRow>(\n rows: readonly TRow[],\n enabledFields: readonly DataGridExportField<TRow>[],\n): ExportTable {\n return {\n csvHeaders: enabledFields.map((field) => field.label),\n jsonKeys: buildJsonKeys(enabledFields),\n rows: rows.map((row) => enabledFields.map((field) => toExportCellValue(field.getValue(row)))),\n };\n}\n\nfunction buildJsonKeys<TRow>(\n fields: readonly DataGridExportField<TRow>[],\n): string[] {\n const labelCounts = new Map<string, number>();\n for (const field of fields) {\n labelCounts.set(field.label, (labelCounts.get(field.label) ?? 0) + 1);\n }\n\n const usedKeys = new Map<string, true>();\n const keys: string[] = [];\n for (const field of fields) {\n const baseKey = labelCounts.get(field.label) === 1 ? field.label : `${field.label} (${field.key})`;\n let key = baseKey;\n let suffix = 2;\n while (usedKeys.has(key)) {\n key = `${baseKey} ${suffix}`;\n suffix++;\n }\n usedKeys.set(key, true);\n keys.push(key);\n }\n\n return keys;\n}\n\nfunction toExportCellValue(value: unknown): ExportCellValue {\n if (value == null) {\n return \"\";\n }\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n return value;\n }\n if (typeof value === \"bigint\") {\n return value.toString();\n }\n if (value instanceof Date) {\n return value.toISOString();\n }\n if (typeof value === \"object\") {\n return JSON.stringify(value);\n }\n return String(value);\n}\n\nfunction exportToCsv(data: ExportTable, filenamePrefix: string) {\n const csvContent = \"\\uFEFF\" + [\n data.csvHeaders.map(escapeCsvCell).join(\",\"),\n ...data.rows.map((row) => row.map(escapeCsvCell).join(\",\")),\n ].join(\"\\n\");\n downloadFile(csvContent, `${buildExportFilename(filenamePrefix)}.csv`, \"text/csv;charset=utf-8;\");\n}\n\nfunction escapeCsvCell(value: ExportCellValue): string {\n const rawText = String(value ?? \"\");\n const text = typeof value === \"string\" && /^[=+\\-@\\t\\r]/.test(rawText.trimStart()) ? `'${rawText}` : rawText;\n if (text.includes(\",\") || text.includes('\"') || text.includes(\"\\n\") || text.includes(\"\\r\")) {\n return `\"${text.replace(/\"/g, '\"\"')}\"`;\n }\n return text;\n}\n\nfunction exportToJson(data: ExportTable, filenamePrefix: string) {\n const rows = data.rows.map((row) => {\n const jsonRow: Record<string, ExportCellValue> = {};\n for (let i = 0; i < data.jsonKeys.length; i++) {\n jsonRow[data.jsonKeys[i]] = row[i] ?? \"\";\n }\n return jsonRow;\n });\n const jsonString = JSON.stringify(rows, null, 2);\n downloadFile(jsonString, `${buildExportFilename(filenamePrefix)}.json`, \"application/json\");\n}\n\nfunction downloadFile(content: string, filename: string, type: string) {\n const blob = new Blob([content], { type });\n const url = URL.createObjectURL(blob);\n const link = document.createElement(\"a\");\n link.href = url;\n link.download = filename;\n document.body.appendChild(link);\n try {\n link.click();\n } finally {\n link.remove();\n URL.revokeObjectURL(url);\n }\n}\n\nfunction buildExportFilename(prefix: string) {\n return `${prefix}-${new Date().toISOString().split(\"T\")[0]}`;\n}\n\nfunction titleCase(value: string) {\n return value.charAt(0).toUpperCase() + value.slice(1);\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-grid.d.ts","names":[],"sources":["../../../../src/components/data-grid/data-grid.tsx"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"data-grid.d.ts","names":[],"sources":["../../../../src/components/data-grid/data-grid.tsx"],"mappings":";;;;iBAoEgB,mCAAA,CAAoC,MAAA,EAAQ,WAAA;;;AAA5D;;;;;AAqhBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,QAAA,MAAA,CAAe,KAAA,EAAO,aAAA,CAAc,IAAA,IAAK,kBAAA,CAAA,GAAA,CAAA,OAAA"}
|
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
import { ArrowDown, ArrowUp, CaretDown, CaretUp, CheckSquare, MinusSquare, Square } from "@phosphor-icons/react";
|
|
4
4
|
import { cn } from "@hexclave/ui";
|
|
5
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
7
|
-
import {
|
|
7
|
+
import { formatGridDate, resolveColumnValue } from "./state.js";
|
|
8
8
|
import { resolveDataGridStrings } from "./strings.js";
|
|
9
9
|
import { throwErr } from "@hexclave/shared/dist/utils/errors";
|
|
10
10
|
import { getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
|
11
11
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
12
12
|
import { DesignSkeleton } from "../skeleton.js";
|
|
13
13
|
import { DEFAULT_COL_WIDTH, clampColumnWidth, getEffectiveMaxWidth, getEffectiveMinWidth } from "./data-grid-sizing.js";
|
|
14
|
+
import { DataGridExportDialog } from "./data-grid-export-dialog.js";
|
|
14
15
|
import { DataGridToolbar } from "./data-grid-toolbar.js";
|
|
15
16
|
|
|
16
17
|
//#region src/components/data-grid/data-grid.tsx
|
|
@@ -428,7 +429,7 @@ function DefaultFooter({ ctx, pagination, onChange }) {
|
|
|
428
429
|
* - `renderCell` must be a pure function — no React hooks inside.
|
|
429
430
|
*/
|
|
430
431
|
function DataGrid(props) {
|
|
431
|
-
const { columns: allColumns, rows, getRowId, totalRowCount, isLoading = false, isRefetching = false, hasMore = false, isLoadingMore = false, onLoadMore, state, onChange, paginationMode = "paginated", selectionMode = "none", resizable = true, rowHeight: rowHeightProp = 44, estimatedRowHeight: estimatedRowHeightProp, headerHeight = 44, overscan = 5, maxHeight, fillHeight = true, stickyTop, toolbar, toolbarExtra, emptyState, loadingState, footer, footerExtra, exportFilename = "export", strings: stringsOverride, className, onRowClick, onRowDoubleClick, onSelectionChange, onSortChange, onColumnResize, onColumnVisibilityChange } = props;
|
|
432
|
+
const { columns: allColumns, rows, getRowId, totalRowCount, isLoading = false, isRefetching = false, hasMore = false, isLoadingMore = false, onLoadMore, state, onChange, paginationMode = "paginated", selectionMode = "none", resizable = true, rowHeight: rowHeightProp = 44, estimatedRowHeight: estimatedRowHeightProp, headerHeight = 44, overscan = 5, maxHeight, fillHeight = true, stickyTop, toolbar, toolbarExtra, emptyState, loadingState, footer, footerExtra, exportFilename = "export", exportOptions, strings: stringsOverride, className, onRowClick, onRowDoubleClick, onSelectionChange, onSortChange, onColumnResize, onColumnVisibilityChange } = props;
|
|
432
433
|
const isDynamicRowHeight = rowHeightProp === "auto";
|
|
433
434
|
const fixedRowHeight = isDynamicRowHeight ? void 0 : rowHeightProp;
|
|
434
435
|
const estimatedRowHeight = estimatedRowHeightProp ?? fixedRowHeight ?? 44;
|
|
@@ -665,18 +666,10 @@ function DataGrid(props) {
|
|
|
665
666
|
state.selection.selectedIds,
|
|
666
667
|
fireSelection
|
|
667
668
|
]);
|
|
669
|
+
const [exportDialogOpen, setExportDialogOpen] = useState(false);
|
|
668
670
|
const handleExportCsv = useCallback(() => {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
if (!window.confirm(`Export ${rows.length.toLocaleString()} loaded row${rows.length === 1 ? "" : "s"}${totalSuffix}?`)) return;
|
|
672
|
-
}
|
|
673
|
-
exportToCsv(rows, visibleColumns, exportFilename);
|
|
674
|
-
}, [
|
|
675
|
-
rows,
|
|
676
|
-
visibleColumns,
|
|
677
|
-
exportFilename,
|
|
678
|
-
totalRowCount
|
|
679
|
-
]);
|
|
671
|
+
setExportDialogOpen(true);
|
|
672
|
+
}, []);
|
|
680
673
|
const scrollContainerRef = useRef(null);
|
|
681
674
|
const headerScrollRef = useRef(null);
|
|
682
675
|
const stickyChromeRef = useRef(null);
|
|
@@ -768,7 +761,14 @@ function DataGrid(props) {
|
|
|
768
761
|
return m;
|
|
769
762
|
}, [headers]);
|
|
770
763
|
const isBounded = fillHeight || maxHeight != null;
|
|
771
|
-
return /* @__PURE__ */ jsxs(
|
|
764
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(DataGridExportDialog, {
|
|
765
|
+
open: exportDialogOpen,
|
|
766
|
+
onOpenChange: setExportDialogOpen,
|
|
767
|
+
rows,
|
|
768
|
+
columns: visibleColumns,
|
|
769
|
+
exportFilename,
|
|
770
|
+
exportOptions
|
|
771
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
772
772
|
ref: gridRef,
|
|
773
773
|
className: cn("isolate flex w-full min-w-0 max-w-full flex-col bg-transparent rounded-[calc(var(--radius)*2)]", fillHeight ? "min-h-0 h-full" : "min-h-0 h-auto", isBounded && "overflow-hidden", className),
|
|
774
774
|
style: maxHeight != null ? {
|
|
@@ -921,7 +921,7 @@ function DataGrid(props) {
|
|
|
921
921
|
}), footerExtra && (typeof footerExtra === "function" ? footerExtra(footerCtx) : footerExtra)]
|
|
922
922
|
})
|
|
923
923
|
]
|
|
924
|
-
});
|
|
924
|
+
})] });
|
|
925
925
|
}
|
|
926
926
|
|
|
927
927
|
//#endregion
|