@burdenoff/fluidgrids-fe-sdk 2026.1111.15 → 2026.1111.17
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/README.md +41 -0
- package/dist/AiStudioPage-kJHSsIcj.js +774 -0
- package/dist/ApprovalInboxPage-dbBDSJ-f.js +558 -0
- package/dist/DataSinkDashboardPage-1h2QsMzH.js +270 -0
- package/dist/DataTablesPage-BZy2z0LB.js +1690 -0
- package/dist/EntityTagPicker-BqRNsLCQ.js +135 -0
- package/dist/{ExecutionDetailPage-DO38vZ_F.js → ExecutionDetailPage-BdcZmLT4.js} +799 -4420
- package/dist/ExecutionDetailPage-DvQ667OE.d.ts +10 -0
- package/dist/ExecutionsListPage-BCUJoAIE.js +2742 -0
- package/dist/{FailureTriagePanel-BHiduL7A.js → FailureTriagePanel-B9hDQymi.js} +4 -97
- package/dist/{FluidGridsProvider-B9RR_CI_.js → FluidGridsProvider-dvZSNtSW.js} +28 -350
- package/dist/HistoryPage-BLoRpJ1R.d.ts +6 -0
- package/dist/{HistoryPage-CA42pU85.js → HistoryPage-U0tqupBN.js} +6 -4
- package/dist/HumanRequestCard-D-ooVPDn.js +439 -0
- package/dist/{InsightsPage-CBoN_Mj9.js → InsightsPage-CALo16eB.js} +17 -328
- package/dist/ManagedAiPolicyAuthority-BxwJ3ptd.js +40 -0
- package/dist/{ManagedAiPolicyAuthority-CGux_DaV.d.ts → ManagedAiPolicyAuthority-DM3t3wNr.d.ts} +1 -5
- package/dist/ManagedAiPolicyAuthorityContext-CNkxIOZ1.js +10 -0
- package/dist/{MissingNodesInstallModal-Dm0nMBXM.js → MissingNodesInstallModal-CMybsI8h.js} +3 -2
- package/dist/{NodesPage-ClvhUfbh.js → NodesPage-Bf5PhfBK.js} +7 -3
- package/dist/NodesPage-DEbnz9iU.d.ts +6 -0
- package/dist/{PublishCustomNodeAction-D2F7oN0S.js → PublishCustomNodeAction-5XXnSA3H.js} +3 -45
- package/dist/QuotaExhaustedDialog-7IFSaLyi.js +54 -0
- package/dist/RunnerStatusPill-Ctg4TJcP.js +187 -0
- package/dist/RunnersPage-CLCOCclc.js +467 -0
- package/dist/{ManagedAiPolicyAuthority-CKihmbJE.js → SettingsPage-CgkRlCM5.js} +19 -50
- package/dist/SettingsPage-DMHQFj5-.d.ts +9 -0
- package/dist/TagFilterSelect-DCBOH6Ou.js +41 -0
- package/dist/TaskQueuesPage-9VgSnLdL.js +951 -0
- package/dist/TemplateCard-DsC0KNH1.js +1416 -0
- package/dist/{TemplateConfigurationModal-Bs_V-E4u.js → TemplateConfigurationModal-BhGWIzpm.js} +3 -2
- package/dist/{TemplateDetailPanel-MlT39VLG.js → TemplateDetailPanel-D4cUVr-m.js} +3 -3
- package/dist/TemplatePreview-GthzCOSo.js +231 -0
- package/dist/TemplatesPage-BG3H-6v6.d.ts +10 -0
- package/dist/{TemplatesPage-CHVpWBuw.js → TemplatesPage-CsDNT54N.js} +11 -228
- package/dist/VariablesPage-67WR_lVE.js +949 -0
- package/dist/{VersionDiffDialog-Dq2xlPFI.js → VersionDiffDialog-DSN0mU0f.js} +2 -1
- package/dist/WorkflowBuilder-DEi_9YOb.js +555 -0
- package/dist/{WorkflowSearchRedirect-3gyHcYY2.js → WorkflowBuilder-iPqXgbYr.js} +19 -585
- package/dist/{WorkflowProposalCard-8Vr56kQa.js → WorkflowProposalCard-qrEeNyYG.js} +4 -405
- package/dist/WorkflowSearchRedirect-DwDNZ_q5.js +36 -0
- package/dist/{WorkflowShareDialog-0Ai1HED-.js → WorkflowShareDialog-B_gn968c.js} +6 -52
- package/dist/{WorkflowsList-CJ3KmMHX.js → WorkflowsList-CA20dVIl.js} +11 -6
- package/dist/WorkflowsList-CcNDQge5.d.ts +6 -0
- package/dist/agent-form-Bfvm32j1.js +83 -0
- package/dist/{agentTools-B1IK1JEO.js → agentTools-CkRJB7r5.js} +3 -2
- package/dist/{ai-usage-D5p7ZeRR.js → ai-usage-glCwHyQz.js} +1 -1
- package/dist/api-BrCHB5oA.js +224 -0
- package/dist/api-C8e0iV-Q.js +55 -0
- package/dist/button-DG7y_FVJ.js +48 -0
- package/dist/{canvasNoteTools-eewVHVuy.js → canvasNoteTools-CQIc7Oxg.js} +2 -2
- package/dist/client-C9XSzSXr.js +1147 -0
- package/dist/{codec-client-DstqyzTN.js → codec-client-CJ95Rmqo.js} +1 -1
- package/dist/constants-B2lhWNoN.js +202 -0
- package/dist/constants-XdvUxeWK.d.ts +162 -0
- package/dist/dialog-Cjb4lB4D.js +105 -0
- package/dist/executions.d.ts +1 -1
- package/dist/executions.js +2 -1
- package/dist/feature-flags-DM5myWaD.js +296 -0
- package/dist/flags.d.ts +3 -0
- package/dist/flags.js +4 -0
- package/dist/{flow-builder-C_3sY3pZ.js → flow-builder-y2Y1iG4Y.js} +182 -1821
- package/dist/{node-endpoint-configs-DHFGA5MQ.js → graphql-operation-types-kX-MMjgW.js} +1 -1194
- package/dist/{graphql-types-XcDLtFwU.js → graphql-types-CDKVWH2R.js} +2 -202
- package/dist/history.d.ts +1 -1
- package/dist/history.js +1 -1
- package/dist/index-72XFgvbn.d.ts +132 -0
- package/dist/index-BcdtKBM-.d.ts +56 -0
- package/dist/{index-B3BWqafN.d.ts → index-DjiFgEQl.d.ts} +5 -131
- package/dist/{index-DRf25-D3.d.ts → index-W2o__Nm6.d.ts} +32 -214
- package/dist/index.d.ts +13 -9
- package/dist/index.js +50 -5300
- package/dist/{node-detail-view-CAws5qjg.js → node-detail-view-B91kXzIy.js} +11 -8
- package/dist/node-endpoint-configs-B5bjNMDW.js +37 -0
- package/dist/node-registry-refresh-Dy0uRg9f.js +84 -0
- package/dist/nodes.d.ts +1 -1
- package/dist/nodes.js +1 -1
- package/dist/observability.d.ts +1 -1
- package/dist/observability.js +2 -1
- package/dist/{quick-config-panel-DOzNkSVV.js → quick-config-panel-DggPl1vY.js} +2 -2
- package/dist/rolldown-runtime-hDbaFkMy.js +18 -0
- package/dist/run-display-BycKYSU0.js +46 -0
- package/dist/run-list-links-ByYC0OlV.js +98 -0
- package/dist/settings.d.ts +2 -1
- package/dist/settings.js +2 -1
- package/dist/shared-CMfn30xT.js +196 -0
- package/dist/templates.d.ts +1 -1
- package/dist/templates.js +2 -1
- package/dist/theme-provider-B3mIuz2d.js +11 -0
- package/dist/{useAgentDefinitions-NFtLytDR.js → useAgentDefinitions-Dpz0HDTq.js} +2 -1
- package/dist/useDurableParity-CzUQMFJ3.js +415 -0
- package/dist/useEntityTags-DhnsZcTl.js +236 -0
- package/dist/{useExecutions-DkS0FTfw.js → useExecutions-9aLuOt1c.js} +4 -3
- package/dist/{useObservability-BkxhHZsX.js → useObservability-CfEw3ZFD.js} +2 -2
- package/dist/{useRunHistory-DHehG09Z.js → useRunHistory-B1xJDwl1.js} +4 -3
- package/dist/{useWorkflows-DyhtoVwp.js → useWorkflows-BlVVu8NM.js} +3 -2
- package/dist/utils-uaL5UVxb.js +65 -0
- package/dist/workflow-builder.d.ts +1 -1
- package/dist/workflow-builder.js +4 -2
- package/dist/workflow-host.d.ts +6 -0
- package/dist/workflow-host.js +9 -0
- package/dist/workflow-pages.d.ts +55 -0
- package/dist/workflow-pages.js +113 -0
- package/dist/workflows-list.d.ts +1 -1
- package/dist/workflows-list.js +1 -1
- package/package.json +13 -1
- package/dist/ExecutionDetailPage-MvsnOnE1.d.ts +0 -4
- package/dist/HistoryPage-BqUizaFv.d.ts +0 -3
- package/dist/NodesPage-C97-9K2Y.d.ts +0 -3
- package/dist/TemplatesPage-D0qnXzHh.d.ts +0 -4
- package/dist/WorkflowsList-0d5GSKdh.d.ts +0 -3
- package/dist/agent-form-BvMWhQ42.js +0 -496
|
@@ -0,0 +1,1690 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { t as __exportAll } from "./rolldown-runtime-hDbaFkMy.js";
|
|
3
|
+
import { t as EntityTagPicker } from "./EntityTagPicker-BqRNsLCQ.js";
|
|
4
|
+
import { t as TagFilterSelect } from "./TagFilterSelect-DCBOH6Ou.js";
|
|
5
|
+
import { _ as throwIfResultError, m as describeOperationError, n as getFluidGridsClient } from "./client-C9XSzSXr.js";
|
|
6
|
+
import { a as useDataTableTagOverlay, n as tagLabel } from "./useEntityTags-DhnsZcTl.js";
|
|
7
|
+
import { r as useTr } from "./TranslationBridge-Da-TPgQm.js";
|
|
8
|
+
import { i as SdkPermissionButton, s as useFluidGridsPermission, t as FLUIDGRIDS_PERMISSIONS } from "./constants-B2lhWNoN.js";
|
|
9
|
+
import { t as FluidGridsDocsLink } from "./FluidGridsDocsLink-JxkBpOeb.js";
|
|
10
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
11
|
+
import { useNavigate } from "react-router";
|
|
12
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
13
|
+
import { toast } from "sonner";
|
|
14
|
+
import { gql } from "@apollo/client";
|
|
15
|
+
import { Badge, Card, CardContent, CardDescription, CardHeader, CardTitle, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, IllustratedEmptyState, Input, Label, NextSteps, PagePurpose, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Textarea } from "@burdenoff/fe-libs/ui";
|
|
16
|
+
import { Button as Button$1 } from "@burdenoff/fe-libs/button";
|
|
17
|
+
import { ArrowDownToLine, ArrowLeft, ArrowUpFromLine, ChevronLeft, ChevronRight, Database, Pencil, Plus, RefreshCw, Search, Table2, Trash2 } from "lucide-react";
|
|
18
|
+
import { TagBadge } from "@burdenoff/fe-libs/tag";
|
|
19
|
+
|
|
20
|
+
// src/modules/data-tables/hooks/useDataTables.ts
|
|
21
|
+
const DATA_TABLE_FIELDS = gql`
|
|
22
|
+
fragment DataTableFields on DataTable {
|
|
23
|
+
id
|
|
24
|
+
workspaceId
|
|
25
|
+
name
|
|
26
|
+
description
|
|
27
|
+
columns {
|
|
28
|
+
key
|
|
29
|
+
label
|
|
30
|
+
type
|
|
31
|
+
required
|
|
32
|
+
defaultValue
|
|
33
|
+
}
|
|
34
|
+
rowCount
|
|
35
|
+
sizeBytes
|
|
36
|
+
createdAt
|
|
37
|
+
updatedAt
|
|
38
|
+
createdBy
|
|
39
|
+
updatedBy
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
const DATA_TABLE_ROW_FIELDS = gql`
|
|
43
|
+
fragment DataTableRowFields on DataTableRow {
|
|
44
|
+
id
|
|
45
|
+
tableId
|
|
46
|
+
data
|
|
47
|
+
createdAt
|
|
48
|
+
updatedAt
|
|
49
|
+
}
|
|
50
|
+
`;
|
|
51
|
+
const GET_DATA_TABLES = gql`
|
|
52
|
+
${DATA_TABLE_FIELDS}
|
|
53
|
+
query GetDataTables($search: String, $limit: Int, $offset: Int) {
|
|
54
|
+
dataTables(search: $search, limit: $limit, offset: $offset) {
|
|
55
|
+
tables {
|
|
56
|
+
...DataTableFields
|
|
57
|
+
}
|
|
58
|
+
total
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
`;
|
|
62
|
+
const GET_DATA_TABLES_BY_TAG = gql`
|
|
63
|
+
${DATA_TABLE_FIELDS}
|
|
64
|
+
query GetDataTablesByTag(
|
|
65
|
+
$search: String
|
|
66
|
+
$limit: Int
|
|
67
|
+
$offset: Int
|
|
68
|
+
$tagIds: [ID!]
|
|
69
|
+
) {
|
|
70
|
+
dataTables(
|
|
71
|
+
search: $search
|
|
72
|
+
limit: $limit
|
|
73
|
+
offset: $offset
|
|
74
|
+
tagIds: $tagIds
|
|
75
|
+
) {
|
|
76
|
+
tables {
|
|
77
|
+
...DataTableFields
|
|
78
|
+
}
|
|
79
|
+
total
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
`;
|
|
83
|
+
const GET_DATA_TABLE_ROWS = gql`
|
|
84
|
+
${DATA_TABLE_ROW_FIELDS}
|
|
85
|
+
query GetDataTableRows($input: DataTableRowsQueryInput!) {
|
|
86
|
+
dataTableRows(input: $input) {
|
|
87
|
+
rows {
|
|
88
|
+
...DataTableRowFields
|
|
89
|
+
}
|
|
90
|
+
total
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
`;
|
|
94
|
+
const CREATE_DATA_TABLE = gql`
|
|
95
|
+
${DATA_TABLE_FIELDS}
|
|
96
|
+
mutation CreateDataTable($input: CreateDataTableInput!) {
|
|
97
|
+
createDataTable(input: $input) {
|
|
98
|
+
success
|
|
99
|
+
message
|
|
100
|
+
table {
|
|
101
|
+
...DataTableFields
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
`;
|
|
106
|
+
const UPDATE_DATA_TABLE = gql`
|
|
107
|
+
${DATA_TABLE_FIELDS}
|
|
108
|
+
mutation UpdateDataTable($id: ID!, $input: UpdateDataTableInput!) {
|
|
109
|
+
updateDataTable(id: $id, input: $input) {
|
|
110
|
+
success
|
|
111
|
+
message
|
|
112
|
+
table {
|
|
113
|
+
...DataTableFields
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
`;
|
|
118
|
+
const DELETE_DATA_TABLE = gql`
|
|
119
|
+
mutation DeleteDataTable($id: ID!) {
|
|
120
|
+
deleteDataTable(id: $id) {
|
|
121
|
+
success
|
|
122
|
+
message
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
`;
|
|
126
|
+
const INSERT_DATA_TABLE_ROW = gql`
|
|
127
|
+
${DATA_TABLE_ROW_FIELDS}
|
|
128
|
+
mutation InsertDataTableRow($tableId: ID!, $data: JSON!) {
|
|
129
|
+
insertDataTableRow(tableId: $tableId, data: $data) {
|
|
130
|
+
success
|
|
131
|
+
message
|
|
132
|
+
row {
|
|
133
|
+
...DataTableRowFields
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
`;
|
|
138
|
+
const UPDATE_DATA_TABLE_ROW = gql`
|
|
139
|
+
${DATA_TABLE_ROW_FIELDS}
|
|
140
|
+
mutation UpdateDataTableRow($id: ID!, $data: JSON!) {
|
|
141
|
+
updateDataTableRow(id: $id, data: $data) {
|
|
142
|
+
success
|
|
143
|
+
message
|
|
144
|
+
row {
|
|
145
|
+
...DataTableRowFields
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
`;
|
|
150
|
+
const DELETE_DATA_TABLE_ROWS = gql`
|
|
151
|
+
mutation DeleteDataTableRows($tableId: ID!, $ids: [ID!]!) {
|
|
152
|
+
deleteDataTableRows(tableId: $tableId, ids: $ids) {
|
|
153
|
+
success
|
|
154
|
+
message
|
|
155
|
+
affected
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
`;
|
|
159
|
+
function toRow(row) {
|
|
160
|
+
return {
|
|
161
|
+
id: row.id,
|
|
162
|
+
tableId: row.tableId,
|
|
163
|
+
data: toRecord(row.data),
|
|
164
|
+
createdAt: row.createdAt,
|
|
165
|
+
updatedAt: row.updatedAt
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function toRecord(value) {
|
|
169
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
170
|
+
}
|
|
171
|
+
function operationError(error, fallback) {
|
|
172
|
+
return new Error(describeOperationError(error, fallback));
|
|
173
|
+
}
|
|
174
|
+
/** The workspace's data tables, optionally narrowed by name and by one tag (G47). */
|
|
175
|
+
function useDataTables(search, tagId) {
|
|
176
|
+
const [tables, setTables] = useState([]);
|
|
177
|
+
const [total, setTotal] = useState(0);
|
|
178
|
+
const [loading, setLoading] = useState(true);
|
|
179
|
+
const [error, setError] = useState(null);
|
|
180
|
+
const fetchTables = useCallback(async () => {
|
|
181
|
+
setLoading(true);
|
|
182
|
+
setError(null);
|
|
183
|
+
try {
|
|
184
|
+
const result = throwIfResultError(await getFluidGridsClient().query({
|
|
185
|
+
query: tagId ? GET_DATA_TABLES_BY_TAG : GET_DATA_TABLES,
|
|
186
|
+
variables: {
|
|
187
|
+
search: search?.trim() || void 0,
|
|
188
|
+
limit: 200,
|
|
189
|
+
offset: 0,
|
|
190
|
+
...tagId ? { tagIds: [tagId] } : {}
|
|
191
|
+
},
|
|
192
|
+
fetchPolicy: "network-only"
|
|
193
|
+
}));
|
|
194
|
+
setTables(result.data?.dataTables.tables ?? []);
|
|
195
|
+
setTotal(result.data?.dataTables.total ?? 0);
|
|
196
|
+
} catch (err) {
|
|
197
|
+
setError(operationError(err, "Failed to load data tables"));
|
|
198
|
+
} finally {
|
|
199
|
+
setLoading(false);
|
|
200
|
+
}
|
|
201
|
+
}, [search, tagId]);
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
fetchTables();
|
|
204
|
+
}, [fetchTables]);
|
|
205
|
+
return {
|
|
206
|
+
tables,
|
|
207
|
+
total,
|
|
208
|
+
loading,
|
|
209
|
+
error,
|
|
210
|
+
refetch: fetchTables
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/** Largest page the service serves for a single `dataTableRows` request. */
|
|
214
|
+
const DATA_TABLE_MAX_PAGE_SIZE = 5e3;
|
|
215
|
+
async function queryRowsPage(tableId, limit, offset) {
|
|
216
|
+
const response = throwIfResultError(await getFluidGridsClient().query({
|
|
217
|
+
query: GET_DATA_TABLE_ROWS,
|
|
218
|
+
variables: { input: {
|
|
219
|
+
tableId,
|
|
220
|
+
limit,
|
|
221
|
+
offset
|
|
222
|
+
} },
|
|
223
|
+
fetchPolicy: "network-only"
|
|
224
|
+
})).data?.dataTableRows;
|
|
225
|
+
return {
|
|
226
|
+
rows: response?.rows.map(toRow) ?? [],
|
|
227
|
+
total: response?.total ?? 0
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Fetch every row of a table in service-sized batches (used by CSV export so
|
|
232
|
+
* it covers the whole table rather than the page currently on screen). A table
|
|
233
|
+
* is capped at 50k rows server-side, so this is at most ten requests.
|
|
234
|
+
*/
|
|
235
|
+
async function fetchAllDataTableRows(tableId) {
|
|
236
|
+
const collected = [];
|
|
237
|
+
let total = Number.POSITIVE_INFINITY;
|
|
238
|
+
while (collected.length < total) {
|
|
239
|
+
const page = await queryRowsPage(tableId, DATA_TABLE_MAX_PAGE_SIZE, collected.length);
|
|
240
|
+
total = page.total;
|
|
241
|
+
if (page.rows.length === 0) break;
|
|
242
|
+
collected.push(...page.rows);
|
|
243
|
+
}
|
|
244
|
+
return collected;
|
|
245
|
+
}
|
|
246
|
+
function useDataTableRows(tableId, paging) {
|
|
247
|
+
const { page, pageSize } = paging;
|
|
248
|
+
const [rows, setRows] = useState([]);
|
|
249
|
+
const [total, setTotal] = useState(0);
|
|
250
|
+
const [loading, setLoading] = useState(false);
|
|
251
|
+
const [error, setError] = useState(null);
|
|
252
|
+
const fetchRows = useCallback(async () => {
|
|
253
|
+
if (!tableId) {
|
|
254
|
+
setRows([]);
|
|
255
|
+
setTotal(0);
|
|
256
|
+
setLoading(false);
|
|
257
|
+
setError(null);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
setLoading(true);
|
|
261
|
+
setError(null);
|
|
262
|
+
try {
|
|
263
|
+
const response = await queryRowsPage(tableId, pageSize, page * pageSize);
|
|
264
|
+
setRows(response.rows);
|
|
265
|
+
setTotal(response.total);
|
|
266
|
+
} catch (err) {
|
|
267
|
+
setError(operationError(err, "Failed to load data table rows"));
|
|
268
|
+
} finally {
|
|
269
|
+
setLoading(false);
|
|
270
|
+
}
|
|
271
|
+
}, [
|
|
272
|
+
tableId,
|
|
273
|
+
page,
|
|
274
|
+
pageSize
|
|
275
|
+
]);
|
|
276
|
+
useEffect(() => {
|
|
277
|
+
fetchRows();
|
|
278
|
+
}, [fetchRows]);
|
|
279
|
+
return {
|
|
280
|
+
rows,
|
|
281
|
+
total,
|
|
282
|
+
loading,
|
|
283
|
+
error,
|
|
284
|
+
refetch: fetchRows
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function useCreateDataTable() {
|
|
288
|
+
const [loading, setLoading] = useState(false);
|
|
289
|
+
return {
|
|
290
|
+
createDataTable: useCallback(async (input) => {
|
|
291
|
+
setLoading(true);
|
|
292
|
+
try {
|
|
293
|
+
const response = throwIfResultError(await getFluidGridsClient().mutate({
|
|
294
|
+
mutation: CREATE_DATA_TABLE,
|
|
295
|
+
variables: { input }
|
|
296
|
+
})).data?.createDataTable;
|
|
297
|
+
if (!response?.success) throw new Error(response?.message || "Failed to create data table");
|
|
298
|
+
return response.table ?? null;
|
|
299
|
+
} finally {
|
|
300
|
+
setLoading(false);
|
|
301
|
+
}
|
|
302
|
+
}, []),
|
|
303
|
+
loading
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
function useUpdateDataTable() {
|
|
307
|
+
const [loading, setLoading] = useState(false);
|
|
308
|
+
return {
|
|
309
|
+
updateDataTable: useCallback(async (id, input) => {
|
|
310
|
+
setLoading(true);
|
|
311
|
+
try {
|
|
312
|
+
const response = throwIfResultError(await getFluidGridsClient().mutate({
|
|
313
|
+
mutation: UPDATE_DATA_TABLE,
|
|
314
|
+
variables: {
|
|
315
|
+
id,
|
|
316
|
+
input
|
|
317
|
+
}
|
|
318
|
+
})).data?.updateDataTable;
|
|
319
|
+
if (!response?.success) throw new Error(response?.message || "Failed to update data table");
|
|
320
|
+
return response.table ?? null;
|
|
321
|
+
} finally {
|
|
322
|
+
setLoading(false);
|
|
323
|
+
}
|
|
324
|
+
}, []),
|
|
325
|
+
loading
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function useDeleteDataTable() {
|
|
329
|
+
const [loading, setLoading] = useState(false);
|
|
330
|
+
return {
|
|
331
|
+
deleteDataTable: useCallback(async (id) => {
|
|
332
|
+
setLoading(true);
|
|
333
|
+
try {
|
|
334
|
+
const response = throwIfResultError(await getFluidGridsClient().mutate({
|
|
335
|
+
mutation: DELETE_DATA_TABLE,
|
|
336
|
+
variables: { id }
|
|
337
|
+
})).data?.deleteDataTable;
|
|
338
|
+
if (!response?.success) throw new Error(response?.message || "Failed to delete data table");
|
|
339
|
+
return true;
|
|
340
|
+
} finally {
|
|
341
|
+
setLoading(false);
|
|
342
|
+
}
|
|
343
|
+
}, []),
|
|
344
|
+
loading
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function useInsertDataTableRow() {
|
|
348
|
+
const [loading, setLoading] = useState(false);
|
|
349
|
+
return {
|
|
350
|
+
insertDataTableRow: useCallback(async (tableId, data) => {
|
|
351
|
+
setLoading(true);
|
|
352
|
+
try {
|
|
353
|
+
const response = throwIfResultError(await getFluidGridsClient().mutate({
|
|
354
|
+
mutation: INSERT_DATA_TABLE_ROW,
|
|
355
|
+
variables: {
|
|
356
|
+
tableId,
|
|
357
|
+
data
|
|
358
|
+
}
|
|
359
|
+
})).data?.insertDataTableRow;
|
|
360
|
+
if (!response?.success) throw new Error(response?.message || "Failed to insert row");
|
|
361
|
+
return response.row ? toRow(response.row) : null;
|
|
362
|
+
} finally {
|
|
363
|
+
setLoading(false);
|
|
364
|
+
}
|
|
365
|
+
}, []),
|
|
366
|
+
loading
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function useUpdateDataTableRow() {
|
|
370
|
+
const [loading, setLoading] = useState(false);
|
|
371
|
+
return {
|
|
372
|
+
updateDataTableRow: useCallback(async (id, data) => {
|
|
373
|
+
setLoading(true);
|
|
374
|
+
try {
|
|
375
|
+
const response = throwIfResultError(await getFluidGridsClient().mutate({
|
|
376
|
+
mutation: UPDATE_DATA_TABLE_ROW,
|
|
377
|
+
variables: {
|
|
378
|
+
id,
|
|
379
|
+
data
|
|
380
|
+
}
|
|
381
|
+
})).data?.updateDataTableRow;
|
|
382
|
+
if (!response?.success) throw new Error(response?.message || "Failed to update row");
|
|
383
|
+
return response.row ? toRow(response.row) : null;
|
|
384
|
+
} finally {
|
|
385
|
+
setLoading(false);
|
|
386
|
+
}
|
|
387
|
+
}, []),
|
|
388
|
+
loading
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
function useDeleteDataTableRows() {
|
|
392
|
+
const [loading, setLoading] = useState(false);
|
|
393
|
+
return {
|
|
394
|
+
deleteDataTableRows: useCallback(async (tableId, ids) => {
|
|
395
|
+
setLoading(true);
|
|
396
|
+
try {
|
|
397
|
+
const response = throwIfResultError(await getFluidGridsClient().mutate({
|
|
398
|
+
mutation: DELETE_DATA_TABLE_ROWS,
|
|
399
|
+
variables: {
|
|
400
|
+
tableId,
|
|
401
|
+
ids
|
|
402
|
+
}
|
|
403
|
+
})).data?.deleteDataTableRows;
|
|
404
|
+
if (!response?.success) throw new Error(response?.message || "Failed to delete rows");
|
|
405
|
+
return response.affected;
|
|
406
|
+
} finally {
|
|
407
|
+
setLoading(false);
|
|
408
|
+
}
|
|
409
|
+
}, []),
|
|
410
|
+
loading
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// src/modules/data-tables/utils/csv.ts
|
|
415
|
+
/** Parse RFC 4180-style CSV while preserving quoted commas and line breaks. */
|
|
416
|
+
function parseCsv(text) {
|
|
417
|
+
const rows = [];
|
|
418
|
+
let row = [];
|
|
419
|
+
let cell = "";
|
|
420
|
+
let quoted = false;
|
|
421
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
422
|
+
const character = text[index];
|
|
423
|
+
const next = text[index + 1];
|
|
424
|
+
if (quoted) {
|
|
425
|
+
if (character === "\"" && next === "\"") {
|
|
426
|
+
cell += "\"";
|
|
427
|
+
index += 1;
|
|
428
|
+
} else if (character === "\"") quoted = false;
|
|
429
|
+
else cell += character;
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
if (character === "\"" && cell.length === 0) quoted = true;
|
|
433
|
+
else if (character === ",") {
|
|
434
|
+
row.push(cell);
|
|
435
|
+
cell = "";
|
|
436
|
+
} else if (character === "\n" || character === "\r") {
|
|
437
|
+
if (character === "\r" && next === "\n") index += 1;
|
|
438
|
+
row.push(cell);
|
|
439
|
+
cell = "";
|
|
440
|
+
if (row.some((value) => value.length > 0)) rows.push(row);
|
|
441
|
+
row = [];
|
|
442
|
+
} else cell += character;
|
|
443
|
+
}
|
|
444
|
+
if (quoted) throw new Error("CSV contains an unterminated quoted value");
|
|
445
|
+
if (cell.length > 0 || row.length > 0) {
|
|
446
|
+
row.push(cell);
|
|
447
|
+
if (row.some((value) => value.length > 0)) rows.push(row);
|
|
448
|
+
}
|
|
449
|
+
return rows;
|
|
450
|
+
}
|
|
451
|
+
function parseValue(value, type) {
|
|
452
|
+
if (value === "") return type === "STRING" ? "" : null;
|
|
453
|
+
switch (type) {
|
|
454
|
+
case "NUMBER": {
|
|
455
|
+
const parsed = Number(value);
|
|
456
|
+
if (!Number.isFinite(parsed)) throw new Error(`Expected a number, got "${value}"`);
|
|
457
|
+
return parsed;
|
|
458
|
+
}
|
|
459
|
+
case "BOOLEAN": {
|
|
460
|
+
const normalized = value.trim().toLowerCase();
|
|
461
|
+
if (normalized === "true") return true;
|
|
462
|
+
if (normalized === "false") return false;
|
|
463
|
+
throw new Error(`Expected true or false, got "${value}"`);
|
|
464
|
+
}
|
|
465
|
+
case "JSON": try {
|
|
466
|
+
return JSON.parse(value);
|
|
467
|
+
} catch {
|
|
468
|
+
throw new Error(`Expected valid JSON, got "${value}"`);
|
|
469
|
+
}
|
|
470
|
+
case "DATE":
|
|
471
|
+
case "STRING": return value;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
/** Convert a CSV whose first row contains column keys into validated row data. */
|
|
475
|
+
function csvToRows(text, columns) {
|
|
476
|
+
const parsed = parseCsv(text);
|
|
477
|
+
if (parsed.length < 2) return [];
|
|
478
|
+
const headers = parsed[0].map((header) => header.trim());
|
|
479
|
+
const columnByKey = new Map(columns.map((column) => [column.key, column]));
|
|
480
|
+
const seen = /* @__PURE__ */ new Set();
|
|
481
|
+
for (const header of headers) {
|
|
482
|
+
if (!header || seen.has(header)) throw new Error("CSV must have unique, non-empty column headers");
|
|
483
|
+
if (!columnByKey.has(header)) throw new Error(`CSV contains an unknown column "${header}"`);
|
|
484
|
+
seen.add(header);
|
|
485
|
+
}
|
|
486
|
+
return parsed.slice(1).map((values, rowIndex) => {
|
|
487
|
+
const row = {};
|
|
488
|
+
headers.forEach((header, columnIndex) => {
|
|
489
|
+
const column = columnByKey.get(header);
|
|
490
|
+
if (!column) return;
|
|
491
|
+
const rawValue = values[columnIndex] ?? "";
|
|
492
|
+
try {
|
|
493
|
+
row[header] = parseValue(rawValue, column.type);
|
|
494
|
+
} catch (error) {
|
|
495
|
+
const message = error instanceof Error ? error.message : "Invalid value";
|
|
496
|
+
throw new Error(`Row ${rowIndex + 2}, ${column.label}: ${message}`);
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
return row;
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
function csvCell(value) {
|
|
503
|
+
if (value === null || value === void 0) return "";
|
|
504
|
+
const text = typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
505
|
+
return /[",\r\n]/.test(text) ? `"${text.replaceAll("\"", "\"\"")}"` : text;
|
|
506
|
+
}
|
|
507
|
+
/** Serialize table rows with a stable header matching the table schema. */
|
|
508
|
+
function rowsToCsv(columns, rows) {
|
|
509
|
+
return [columns.map((column) => csvCell(column.key)).join(","), ...rows.map((row) => columns.map((column) => csvCell(row.data[column.key])).join(","))].join("\r\n");
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// src/modules/data-tables/utils/paging.ts
|
|
513
|
+
/** Page sizes offered in the rows pager; the first entry is the default. */
|
|
514
|
+
const ROW_PAGE_SIZES = [
|
|
515
|
+
50,
|
|
516
|
+
100,
|
|
517
|
+
250
|
|
518
|
+
];
|
|
519
|
+
const DEFAULT_ROW_PAGE_SIZE = ROW_PAGE_SIZES[0];
|
|
520
|
+
/** Number of pages needed for `total` rows; an empty table still has one page. */
|
|
521
|
+
function pageCount(total, pageSize) {
|
|
522
|
+
if (pageSize <= 0) return 1;
|
|
523
|
+
return Math.max(1, Math.ceil(total / pageSize));
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Keep a zero-based page index inside the table. Needed after deleting rows,
|
|
527
|
+
* when the page being viewed may no longer exist.
|
|
528
|
+
*/
|
|
529
|
+
function clampPage(page, total, pageSize) {
|
|
530
|
+
return Math.min(Math.max(page, 0), pageCount(total, pageSize) - 1);
|
|
531
|
+
}
|
|
532
|
+
/** One-based, inclusive `from`–`to` range of the rows shown on a page. */
|
|
533
|
+
function pageRange(page, pageSize, shown) {
|
|
534
|
+
if (shown <= 0) return {
|
|
535
|
+
from: 0,
|
|
536
|
+
to: 0
|
|
537
|
+
};
|
|
538
|
+
const from = page * pageSize + 1;
|
|
539
|
+
return {
|
|
540
|
+
from,
|
|
541
|
+
to: from + shown - 1
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// src/modules/data-tables/pages/DataTablesPage.tsx
|
|
546
|
+
var DataTablesPage_exports = /* @__PURE__ */ __exportAll({ DataTablesPage: () => DataTablesPage });
|
|
547
|
+
const COLUMN_TYPES = [
|
|
548
|
+
"STRING",
|
|
549
|
+
"NUMBER",
|
|
550
|
+
"BOOLEAN",
|
|
551
|
+
"DATE",
|
|
552
|
+
"JSON"
|
|
553
|
+
];
|
|
554
|
+
function emptyColumn(index) {
|
|
555
|
+
return {
|
|
556
|
+
key: `column_${index}`,
|
|
557
|
+
label: `Column ${index}`,
|
|
558
|
+
type: "STRING",
|
|
559
|
+
required: false
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
function formatBytes(bytes) {
|
|
563
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
564
|
+
if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
565
|
+
return `${(bytes / 1048576).toFixed(1)} MB`;
|
|
566
|
+
}
|
|
567
|
+
function formatValue(value) {
|
|
568
|
+
if (value === null || value === void 0) return "—";
|
|
569
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
570
|
+
return String(value);
|
|
571
|
+
}
|
|
572
|
+
function columnTypeLabel(type) {
|
|
573
|
+
return type === "JSON" ? "JSON" : type[0] + type.slice(1).toLowerCase();
|
|
574
|
+
}
|
|
575
|
+
function asFormValue(value, type) {
|
|
576
|
+
if (value === null || value === void 0) return "";
|
|
577
|
+
if (type === "JSON") return JSON.stringify(value, null, 2);
|
|
578
|
+
return String(value);
|
|
579
|
+
}
|
|
580
|
+
function parseFormValue(value, type) {
|
|
581
|
+
if (type === "STRING" || type === "DATE") return value;
|
|
582
|
+
if (value.trim() === "") return null;
|
|
583
|
+
if (type === "NUMBER") {
|
|
584
|
+
const number = Number(value);
|
|
585
|
+
if (!Number.isFinite(number)) throw new Error(`"${value}" is not a number`);
|
|
586
|
+
return number;
|
|
587
|
+
}
|
|
588
|
+
if (type === "BOOLEAN") {
|
|
589
|
+
if (value === "true") return true;
|
|
590
|
+
if (value === "false") return false;
|
|
591
|
+
throw new Error("Boolean values must be true or false");
|
|
592
|
+
}
|
|
593
|
+
try {
|
|
594
|
+
return JSON.parse(value);
|
|
595
|
+
} catch {
|
|
596
|
+
throw new Error("JSON values must contain valid JSON");
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
function TableFormDialog({ open, table, onOpenChange, onSaved }) {
|
|
600
|
+
const tr = useTr();
|
|
601
|
+
const { createDataTable, loading: creating } = useCreateDataTable();
|
|
602
|
+
const { updateDataTable, loading: updating } = useUpdateDataTable();
|
|
603
|
+
const [name, setName] = useState("");
|
|
604
|
+
const [description, setDescription] = useState("");
|
|
605
|
+
const [columns, setColumns] = useState([]);
|
|
606
|
+
const editing = table !== null;
|
|
607
|
+
const saving = creating || updating;
|
|
608
|
+
useEffect(() => {
|
|
609
|
+
if (!open) return;
|
|
610
|
+
setName(table?.name ?? "");
|
|
611
|
+
setDescription(table?.description ?? "");
|
|
612
|
+
setColumns(table?.columns.map((column) => ({
|
|
613
|
+
key: column.key,
|
|
614
|
+
label: column.label,
|
|
615
|
+
type: column.type,
|
|
616
|
+
required: column.required,
|
|
617
|
+
...column.defaultValue !== void 0 ? { defaultValue: column.defaultValue } : {}
|
|
618
|
+
})) ?? [emptyColumn(1)]);
|
|
619
|
+
}, [open, table]);
|
|
620
|
+
const updateColumn = useCallback((index, patch) => {
|
|
621
|
+
setColumns((current) => current.map((column, columnIndex) => columnIndex === index ? {
|
|
622
|
+
...column,
|
|
623
|
+
...patch
|
|
624
|
+
} : column));
|
|
625
|
+
}, []);
|
|
626
|
+
const handleSubmit = useCallback(async () => {
|
|
627
|
+
const trimmedName = name.trim();
|
|
628
|
+
if (!trimmedName) {
|
|
629
|
+
toast.error(tr("fluidgrids.dataTables.nameRequired", "Table name is required"));
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
if (columns.length === 0 || columns.some((column) => !column.key.trim() || !column.label.trim())) {
|
|
633
|
+
toast.error(tr("fluidgrids.dataTables.columnsRequired", "Add at least one column with a key and label"));
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
const normalizedColumns = columns.map((column) => ({
|
|
637
|
+
...column,
|
|
638
|
+
key: column.key.trim(),
|
|
639
|
+
label: column.label.trim()
|
|
640
|
+
}));
|
|
641
|
+
if (new Set(normalizedColumns.map((column) => column.key)).size !== normalizedColumns.length) {
|
|
642
|
+
toast.error(tr("fluidgrids.dataTables.uniqueKeys", "Column keys must be unique"));
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
try {
|
|
646
|
+
const saved = editing ? await updateDataTable(table.id, {
|
|
647
|
+
name: trimmedName,
|
|
648
|
+
description: description.trim() || void 0,
|
|
649
|
+
columns: normalizedColumns
|
|
650
|
+
}) : await createDataTable({
|
|
651
|
+
name: trimmedName,
|
|
652
|
+
description: description.trim() || void 0,
|
|
653
|
+
columns: normalizedColumns
|
|
654
|
+
});
|
|
655
|
+
toast.success(editing ? tr("fluidgrids.dataTables.updated", "Data table updated") : tr("fluidgrids.dataTables.created", "Data table created"));
|
|
656
|
+
onSaved(saved);
|
|
657
|
+
onOpenChange(false);
|
|
658
|
+
} catch (error) {
|
|
659
|
+
toast.error(describeOperationError(error, tr("fluidgrids.dataTables.saveFailed", "Failed to save data table")));
|
|
660
|
+
}
|
|
661
|
+
}, [
|
|
662
|
+
columns,
|
|
663
|
+
createDataTable,
|
|
664
|
+
description,
|
|
665
|
+
editing,
|
|
666
|
+
name,
|
|
667
|
+
onOpenChange,
|
|
668
|
+
onSaved,
|
|
669
|
+
table,
|
|
670
|
+
tr,
|
|
671
|
+
updateDataTable
|
|
672
|
+
]);
|
|
673
|
+
return /* @__PURE__ */ jsx(Dialog, {
|
|
674
|
+
open,
|
|
675
|
+
onOpenChange,
|
|
676
|
+
children: /* @__PURE__ */ jsxs(DialogContent, {
|
|
677
|
+
className: "max-h-[90vh] max-w-3xl overflow-y-auto",
|
|
678
|
+
children: [
|
|
679
|
+
/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: editing ? tr("fluidgrids.dataTables.editTable", "Edit data table") : tr("fluidgrids.dataTables.newTable", "New data table") }) }),
|
|
680
|
+
/* @__PURE__ */ jsxs("div", {
|
|
681
|
+
className: "space-y-5 py-2",
|
|
682
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
683
|
+
className: "grid gap-4 sm:grid-cols-2",
|
|
684
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
685
|
+
className: "space-y-1.5",
|
|
686
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
687
|
+
htmlFor: "data-table-name",
|
|
688
|
+
children: tr("fluidgrids.dataTables.name", "Name")
|
|
689
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
690
|
+
id: "data-table-name",
|
|
691
|
+
value: name,
|
|
692
|
+
onChange: (event) => setName(event.target.value),
|
|
693
|
+
placeholder: "contacts",
|
|
694
|
+
autoFocus: true
|
|
695
|
+
})]
|
|
696
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
697
|
+
className: "space-y-1.5",
|
|
698
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
699
|
+
htmlFor: "data-table-description",
|
|
700
|
+
children: tr("fluidgrids.dataTables.description", "Description")
|
|
701
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
702
|
+
id: "data-table-description",
|
|
703
|
+
value: description,
|
|
704
|
+
onChange: (event) => setDescription(event.target.value),
|
|
705
|
+
placeholder: tr("fluidgrids.dataTables.descriptionPlaceholder", "Optional")
|
|
706
|
+
})]
|
|
707
|
+
})]
|
|
708
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
709
|
+
className: "space-y-3",
|
|
710
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
711
|
+
className: "flex items-center justify-between gap-3",
|
|
712
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h3", {
|
|
713
|
+
className: "text-sm font-semibold text-text-primary",
|
|
714
|
+
children: tr("fluidgrids.dataTables.columns", "Columns")
|
|
715
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
716
|
+
className: "text-xs text-text-secondary",
|
|
717
|
+
children: tr("fluidgrids.dataTables.columnsHint", "Column keys are used by workflow nodes and must be unique.")
|
|
718
|
+
})] }), /* @__PURE__ */ jsxs(Button$1, {
|
|
719
|
+
type: "button",
|
|
720
|
+
variant: "outline",
|
|
721
|
+
size: "sm",
|
|
722
|
+
onClick: () => setColumns((current) => [...current, emptyColumn(current.length + 1)]),
|
|
723
|
+
children: [/* @__PURE__ */ jsx(Plus, {
|
|
724
|
+
className: "mr-1.5 size-4",
|
|
725
|
+
"aria-hidden": "true"
|
|
726
|
+
}), tr("fluidgrids.dataTables.addColumn", "Add column")]
|
|
727
|
+
})]
|
|
728
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
729
|
+
className: "space-y-2",
|
|
730
|
+
children: columns.map((column, index) => /* @__PURE__ */ jsxs("div", {
|
|
731
|
+
className: "grid gap-2 rounded-md border border-border-secondary p-3 sm:grid-cols-[1fr_1fr_8rem_auto] sm:items-end",
|
|
732
|
+
children: [
|
|
733
|
+
/* @__PURE__ */ jsxs("div", {
|
|
734
|
+
className: "space-y-1",
|
|
735
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
736
|
+
htmlFor: `data-table-column-key-${index}`,
|
|
737
|
+
children: tr("fluidgrids.dataTables.key", "Key")
|
|
738
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
739
|
+
id: `data-table-column-key-${index}`,
|
|
740
|
+
value: column.key,
|
|
741
|
+
onChange: (event) => updateColumn(index, { key: event.target.value })
|
|
742
|
+
})]
|
|
743
|
+
}),
|
|
744
|
+
/* @__PURE__ */ jsxs("div", {
|
|
745
|
+
className: "space-y-1",
|
|
746
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
747
|
+
htmlFor: `data-table-column-label-${index}`,
|
|
748
|
+
children: tr("fluidgrids.dataTables.label", "Label")
|
|
749
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
750
|
+
id: `data-table-column-label-${index}`,
|
|
751
|
+
value: column.label,
|
|
752
|
+
onChange: (event) => updateColumn(index, { label: event.target.value })
|
|
753
|
+
})]
|
|
754
|
+
}),
|
|
755
|
+
/* @__PURE__ */ jsxs("div", {
|
|
756
|
+
className: "space-y-1",
|
|
757
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
758
|
+
htmlFor: `data-table-column-type-${index}`,
|
|
759
|
+
children: tr("fluidgrids.dataTables.type", "Type")
|
|
760
|
+
}), /* @__PURE__ */ jsx("select", {
|
|
761
|
+
id: `data-table-column-type-${index}`,
|
|
762
|
+
className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-text-primary",
|
|
763
|
+
value: column.type,
|
|
764
|
+
onChange: (event) => updateColumn(index, { type: event.target.value }),
|
|
765
|
+
children: COLUMN_TYPES.map((type) => /* @__PURE__ */ jsx("option", {
|
|
766
|
+
value: type,
|
|
767
|
+
children: columnTypeLabel(type)
|
|
768
|
+
}, type))
|
|
769
|
+
})]
|
|
770
|
+
}),
|
|
771
|
+
/* @__PURE__ */ jsxs("div", {
|
|
772
|
+
className: "flex items-center gap-2 pb-2 sm:flex-col sm:items-center sm:gap-1",
|
|
773
|
+
children: [/* @__PURE__ */ jsxs("label", {
|
|
774
|
+
className: "flex items-center gap-2 text-xs text-text-secondary",
|
|
775
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
776
|
+
type: "checkbox",
|
|
777
|
+
checked: column.required ?? false,
|
|
778
|
+
onChange: (event) => updateColumn(index, { required: event.target.checked })
|
|
779
|
+
}), tr("fluidgrids.dataTables.required", "Required")]
|
|
780
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
781
|
+
type: "button",
|
|
782
|
+
className: "rounded p-1 text-text-secondary hover:bg-background-tertiary hover:text-text-primary disabled:opacity-40",
|
|
783
|
+
onClick: () => setColumns((current) => current.filter((_, columnIndex) => columnIndex !== index)),
|
|
784
|
+
disabled: columns.length === 1,
|
|
785
|
+
title: tr("fluidgrids.dataTables.removeColumn", "Remove column"),
|
|
786
|
+
"aria-label": tr("fluidgrids.dataTables.removeColumn", "Remove column"),
|
|
787
|
+
children: /* @__PURE__ */ jsx(Trash2, {
|
|
788
|
+
className: "size-4",
|
|
789
|
+
"aria-hidden": "true"
|
|
790
|
+
})
|
|
791
|
+
})]
|
|
792
|
+
})
|
|
793
|
+
]
|
|
794
|
+
}, `${column.key}-${index}`))
|
|
795
|
+
})]
|
|
796
|
+
})]
|
|
797
|
+
}),
|
|
798
|
+
/* @__PURE__ */ jsxs(DialogFooter, { children: [/* @__PURE__ */ jsx(Button$1, {
|
|
799
|
+
variant: "outline",
|
|
800
|
+
onClick: () => onOpenChange(false),
|
|
801
|
+
children: tr("fluidgrids.dataTables.cancel", "Cancel")
|
|
802
|
+
}), /* @__PURE__ */ jsx(Button$1, {
|
|
803
|
+
onClick: handleSubmit,
|
|
804
|
+
disabled: saving,
|
|
805
|
+
children: saving ? tr("fluidgrids.dataTables.saving", "Saving…") : tr("fluidgrids.dataTables.save", "Save table")
|
|
806
|
+
})] })
|
|
807
|
+
]
|
|
808
|
+
})
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
function RowFormDialog({ open, table, row, onOpenChange, onSaved }) {
|
|
812
|
+
const tr = useTr();
|
|
813
|
+
const { insertDataTableRow, loading: inserting } = useInsertDataTableRow();
|
|
814
|
+
const { updateDataTableRow, loading: updating } = useUpdateDataTableRow();
|
|
815
|
+
const [values, setValues] = useState({});
|
|
816
|
+
const editing = row !== null;
|
|
817
|
+
const saving = inserting || updating;
|
|
818
|
+
useEffect(() => {
|
|
819
|
+
if (!open || !table) return;
|
|
820
|
+
const next = {};
|
|
821
|
+
for (const column of table.columns) next[column.key] = asFormValue(row?.data[column.key], column.type);
|
|
822
|
+
setValues(next);
|
|
823
|
+
}, [
|
|
824
|
+
open,
|
|
825
|
+
row,
|
|
826
|
+
table
|
|
827
|
+
]);
|
|
828
|
+
const handleSubmit = useCallback(async () => {
|
|
829
|
+
if (!table) return;
|
|
830
|
+
try {
|
|
831
|
+
const data = {};
|
|
832
|
+
for (const column of table.columns) {
|
|
833
|
+
const value = values[column.key] ?? "";
|
|
834
|
+
if (column.required && value.trim() === "") throw new Error(`${column.label} is required`);
|
|
835
|
+
data[column.key] = parseFormValue(value, column.type);
|
|
836
|
+
}
|
|
837
|
+
if (editing && row) await updateDataTableRow(row.id, data);
|
|
838
|
+
else await insertDataTableRow(table.id, data);
|
|
839
|
+
toast.success(editing ? tr("fluidgrids.dataTables.rowUpdated", "Row updated") : tr("fluidgrids.dataTables.rowCreated", "Row added"));
|
|
840
|
+
onSaved();
|
|
841
|
+
onOpenChange(false);
|
|
842
|
+
} catch (error) {
|
|
843
|
+
toast.error(describeOperationError(error, tr("fluidgrids.dataTables.rowSaveFailed", "Failed to save row")));
|
|
844
|
+
}
|
|
845
|
+
}, [
|
|
846
|
+
editing,
|
|
847
|
+
insertDataTableRow,
|
|
848
|
+
onOpenChange,
|
|
849
|
+
onSaved,
|
|
850
|
+
row,
|
|
851
|
+
table,
|
|
852
|
+
tr,
|
|
853
|
+
updateDataTableRow,
|
|
854
|
+
values
|
|
855
|
+
]);
|
|
856
|
+
return /* @__PURE__ */ jsx(Dialog, {
|
|
857
|
+
open,
|
|
858
|
+
onOpenChange,
|
|
859
|
+
children: /* @__PURE__ */ jsxs(DialogContent, {
|
|
860
|
+
className: "max-h-[90vh] max-w-2xl overflow-y-auto",
|
|
861
|
+
children: [
|
|
862
|
+
/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: editing ? tr("fluidgrids.dataTables.editRow", "Edit row") : tr("fluidgrids.dataTables.addRow", "Add row") }) }),
|
|
863
|
+
table && /* @__PURE__ */ jsx("div", {
|
|
864
|
+
className: "space-y-4 py-2",
|
|
865
|
+
children: table.columns.map((column) => /* @__PURE__ */ jsxs("div", {
|
|
866
|
+
className: "space-y-1.5",
|
|
867
|
+
children: [/* @__PURE__ */ jsxs(Label, {
|
|
868
|
+
htmlFor: `data-table-row-${column.key}`,
|
|
869
|
+
children: [
|
|
870
|
+
column.label,
|
|
871
|
+
/* @__PURE__ */ jsxs("span", {
|
|
872
|
+
className: "ml-1 text-xs text-text-secondary",
|
|
873
|
+
children: [
|
|
874
|
+
"(",
|
|
875
|
+
column.key,
|
|
876
|
+
" · ",
|
|
877
|
+
columnTypeLabel(column.type),
|
|
878
|
+
")"
|
|
879
|
+
]
|
|
880
|
+
}),
|
|
881
|
+
column.required && /* @__PURE__ */ jsx("span", {
|
|
882
|
+
className: "ml-1 text-text-secondary",
|
|
883
|
+
children: "*"
|
|
884
|
+
})
|
|
885
|
+
]
|
|
886
|
+
}), column.type === "JSON" ? /* @__PURE__ */ jsx(Textarea, {
|
|
887
|
+
id: `data-table-row-${column.key}`,
|
|
888
|
+
value: values[column.key] ?? "",
|
|
889
|
+
onChange: (event) => setValues((current) => ({
|
|
890
|
+
...current,
|
|
891
|
+
[column.key]: event.target.value
|
|
892
|
+
})),
|
|
893
|
+
rows: 4,
|
|
894
|
+
placeholder: "{ }"
|
|
895
|
+
}) : /* @__PURE__ */ jsx(Input, {
|
|
896
|
+
id: `data-table-row-${column.key}`,
|
|
897
|
+
type: column.type === "NUMBER" ? "number" : column.type === "DATE" ? "datetime-local" : "text",
|
|
898
|
+
value: values[column.key] ?? "",
|
|
899
|
+
onChange: (event) => setValues((current) => ({
|
|
900
|
+
...current,
|
|
901
|
+
[column.key]: event.target.value
|
|
902
|
+
}))
|
|
903
|
+
})]
|
|
904
|
+
}, column.key))
|
|
905
|
+
}),
|
|
906
|
+
/* @__PURE__ */ jsxs(DialogFooter, { children: [/* @__PURE__ */ jsx(Button$1, {
|
|
907
|
+
variant: "outline",
|
|
908
|
+
onClick: () => onOpenChange(false),
|
|
909
|
+
children: tr("fluidgrids.dataTables.cancel", "Cancel")
|
|
910
|
+
}), /* @__PURE__ */ jsx(Button$1, {
|
|
911
|
+
onClick: handleSubmit,
|
|
912
|
+
disabled: saving || !table,
|
|
913
|
+
children: saving ? tr("fluidgrids.dataTables.saving", "Saving…") : tr("fluidgrids.dataTables.saveRow", "Save row")
|
|
914
|
+
})] })
|
|
915
|
+
]
|
|
916
|
+
})
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
function TableRowsPanel({ table, onTableChanged, onEditTable, onDeleteTable, deletingTable, onTagsChanged }) {
|
|
920
|
+
const tr = useTr();
|
|
921
|
+
const { allowed: canTag } = useFluidGridsPermission(FLUIDGRIDS_PERMISSIONS.WORKFLOW_UPDATE);
|
|
922
|
+
const [page, setPage] = useState(0);
|
|
923
|
+
const [pageSize, setPageSize] = useState(DEFAULT_ROW_PAGE_SIZE);
|
|
924
|
+
const { rows, total, loading, error, refetch } = useDataTableRows(table.id, {
|
|
925
|
+
page,
|
|
926
|
+
pageSize
|
|
927
|
+
});
|
|
928
|
+
const { deleteDataTableRows, loading: deleting } = useDeleteDataTableRows();
|
|
929
|
+
const { insertDataTableRow } = useInsertDataTableRow();
|
|
930
|
+
const [selectedIds, setSelectedIds] = useState(/* @__PURE__ */ new Set());
|
|
931
|
+
const [rowDialogOpen, setRowDialogOpen] = useState(false);
|
|
932
|
+
const [editingRow, setEditingRow] = useState(null);
|
|
933
|
+
const [exporting, setExporting] = useState(false);
|
|
934
|
+
const [importProgress, setImportProgress] = useState(null);
|
|
935
|
+
const fileInputRef = useRef(null);
|
|
936
|
+
const pages = pageCount(total, pageSize);
|
|
937
|
+
const range = pageRange(page, pageSize, rows.length);
|
|
938
|
+
useEffect(() => {
|
|
939
|
+
if (loading) return;
|
|
940
|
+
const clamped = clampPage(page, total, pageSize);
|
|
941
|
+
if (clamped !== page) setPage(clamped);
|
|
942
|
+
}, [
|
|
943
|
+
loading,
|
|
944
|
+
page,
|
|
945
|
+
pageSize,
|
|
946
|
+
total
|
|
947
|
+
]);
|
|
948
|
+
useEffect(() => {
|
|
949
|
+
setSelectedIds((current) => {
|
|
950
|
+
const visible = new Set(rows.map((row) => row.id));
|
|
951
|
+
return new Set([...current].filter((id) => visible.has(id)));
|
|
952
|
+
});
|
|
953
|
+
}, [rows]);
|
|
954
|
+
const allVisibleSelected = rows.length > 0 && selectedIds.size === rows.length;
|
|
955
|
+
const toggleAll = useCallback(() => {
|
|
956
|
+
setSelectedIds(allVisibleSelected ? /* @__PURE__ */ new Set() : new Set(rows.map((row) => row.id)));
|
|
957
|
+
}, [allVisibleSelected, rows]);
|
|
958
|
+
const handleDeleteRows = useCallback(async () => {
|
|
959
|
+
if (selectedIds.size === 0) return;
|
|
960
|
+
if (!window.confirm(tr("fluidgrids.dataTables.confirmDeleteRows", `Delete ${selectedIds.size} selected row(s)?`))) return;
|
|
961
|
+
try {
|
|
962
|
+
const affected = await deleteDataTableRows(table.id, [...selectedIds]);
|
|
963
|
+
toast.success(tr("fluidgrids.dataTables.rowsDeleted", `${affected} row(s) deleted`));
|
|
964
|
+
setSelectedIds(/* @__PURE__ */ new Set());
|
|
965
|
+
await refetch();
|
|
966
|
+
onTableChanged();
|
|
967
|
+
} catch (error) {
|
|
968
|
+
toast.error(describeOperationError(error, tr("fluidgrids.dataTables.deleteRowsFailed", "Failed to delete rows")));
|
|
969
|
+
}
|
|
970
|
+
}, [
|
|
971
|
+
deleteDataTableRows,
|
|
972
|
+
onTableChanged,
|
|
973
|
+
refetch,
|
|
974
|
+
selectedIds,
|
|
975
|
+
table.id,
|
|
976
|
+
tr
|
|
977
|
+
]);
|
|
978
|
+
const handleExport = useCallback(async () => {
|
|
979
|
+
setExporting(true);
|
|
980
|
+
try {
|
|
981
|
+
const allRows = await fetchAllDataTableRows(table.id);
|
|
982
|
+
const csv = rowsToCsv(table.columns, allRows);
|
|
983
|
+
const blob = new Blob([csv], { type: "text/csv;charset=utf-8" });
|
|
984
|
+
const url = URL.createObjectURL(blob);
|
|
985
|
+
const anchor = document.createElement("a");
|
|
986
|
+
anchor.href = url;
|
|
987
|
+
anchor.download = `${table.name}.csv`;
|
|
988
|
+
anchor.click();
|
|
989
|
+
URL.revokeObjectURL(url);
|
|
990
|
+
toast.success(tr("fluidgrids.dataTables.exported", `${allRows.length} row(s) exported to CSV`));
|
|
991
|
+
} catch (error) {
|
|
992
|
+
toast.error(describeOperationError(error, tr("fluidgrids.dataTables.exportFailed", "Failed to export CSV")));
|
|
993
|
+
} finally {
|
|
994
|
+
setExporting(false);
|
|
995
|
+
}
|
|
996
|
+
}, [
|
|
997
|
+
table.columns,
|
|
998
|
+
table.id,
|
|
999
|
+
table.name,
|
|
1000
|
+
tr
|
|
1001
|
+
]);
|
|
1002
|
+
const handleImport = useCallback(async (event) => {
|
|
1003
|
+
const file = event.target.files?.[0];
|
|
1004
|
+
event.target.value = "";
|
|
1005
|
+
if (!file) return;
|
|
1006
|
+
let inserted = 0;
|
|
1007
|
+
try {
|
|
1008
|
+
const importedRows = csvToRows(await file.text(), table.columns);
|
|
1009
|
+
setImportProgress({
|
|
1010
|
+
done: 0,
|
|
1011
|
+
total: importedRows.length
|
|
1012
|
+
});
|
|
1013
|
+
for (const data of importedRows) {
|
|
1014
|
+
await insertDataTableRow(table.id, data);
|
|
1015
|
+
inserted += 1;
|
|
1016
|
+
setImportProgress({
|
|
1017
|
+
done: inserted,
|
|
1018
|
+
total: importedRows.length
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
toast.success(tr("fluidgrids.dataTables.imported", `${inserted} row(s) imported`));
|
|
1022
|
+
} catch (error) {
|
|
1023
|
+
const reason = describeOperationError(error, tr("fluidgrids.dataTables.importFailed", "Failed to import CSV"));
|
|
1024
|
+
toast.error(inserted > 0 ? tr("fluidgrids.dataTables.importPartial", `Import stopped after ${inserted} row(s): ${reason}`) : reason);
|
|
1025
|
+
} finally {
|
|
1026
|
+
setImportProgress(null);
|
|
1027
|
+
setPage(0);
|
|
1028
|
+
await refetch();
|
|
1029
|
+
onTableChanged();
|
|
1030
|
+
}
|
|
1031
|
+
}, [
|
|
1032
|
+
insertDataTableRow,
|
|
1033
|
+
onTableChanged,
|
|
1034
|
+
refetch,
|
|
1035
|
+
table.columns,
|
|
1036
|
+
table.id,
|
|
1037
|
+
tr
|
|
1038
|
+
]);
|
|
1039
|
+
const openNewRow = useCallback(() => {
|
|
1040
|
+
setEditingRow(null);
|
|
1041
|
+
setRowDialogOpen(true);
|
|
1042
|
+
}, []);
|
|
1043
|
+
const openEditRow = useCallback((row) => {
|
|
1044
|
+
setEditingRow(row);
|
|
1045
|
+
setRowDialogOpen(true);
|
|
1046
|
+
}, []);
|
|
1047
|
+
return /* @__PURE__ */ jsxs(Card, {
|
|
1048
|
+
className: "min-w-0 border-border-secondary",
|
|
1049
|
+
children: [
|
|
1050
|
+
/* @__PURE__ */ jsxs(CardHeader, {
|
|
1051
|
+
className: "gap-4 border-b border-border-secondary p-4 sm:p-5",
|
|
1052
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1053
|
+
className: "flex flex-wrap items-start justify-between gap-3",
|
|
1054
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1055
|
+
className: "min-w-0 flex-1 basis-64",
|
|
1056
|
+
children: [
|
|
1057
|
+
/* @__PURE__ */ jsxs(CardTitle, {
|
|
1058
|
+
className: "flex items-center gap-2 text-lg",
|
|
1059
|
+
children: [/* @__PURE__ */ jsx(Table2, {
|
|
1060
|
+
className: "size-5 shrink-0 text-text-secondary",
|
|
1061
|
+
"aria-hidden": "true"
|
|
1062
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1063
|
+
className: "truncate",
|
|
1064
|
+
children: table.name
|
|
1065
|
+
})]
|
|
1066
|
+
}),
|
|
1067
|
+
/* @__PURE__ */ jsx(CardDescription, {
|
|
1068
|
+
className: "mt-1",
|
|
1069
|
+
children: table.description || tr("fluidgrids.dataTables.noDescription", "No description")
|
|
1070
|
+
}),
|
|
1071
|
+
/* @__PURE__ */ jsx("div", {
|
|
1072
|
+
className: "mt-3",
|
|
1073
|
+
"data-testid": "data-table-tags",
|
|
1074
|
+
children: /* @__PURE__ */ jsx(EntityTagPicker, {
|
|
1075
|
+
entityType: "FLUIDGRIDS_DATA_TABLE",
|
|
1076
|
+
entityId: table.id,
|
|
1077
|
+
readOnly: !canTag,
|
|
1078
|
+
onChange: onTagsChanged
|
|
1079
|
+
})
|
|
1080
|
+
})
|
|
1081
|
+
]
|
|
1082
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1083
|
+
className: "flex shrink-0 items-center gap-2",
|
|
1084
|
+
children: [/* @__PURE__ */ jsx(SdkPermissionButton, {
|
|
1085
|
+
permission: FLUIDGRIDS_PERMISSIONS.DATA_TABLE_UPDATE,
|
|
1086
|
+
deniedTooltip: tr("fluidgrids.dataTables.editDeniedTooltip", "You don't have permission to edit data tables"),
|
|
1087
|
+
children: /* @__PURE__ */ jsxs(Button$1, {
|
|
1088
|
+
variant: "outline",
|
|
1089
|
+
size: "sm",
|
|
1090
|
+
className: "whitespace-nowrap",
|
|
1091
|
+
onClick: onEditTable,
|
|
1092
|
+
children: [/* @__PURE__ */ jsx(Pencil, {
|
|
1093
|
+
className: "mr-1.5 size-4",
|
|
1094
|
+
"aria-hidden": "true"
|
|
1095
|
+
}), tr("fluidgrids.dataTables.editTable", "Edit table")]
|
|
1096
|
+
})
|
|
1097
|
+
}), /* @__PURE__ */ jsx(SdkPermissionButton, {
|
|
1098
|
+
permission: FLUIDGRIDS_PERMISSIONS.DATA_TABLE_DELETE,
|
|
1099
|
+
deniedTooltip: tr("fluidgrids.dataTables.deleteDeniedTooltip", "You don't have permission to delete data tables"),
|
|
1100
|
+
children: /* @__PURE__ */ jsxs(Button$1, {
|
|
1101
|
+
variant: "outline",
|
|
1102
|
+
size: "sm",
|
|
1103
|
+
className: "whitespace-nowrap",
|
|
1104
|
+
onClick: onDeleteTable,
|
|
1105
|
+
disabled: deletingTable,
|
|
1106
|
+
children: [/* @__PURE__ */ jsx(Trash2, {
|
|
1107
|
+
className: "mr-1.5 size-4",
|
|
1108
|
+
"aria-hidden": "true"
|
|
1109
|
+
}), deletingTable ? tr("fluidgrids.dataTables.deleting", "Deleting…") : tr("fluidgrids.dataTables.deleteTable", "Delete table")]
|
|
1110
|
+
})
|
|
1111
|
+
})]
|
|
1112
|
+
})]
|
|
1113
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1114
|
+
className: "flex flex-wrap items-center justify-between gap-3",
|
|
1115
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1116
|
+
className: "flex flex-wrap items-center gap-2 text-xs text-text-secondary",
|
|
1117
|
+
children: [
|
|
1118
|
+
/* @__PURE__ */ jsxs(Badge, {
|
|
1119
|
+
variant: "outline",
|
|
1120
|
+
children: [
|
|
1121
|
+
total,
|
|
1122
|
+
" ",
|
|
1123
|
+
tr("fluidgrids.dataTables.rows", "rows")
|
|
1124
|
+
]
|
|
1125
|
+
}),
|
|
1126
|
+
/* @__PURE__ */ jsxs(Badge, {
|
|
1127
|
+
variant: "outline",
|
|
1128
|
+
children: [
|
|
1129
|
+
table.columns.length,
|
|
1130
|
+
" ",
|
|
1131
|
+
tr("fluidgrids.dataTables.columns", "columns")
|
|
1132
|
+
]
|
|
1133
|
+
}),
|
|
1134
|
+
/* @__PURE__ */ jsx(Badge, {
|
|
1135
|
+
variant: "outline",
|
|
1136
|
+
children: formatBytes(table.sizeBytes)
|
|
1137
|
+
}),
|
|
1138
|
+
selectedIds.size > 0 && /* @__PURE__ */ jsx(SdkPermissionButton, {
|
|
1139
|
+
permission: FLUIDGRIDS_PERMISSIONS.DATA_TABLE_ROW_DELETE,
|
|
1140
|
+
deniedTooltip: tr("fluidgrids.dataTables.deleteRowsDeniedTooltip", "You don't have permission to delete rows"),
|
|
1141
|
+
children: /* @__PURE__ */ jsxs(Button$1, {
|
|
1142
|
+
variant: "outline",
|
|
1143
|
+
size: "sm",
|
|
1144
|
+
className: "whitespace-nowrap",
|
|
1145
|
+
onClick: handleDeleteRows,
|
|
1146
|
+
disabled: deleting,
|
|
1147
|
+
children: [/* @__PURE__ */ jsx(Trash2, {
|
|
1148
|
+
className: "mr-1.5 size-4",
|
|
1149
|
+
"aria-hidden": "true"
|
|
1150
|
+
}), deleting ? tr("fluidgrids.dataTables.deleting", "Deleting…") : tr("fluidgrids.dataTables.deleteSelected", `Delete ${selectedIds.size} selected`)]
|
|
1151
|
+
})
|
|
1152
|
+
})
|
|
1153
|
+
]
|
|
1154
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1155
|
+
className: "flex flex-wrap items-center gap-2",
|
|
1156
|
+
children: [
|
|
1157
|
+
/* @__PURE__ */ jsx(SdkPermissionButton, {
|
|
1158
|
+
permission: FLUIDGRIDS_PERMISSIONS.DATA_TABLE_ROW_CREATE,
|
|
1159
|
+
deniedTooltip: tr("fluidgrids.dataTables.addRowDeniedTooltip", "You don't have permission to add rows"),
|
|
1160
|
+
children: /* @__PURE__ */ jsxs(Button$1, {
|
|
1161
|
+
size: "sm",
|
|
1162
|
+
className: "whitespace-nowrap",
|
|
1163
|
+
onClick: openNewRow,
|
|
1164
|
+
disabled: importProgress !== null,
|
|
1165
|
+
children: [/* @__PURE__ */ jsx(Plus, {
|
|
1166
|
+
className: "mr-1.5 size-4",
|
|
1167
|
+
"aria-hidden": "true"
|
|
1168
|
+
}), tr("fluidgrids.dataTables.addRow", "Add row")]
|
|
1169
|
+
})
|
|
1170
|
+
}),
|
|
1171
|
+
/* @__PURE__ */ jsx(SdkPermissionButton, {
|
|
1172
|
+
permission: FLUIDGRIDS_PERMISSIONS.DATA_TABLE_ROW_CREATE,
|
|
1173
|
+
deniedTooltip: tr("fluidgrids.dataTables.importDeniedTooltip", "You don't have permission to import rows"),
|
|
1174
|
+
children: /* @__PURE__ */ jsxs(Button$1, {
|
|
1175
|
+
variant: "outline",
|
|
1176
|
+
size: "sm",
|
|
1177
|
+
className: "whitespace-nowrap",
|
|
1178
|
+
onClick: () => fileInputRef.current?.click(),
|
|
1179
|
+
disabled: importProgress !== null,
|
|
1180
|
+
children: [/* @__PURE__ */ jsx(ArrowUpFromLine, {
|
|
1181
|
+
className: "mr-1.5 size-4",
|
|
1182
|
+
"aria-hidden": "true"
|
|
1183
|
+
}), importProgress ? tr("fluidgrids.dataTables.importing", `Importing ${importProgress.done} / ${importProgress.total}…`) : tr("fluidgrids.dataTables.import", "Import CSV")]
|
|
1184
|
+
})
|
|
1185
|
+
}),
|
|
1186
|
+
/* @__PURE__ */ jsx("input", {
|
|
1187
|
+
ref: fileInputRef,
|
|
1188
|
+
type: "file",
|
|
1189
|
+
accept: ".csv,text/csv",
|
|
1190
|
+
className: "hidden",
|
|
1191
|
+
onChange: handleImport
|
|
1192
|
+
}),
|
|
1193
|
+
/* @__PURE__ */ jsxs(Button$1, {
|
|
1194
|
+
variant: "outline",
|
|
1195
|
+
size: "sm",
|
|
1196
|
+
className: "whitespace-nowrap",
|
|
1197
|
+
onClick: handleExport,
|
|
1198
|
+
disabled: total === 0 || exporting,
|
|
1199
|
+
children: [/* @__PURE__ */ jsx(ArrowDownToLine, {
|
|
1200
|
+
className: "mr-1.5 size-4",
|
|
1201
|
+
"aria-hidden": "true"
|
|
1202
|
+
}), exporting ? tr("fluidgrids.dataTables.exporting", "Exporting…") : tr("fluidgrids.dataTables.export", "Export CSV")]
|
|
1203
|
+
})
|
|
1204
|
+
]
|
|
1205
|
+
})]
|
|
1206
|
+
})]
|
|
1207
|
+
}),
|
|
1208
|
+
/* @__PURE__ */ jsxs(CardContent, {
|
|
1209
|
+
className: "p-0",
|
|
1210
|
+
children: [
|
|
1211
|
+
error && /* @__PURE__ */ jsx("div", {
|
|
1212
|
+
className: "m-4 rounded-md border border-border-secondary bg-background-secondary p-3 text-sm text-text-secondary",
|
|
1213
|
+
children: error.message
|
|
1214
|
+
}),
|
|
1215
|
+
/* @__PURE__ */ jsx("div", {
|
|
1216
|
+
className: "overflow-x-auto",
|
|
1217
|
+
children: /* @__PURE__ */ jsxs(Table, { children: [/* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
|
|
1218
|
+
/* @__PURE__ */ jsx(TableHead, {
|
|
1219
|
+
className: "w-10",
|
|
1220
|
+
children: /* @__PURE__ */ jsx("input", {
|
|
1221
|
+
type: "checkbox",
|
|
1222
|
+
checked: allVisibleSelected,
|
|
1223
|
+
onChange: toggleAll,
|
|
1224
|
+
"aria-label": tr("fluidgrids.dataTables.selectAll", "Select all rows on this page")
|
|
1225
|
+
})
|
|
1226
|
+
}),
|
|
1227
|
+
/* @__PURE__ */ jsx(TableHead, { children: tr("fluidgrids.dataTables.rowId", "Row") }),
|
|
1228
|
+
table.columns.map((column) => /* @__PURE__ */ jsx(TableHead, { children: column.label }, column.key)),
|
|
1229
|
+
/* @__PURE__ */ jsx(TableHead, {
|
|
1230
|
+
className: "text-right",
|
|
1231
|
+
children: tr("fluidgrids.dataTables.actions", "Actions")
|
|
1232
|
+
})
|
|
1233
|
+
] }) }), /* @__PURE__ */ jsxs(TableBody, { children: [
|
|
1234
|
+
loading && /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, {
|
|
1235
|
+
colSpan: table.columns.length + 3,
|
|
1236
|
+
className: "py-10 text-center text-text-secondary",
|
|
1237
|
+
children: tr("fluidgrids.dataTables.loadingRows", "Loading rows…")
|
|
1238
|
+
}) }),
|
|
1239
|
+
!loading && rows.length === 0 && /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, {
|
|
1240
|
+
colSpan: table.columns.length + 3,
|
|
1241
|
+
className: "py-10 text-center text-text-secondary",
|
|
1242
|
+
children: tr("fluidgrids.dataTables.emptyRows", "No rows yet. Add a row or import a CSV to get started.")
|
|
1243
|
+
}) }),
|
|
1244
|
+
!loading && rows.map((row) => /* @__PURE__ */ jsxs(TableRow, { children: [
|
|
1245
|
+
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx("input", {
|
|
1246
|
+
type: "checkbox",
|
|
1247
|
+
checked: selectedIds.has(row.id),
|
|
1248
|
+
onChange: () => setSelectedIds((current) => {
|
|
1249
|
+
const next = new Set(current);
|
|
1250
|
+
if (next.has(row.id)) next.delete(row.id);
|
|
1251
|
+
else next.add(row.id);
|
|
1252
|
+
return next;
|
|
1253
|
+
}),
|
|
1254
|
+
"aria-label": tr("fluidgrids.dataTables.selectRow", "Select row")
|
|
1255
|
+
}) }),
|
|
1256
|
+
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx("code", {
|
|
1257
|
+
className: "text-xs text-text-secondary",
|
|
1258
|
+
children: row.id.slice(0, 8)
|
|
1259
|
+
}) }),
|
|
1260
|
+
table.columns.map((column) => /* @__PURE__ */ jsx(TableCell, {
|
|
1261
|
+
className: "max-w-[16rem] truncate text-sm",
|
|
1262
|
+
title: formatValue(row.data[column.key]),
|
|
1263
|
+
children: formatValue(row.data[column.key])
|
|
1264
|
+
}, column.key)),
|
|
1265
|
+
/* @__PURE__ */ jsx(TableCell, {
|
|
1266
|
+
className: "text-right",
|
|
1267
|
+
children: /* @__PURE__ */ jsx(SdkPermissionButton, {
|
|
1268
|
+
permission: FLUIDGRIDS_PERMISSIONS.DATA_TABLE_ROW_UPDATE,
|
|
1269
|
+
deniedTooltip: tr("fluidgrids.dataTables.editRowDeniedTooltip", "You don't have permission to edit rows"),
|
|
1270
|
+
children: /* @__PURE__ */ jsx(Button$1, {
|
|
1271
|
+
variant: "ghost",
|
|
1272
|
+
size: "icon",
|
|
1273
|
+
onClick: () => openEditRow(row),
|
|
1274
|
+
title: tr("fluidgrids.dataTables.editRow", "Edit row"),
|
|
1275
|
+
"aria-label": tr("fluidgrids.dataTables.editRow", "Edit row"),
|
|
1276
|
+
children: /* @__PURE__ */ jsx(Pencil, {
|
|
1277
|
+
className: "size-4",
|
|
1278
|
+
"aria-hidden": "true"
|
|
1279
|
+
})
|
|
1280
|
+
})
|
|
1281
|
+
})
|
|
1282
|
+
})
|
|
1283
|
+
] }, row.id))
|
|
1284
|
+
] })] })
|
|
1285
|
+
}),
|
|
1286
|
+
total > 0 && /* @__PURE__ */ jsxs("div", {
|
|
1287
|
+
className: "flex flex-wrap items-center justify-between gap-3 border-t border-border-secondary px-4 py-3 text-xs text-text-secondary",
|
|
1288
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1289
|
+
"aria-live": "polite",
|
|
1290
|
+
children: tr("fluidgrids.dataTables.showingRange", `Showing ${range.from}–${range.to} of ${total}`)
|
|
1291
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1292
|
+
className: "flex flex-wrap items-center gap-3",
|
|
1293
|
+
children: [
|
|
1294
|
+
/* @__PURE__ */ jsxs("label", {
|
|
1295
|
+
className: "flex items-center gap-2",
|
|
1296
|
+
children: [/* @__PURE__ */ jsx("span", { children: tr("fluidgrids.dataTables.rowsPerPage", "Rows per page") }), /* @__PURE__ */ jsx("select", {
|
|
1297
|
+
className: "h-8 rounded-md border border-input bg-background px-2 text-xs text-text-primary",
|
|
1298
|
+
value: pageSize,
|
|
1299
|
+
onChange: (event) => {
|
|
1300
|
+
setPageSize(Number(event.target.value));
|
|
1301
|
+
setPage(0);
|
|
1302
|
+
},
|
|
1303
|
+
children: ROW_PAGE_SIZES.map((size) => /* @__PURE__ */ jsx("option", {
|
|
1304
|
+
value: size,
|
|
1305
|
+
children: size
|
|
1306
|
+
}, size))
|
|
1307
|
+
})]
|
|
1308
|
+
}),
|
|
1309
|
+
/* @__PURE__ */ jsx("span", { children: tr("fluidgrids.dataTables.pageOf", `Page ${page + 1} of ${pages}`) }),
|
|
1310
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1311
|
+
className: "flex items-center gap-1",
|
|
1312
|
+
children: [/* @__PURE__ */ jsx(Button$1, {
|
|
1313
|
+
variant: "outline",
|
|
1314
|
+
size: "icon",
|
|
1315
|
+
className: "size-8",
|
|
1316
|
+
onClick: () => setPage((current) => Math.max(current - 1, 0)),
|
|
1317
|
+
disabled: page === 0 || loading,
|
|
1318
|
+
title: tr("fluidgrids.dataTables.prevPage", "Previous page"),
|
|
1319
|
+
"aria-label": tr("fluidgrids.dataTables.prevPage", "Previous page"),
|
|
1320
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, {
|
|
1321
|
+
className: "size-4",
|
|
1322
|
+
"aria-hidden": "true"
|
|
1323
|
+
})
|
|
1324
|
+
}), /* @__PURE__ */ jsx(Button$1, {
|
|
1325
|
+
variant: "outline",
|
|
1326
|
+
size: "icon",
|
|
1327
|
+
className: "size-8",
|
|
1328
|
+
onClick: () => setPage((current) => Math.min(current + 1, pages - 1)),
|
|
1329
|
+
disabled: page >= pages - 1 || loading,
|
|
1330
|
+
title: tr("fluidgrids.dataTables.nextPage", "Next page"),
|
|
1331
|
+
"aria-label": tr("fluidgrids.dataTables.nextPage", "Next page"),
|
|
1332
|
+
children: /* @__PURE__ */ jsx(ChevronRight, {
|
|
1333
|
+
className: "size-4",
|
|
1334
|
+
"aria-hidden": "true"
|
|
1335
|
+
})
|
|
1336
|
+
})]
|
|
1337
|
+
})
|
|
1338
|
+
]
|
|
1339
|
+
})]
|
|
1340
|
+
})
|
|
1341
|
+
]
|
|
1342
|
+
}),
|
|
1343
|
+
/* @__PURE__ */ jsx(RowFormDialog, {
|
|
1344
|
+
open: rowDialogOpen,
|
|
1345
|
+
table,
|
|
1346
|
+
row: editingRow,
|
|
1347
|
+
onOpenChange: setRowDialogOpen,
|
|
1348
|
+
onSaved: async () => {
|
|
1349
|
+
await refetch();
|
|
1350
|
+
onTableChanged();
|
|
1351
|
+
}
|
|
1352
|
+
})
|
|
1353
|
+
]
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
function DataTablesPage() {
|
|
1357
|
+
const tr = useTr();
|
|
1358
|
+
const navigate = useNavigate();
|
|
1359
|
+
const { allowed: canCreateTable } = useFluidGridsPermission(FLUIDGRIDS_PERMISSIONS.DATA_TABLE_CREATE);
|
|
1360
|
+
const [searchInput, setSearchInput] = useState("");
|
|
1361
|
+
const [search, setSearch] = useState("");
|
|
1362
|
+
const [tagFilter, setTagFilter] = useState("");
|
|
1363
|
+
const { tables, total, loading, error, refetch } = useDataTables(search, tagFilter);
|
|
1364
|
+
const { tagsById, refetch: refetchTags } = useDataTableTagOverlay();
|
|
1365
|
+
const { deleteDataTable, loading: deleting } = useDeleteDataTable();
|
|
1366
|
+
const [selectedId, setSelectedId] = useState(null);
|
|
1367
|
+
const [tableDialogOpen, setTableDialogOpen] = useState(false);
|
|
1368
|
+
const [editingTable, setEditingTable] = useState(null);
|
|
1369
|
+
const selectedTable = useMemo(() => tables.find((table) => table.id === selectedId) ?? null, [selectedId, tables]);
|
|
1370
|
+
useEffect(() => {
|
|
1371
|
+
if (tables.length === 0) {
|
|
1372
|
+
setSelectedId(null);
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
if (!selectedId || !tables.some((table) => table.id === selectedId)) setSelectedId(tables[0].id);
|
|
1376
|
+
}, [selectedId, tables]);
|
|
1377
|
+
const refresh = useCallback(async () => {
|
|
1378
|
+
await refetch();
|
|
1379
|
+
}, [refetch]);
|
|
1380
|
+
const handleTagsChanged = useCallback(() => {
|
|
1381
|
+
refetchTags();
|
|
1382
|
+
if (tagFilter) refetch();
|
|
1383
|
+
}, [
|
|
1384
|
+
refetch,
|
|
1385
|
+
refetchTags,
|
|
1386
|
+
tagFilter
|
|
1387
|
+
]);
|
|
1388
|
+
const handleDeleteTable = useCallback(async () => {
|
|
1389
|
+
if (!selectedTable) return;
|
|
1390
|
+
if (!window.confirm(tr("fluidgrids.dataTables.confirmDeleteTable", `Delete table "${selectedTable.name}" and all of its rows?`))) return;
|
|
1391
|
+
try {
|
|
1392
|
+
await deleteDataTable(selectedTable.id);
|
|
1393
|
+
toast.success(tr("fluidgrids.dataTables.deleted", "Data table deleted"));
|
|
1394
|
+
setSelectedId(null);
|
|
1395
|
+
await refetch();
|
|
1396
|
+
} catch (error) {
|
|
1397
|
+
toast.error(describeOperationError(error, tr("fluidgrids.dataTables.deleteFailed", "Failed to delete data table")));
|
|
1398
|
+
}
|
|
1399
|
+
}, [
|
|
1400
|
+
deleteDataTable,
|
|
1401
|
+
refetch,
|
|
1402
|
+
selectedTable,
|
|
1403
|
+
tr
|
|
1404
|
+
]);
|
|
1405
|
+
const handleTableSaved = useCallback((table) => {
|
|
1406
|
+
if (table) setSelectedId(table.id);
|
|
1407
|
+
refetch();
|
|
1408
|
+
}, [refetch]);
|
|
1409
|
+
const submitSearch = useCallback((event) => {
|
|
1410
|
+
event.preventDefault();
|
|
1411
|
+
setSearch(searchInput);
|
|
1412
|
+
}, [searchInput]);
|
|
1413
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1414
|
+
className: "min-h-screen bg-background-primary p-4 pb-24 sm:p-6 sm:pb-24",
|
|
1415
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1416
|
+
className: "mx-auto flex max-w-[1500px] flex-col gap-5",
|
|
1417
|
+
children: [
|
|
1418
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1419
|
+
className: "flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between",
|
|
1420
|
+
"data-tour": "fluidgrids-data-tables-header",
|
|
1421
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [
|
|
1422
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1423
|
+
className: "mb-2 flex items-center gap-2 text-sm text-text-secondary",
|
|
1424
|
+
children: [/* @__PURE__ */ jsx(Database, {
|
|
1425
|
+
className: "size-4",
|
|
1426
|
+
"aria-hidden": "true"
|
|
1427
|
+
}), tr("fluidgrids.dataTables.workflowData", "Workflow data")]
|
|
1428
|
+
}),
|
|
1429
|
+
/* @__PURE__ */ jsx("h1", {
|
|
1430
|
+
className: "text-2xl font-bold text-text-primary sm:text-3xl",
|
|
1431
|
+
children: tr("fluidgrids.dataTables.pageTitle", "Data Tables")
|
|
1432
|
+
}),
|
|
1433
|
+
/* @__PURE__ */ jsx(PagePurpose, {
|
|
1434
|
+
icon: /* @__PURE__ */ jsx(Database, {
|
|
1435
|
+
className: "size-4",
|
|
1436
|
+
"aria-hidden": "true"
|
|
1437
|
+
}),
|
|
1438
|
+
children: tr("fluidgrids.dataTables.pageSubtitle", "Use Data Tables as lightweight persistent storage for workflow state, lookups, and records shared across runs.")
|
|
1439
|
+
}),
|
|
1440
|
+
/* @__PURE__ */ jsx(FluidGridsDocsLink, {
|
|
1441
|
+
topic: "dataTables",
|
|
1442
|
+
className: "mt-1"
|
|
1443
|
+
})
|
|
1444
|
+
] }), /* @__PURE__ */ jsxs("div", {
|
|
1445
|
+
className: "flex flex-wrap gap-2",
|
|
1446
|
+
children: [/* @__PURE__ */ jsxs(Button$1, {
|
|
1447
|
+
variant: "outline",
|
|
1448
|
+
onClick: () => navigate("../list"),
|
|
1449
|
+
children: [/* @__PURE__ */ jsx(ArrowLeft, {
|
|
1450
|
+
className: "mr-2 size-4",
|
|
1451
|
+
"aria-hidden": "true"
|
|
1452
|
+
}), tr("fluidgrids.dataTables.backToWorkflows", "Back to Workflows")]
|
|
1453
|
+
}), /* @__PURE__ */ jsx(SdkPermissionButton, {
|
|
1454
|
+
permission: FLUIDGRIDS_PERMISSIONS.DATA_TABLE_CREATE,
|
|
1455
|
+
deniedTooltip: tr("fluidgrids.dataTables.createDeniedTooltip", "You don't have permission to create data tables"),
|
|
1456
|
+
children: /* @__PURE__ */ jsxs(Button$1, {
|
|
1457
|
+
onClick: () => {
|
|
1458
|
+
setEditingTable(null);
|
|
1459
|
+
setTableDialogOpen(true);
|
|
1460
|
+
},
|
|
1461
|
+
"data-tour": "fluidgrids-data-tables-create",
|
|
1462
|
+
children: [/* @__PURE__ */ jsx(Plus, {
|
|
1463
|
+
className: "mr-2 size-4",
|
|
1464
|
+
"aria-hidden": "true"
|
|
1465
|
+
}), tr("fluidgrids.dataTables.newTable", "New data table")]
|
|
1466
|
+
})
|
|
1467
|
+
})]
|
|
1468
|
+
})]
|
|
1469
|
+
}),
|
|
1470
|
+
/* @__PURE__ */ jsx(NextSteps, {
|
|
1471
|
+
storageKey: "data-tables",
|
|
1472
|
+
title: tr("fluidgrids.dataTables.nextStepsTitle", "How to use Data Tables"),
|
|
1473
|
+
steps: [
|
|
1474
|
+
{
|
|
1475
|
+
id: "create",
|
|
1476
|
+
label: tr("fluidgrids.dataTables.nextStepCreateLabel", "Create a table"),
|
|
1477
|
+
description: tr("fluidgrids.dataTables.nextStepCreateDescription", "Define typed columns for the records your workflow needs."),
|
|
1478
|
+
icon: /* @__PURE__ */ jsx(Plus, {
|
|
1479
|
+
className: "size-4",
|
|
1480
|
+
"aria-hidden": "true"
|
|
1481
|
+
}),
|
|
1482
|
+
onClick: canCreateTable ? () => {
|
|
1483
|
+
setEditingTable(null);
|
|
1484
|
+
setTableDialogOpen(true);
|
|
1485
|
+
} : void 0
|
|
1486
|
+
},
|
|
1487
|
+
{
|
|
1488
|
+
id: "connect",
|
|
1489
|
+
label: tr("fluidgrids.dataTables.nextStepConnectLabel", "Connect it to a workflow"),
|
|
1490
|
+
description: tr("fluidgrids.dataTables.nextStepConnectDescription", "Add a Data Table node to get, insert, update, upsert, or delete rows."),
|
|
1491
|
+
icon: /* @__PURE__ */ jsx(Table2, {
|
|
1492
|
+
className: "size-4",
|
|
1493
|
+
"aria-hidden": "true"
|
|
1494
|
+
}),
|
|
1495
|
+
onClick: () => navigate("../list")
|
|
1496
|
+
},
|
|
1497
|
+
{
|
|
1498
|
+
id: "reuse",
|
|
1499
|
+
label: tr("fluidgrids.dataTables.nextStepReuseLabel", "Reuse data across runs"),
|
|
1500
|
+
description: tr("fluidgrids.dataTables.nextStepReuseDescription", "Use saved status, cursors, lookups, and deduplication keys on later runs."),
|
|
1501
|
+
icon: /* @__PURE__ */ jsx(RefreshCw, {
|
|
1502
|
+
className: "size-4",
|
|
1503
|
+
"aria-hidden": "true"
|
|
1504
|
+
})
|
|
1505
|
+
}
|
|
1506
|
+
]
|
|
1507
|
+
}),
|
|
1508
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1509
|
+
className: "grid gap-5 xl:grid-cols-[minmax(16rem,22rem)_minmax(0,1fr)]",
|
|
1510
|
+
children: [
|
|
1511
|
+
/* @__PURE__ */ jsxs(Card, {
|
|
1512
|
+
className: "h-fit border-border-secondary",
|
|
1513
|
+
"data-tour": "fluidgrids-data-tables-list",
|
|
1514
|
+
children: [/* @__PURE__ */ jsxs(CardHeader, {
|
|
1515
|
+
className: "gap-3 border-b border-border-secondary p-4",
|
|
1516
|
+
children: [
|
|
1517
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1518
|
+
className: "flex items-center justify-between gap-2",
|
|
1519
|
+
children: [/* @__PURE__ */ jsx(CardTitle, {
|
|
1520
|
+
className: "text-base",
|
|
1521
|
+
children: tr("fluidgrids.dataTables.tables", "Tables")
|
|
1522
|
+
}), /* @__PURE__ */ jsx(Badge, {
|
|
1523
|
+
variant: "outline",
|
|
1524
|
+
children: total
|
|
1525
|
+
})]
|
|
1526
|
+
}),
|
|
1527
|
+
/* @__PURE__ */ jsxs("form", {
|
|
1528
|
+
className: "flex gap-2",
|
|
1529
|
+
onSubmit: submitSearch,
|
|
1530
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1531
|
+
className: "relative min-w-0 flex-1",
|
|
1532
|
+
children: [/* @__PURE__ */ jsx(Search, {
|
|
1533
|
+
className: "pointer-events-none absolute left-2.5 top-2.5 size-4 text-text-secondary",
|
|
1534
|
+
"aria-hidden": "true"
|
|
1535
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
1536
|
+
className: "pl-8",
|
|
1537
|
+
value: searchInput,
|
|
1538
|
+
onChange: (event) => setSearchInput(event.target.value),
|
|
1539
|
+
placeholder: tr("fluidgrids.dataTables.search", "Search tables"),
|
|
1540
|
+
"aria-label": tr("fluidgrids.dataTables.search", "Search tables")
|
|
1541
|
+
})]
|
|
1542
|
+
}), /* @__PURE__ */ jsx(Button$1, {
|
|
1543
|
+
type: "submit",
|
|
1544
|
+
variant: "outline",
|
|
1545
|
+
size: "sm",
|
|
1546
|
+
children: tr("fluidgrids.dataTables.searchAction", "Search")
|
|
1547
|
+
})]
|
|
1548
|
+
}),
|
|
1549
|
+
/* @__PURE__ */ jsx(TagFilterSelect, {
|
|
1550
|
+
value: tagFilter,
|
|
1551
|
+
onChange: setTagFilter,
|
|
1552
|
+
className: "w-full"
|
|
1553
|
+
})
|
|
1554
|
+
]
|
|
1555
|
+
}), /* @__PURE__ */ jsxs(CardContent, {
|
|
1556
|
+
className: "max-h-72 overflow-y-auto p-2 xl:max-h-[36rem]",
|
|
1557
|
+
children: [
|
|
1558
|
+
loading && /* @__PURE__ */ jsx("p", {
|
|
1559
|
+
className: "p-4 text-sm text-text-secondary",
|
|
1560
|
+
children: tr("fluidgrids.dataTables.loading", "Loading tables…")
|
|
1561
|
+
}),
|
|
1562
|
+
!loading && tables.length === 0 && tagFilter && /* @__PURE__ */ jsx("p", {
|
|
1563
|
+
className: "p-4 text-sm text-text-secondary",
|
|
1564
|
+
children: tr("fluidgrids.dataTables.noTablesWithTag", "No tables carry this tag.")
|
|
1565
|
+
}),
|
|
1566
|
+
!loading && tables.length === 0 && !tagFilter && /* @__PURE__ */ jsx(IllustratedEmptyState, {
|
|
1567
|
+
className: "py-8",
|
|
1568
|
+
illustration: "onboarding",
|
|
1569
|
+
title: tr("fluidgrids.dataTables.empty", "Create your first Data Table"),
|
|
1570
|
+
description: tr("fluidgrids.dataTables.emptyHint", "Give your workflows a shared place to store typed records across runs."),
|
|
1571
|
+
action: /* @__PURE__ */ jsx(SdkPermissionButton, {
|
|
1572
|
+
permission: FLUIDGRIDS_PERMISSIONS.DATA_TABLE_CREATE,
|
|
1573
|
+
deniedTooltip: tr("fluidgrids.dataTables.createDeniedTooltip", "You don't have permission to create data tables"),
|
|
1574
|
+
children: /* @__PURE__ */ jsxs(Button$1, {
|
|
1575
|
+
size: "sm",
|
|
1576
|
+
onClick: () => {
|
|
1577
|
+
setEditingTable(null);
|
|
1578
|
+
setTableDialogOpen(true);
|
|
1579
|
+
},
|
|
1580
|
+
children: [/* @__PURE__ */ jsx(Plus, {
|
|
1581
|
+
className: "mr-2 size-4",
|
|
1582
|
+
"aria-hidden": "true"
|
|
1583
|
+
}), tr("fluidgrids.dataTables.createFirstTable", "Create a table")]
|
|
1584
|
+
})
|
|
1585
|
+
})
|
|
1586
|
+
}),
|
|
1587
|
+
!loading && tables.map((table) => /* @__PURE__ */ jsxs("button", {
|
|
1588
|
+
type: "button",
|
|
1589
|
+
onClick: () => setSelectedId(table.id),
|
|
1590
|
+
className: `mb-1 flex w-full items-start justify-between gap-3 rounded-md px-3 py-3 text-left transition-colors ${selectedId === table.id ? "bg-background-tertiary text-text-primary" : "text-text-secondary hover:bg-background-secondary hover:text-text-primary"}`,
|
|
1591
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
1592
|
+
className: "min-w-0",
|
|
1593
|
+
children: [
|
|
1594
|
+
/* @__PURE__ */ jsx("span", {
|
|
1595
|
+
className: "block truncate text-sm font-medium",
|
|
1596
|
+
children: table.name
|
|
1597
|
+
}),
|
|
1598
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1599
|
+
className: "mt-1 block text-xs text-text-secondary",
|
|
1600
|
+
children: [
|
|
1601
|
+
table.rowCount,
|
|
1602
|
+
" ",
|
|
1603
|
+
tr("fluidgrids.dataTables.rows", "rows"),
|
|
1604
|
+
" ·",
|
|
1605
|
+
" ",
|
|
1606
|
+
table.columns.length,
|
|
1607
|
+
" ",
|
|
1608
|
+
tr("fluidgrids.dataTables.columns", "columns")
|
|
1609
|
+
]
|
|
1610
|
+
}),
|
|
1611
|
+
(tagsById[table.id] ?? []).length > 0 && /* @__PURE__ */ jsx("span", {
|
|
1612
|
+
className: "mt-1.5 flex flex-wrap gap-1",
|
|
1613
|
+
children: (tagsById[table.id] ?? []).map((assignment) => /* @__PURE__ */ jsx(TagBadge, {
|
|
1614
|
+
id: assignment.tagId,
|
|
1615
|
+
label: tagLabel(assignment),
|
|
1616
|
+
color: assignment.tagInfo?.color ?? void 0,
|
|
1617
|
+
size: "sm"
|
|
1618
|
+
}, assignment.tagId))
|
|
1619
|
+
})
|
|
1620
|
+
]
|
|
1621
|
+
}), /* @__PURE__ */ jsx(Table2, {
|
|
1622
|
+
className: "mt-0.5 size-4 shrink-0",
|
|
1623
|
+
"aria-hidden": "true"
|
|
1624
|
+
})]
|
|
1625
|
+
}, table.id))
|
|
1626
|
+
]
|
|
1627
|
+
})]
|
|
1628
|
+
}),
|
|
1629
|
+
error && !selectedTable && /* @__PURE__ */ jsx(Card, {
|
|
1630
|
+
className: "border-border-secondary",
|
|
1631
|
+
children: /* @__PURE__ */ jsxs(CardContent, {
|
|
1632
|
+
className: "p-6 text-sm text-text-secondary",
|
|
1633
|
+
children: [/* @__PURE__ */ jsx("p", { children: error.message }), /* @__PURE__ */ jsxs(Button$1, {
|
|
1634
|
+
className: "mt-4",
|
|
1635
|
+
variant: "outline",
|
|
1636
|
+
onClick: () => void refresh(),
|
|
1637
|
+
children: [/* @__PURE__ */ jsx(RefreshCw, {
|
|
1638
|
+
className: "mr-2 size-4",
|
|
1639
|
+
"aria-hidden": "true"
|
|
1640
|
+
}), tr("fluidgrids.dataTables.retry", "Try again")]
|
|
1641
|
+
})]
|
|
1642
|
+
})
|
|
1643
|
+
}),
|
|
1644
|
+
!error && !selectedTable && !loading && /* @__PURE__ */ jsx(Card, {
|
|
1645
|
+
className: "border-border-secondary",
|
|
1646
|
+
children: /* @__PURE__ */ jsxs(CardContent, {
|
|
1647
|
+
className: "flex min-h-[22rem] flex-col items-center justify-center p-6 text-center",
|
|
1648
|
+
children: [
|
|
1649
|
+
/* @__PURE__ */ jsx(Database, {
|
|
1650
|
+
className: "mb-3 size-10 text-text-secondary",
|
|
1651
|
+
"aria-hidden": "true"
|
|
1652
|
+
}),
|
|
1653
|
+
/* @__PURE__ */ jsx("h2", {
|
|
1654
|
+
className: "text-lg font-semibold text-text-primary",
|
|
1655
|
+
children: tr("fluidgrids.dataTables.selectTitle", "Select a data table")
|
|
1656
|
+
}),
|
|
1657
|
+
/* @__PURE__ */ jsx("p", {
|
|
1658
|
+
className: "mt-1 max-w-sm text-sm text-text-secondary",
|
|
1659
|
+
children: tr("fluidgrids.dataTables.selectDescription", "Choose a table to view and edit its rows, or create your first table.")
|
|
1660
|
+
})
|
|
1661
|
+
]
|
|
1662
|
+
})
|
|
1663
|
+
}),
|
|
1664
|
+
selectedTable && /* @__PURE__ */ jsx("div", {
|
|
1665
|
+
className: "min-w-0",
|
|
1666
|
+
children: /* @__PURE__ */ jsx(TableRowsPanel, {
|
|
1667
|
+
table: selectedTable,
|
|
1668
|
+
onTableChanged: refresh,
|
|
1669
|
+
onEditTable: () => {
|
|
1670
|
+
setEditingTable(selectedTable);
|
|
1671
|
+
setTableDialogOpen(true);
|
|
1672
|
+
},
|
|
1673
|
+
onDeleteTable: handleDeleteTable,
|
|
1674
|
+
deletingTable: deleting,
|
|
1675
|
+
onTagsChanged: handleTagsChanged
|
|
1676
|
+
}, selectedTable.id)
|
|
1677
|
+
})
|
|
1678
|
+
]
|
|
1679
|
+
})
|
|
1680
|
+
]
|
|
1681
|
+
}), /* @__PURE__ */ jsx(TableFormDialog, {
|
|
1682
|
+
open: tableDialogOpen,
|
|
1683
|
+
table: editingTable,
|
|
1684
|
+
onOpenChange: setTableDialogOpen,
|
|
1685
|
+
onSaved: handleTableSaved
|
|
1686
|
+
})]
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
export { rowsToCsv as a, useDataTables as c, useInsertDataTableRow as d, useUpdateDataTable as f, parseCsv as i, useDeleteDataTable as l, DataTablesPage_exports as n, useCreateDataTable as o, useUpdateDataTableRow as p, csvToRows as r, useDataTableRows as s, DataTablesPage as t, useDeleteDataTableRows as u };
|