@burdenoff/fluidgrids-fe-sdk 2026.923.4 → 2026.923.5
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/index.d.ts +96 -1
- package/dist/index.js +1414 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -31,28 +31,28 @@ export { useActivateWorkflow, useCreateWorkflow, useCreateWorkflowVersion, useDa
|
|
|
31
31
|
export { config, getFluidGridsConfig, getGraphQLEndpoint, resetFluidGridsConfig, setFluidGridsConfig } from './chunk-WWU2O2SR.js';
|
|
32
32
|
export { useFlowStore } from './chunk-YMD3T6J2.js';
|
|
33
33
|
export { ActivateWorkflowDefinitionDocument, ActiveExecutionsDocument, AvailableFluidgridsTagsDocument, CancelExecutionDocument, CreateFluidgridsTagDocument, CreateWorkflowDefinitionDocument, CreateWorkflowVersionDocument, DashboardSummaryDocument, DeactivateWorkflowDefinitionDocument, DeleteWorkflowDefinitionDocument, DuplicateWorkflowDocument, EventRegistrationsDocument, ExecutionDetailDocument, ExecutionLogsDocument, ExecutionStatsDocument, ExecutionsListDocument, FluidgridsAvailableTagFieldsFragmentDoc, FluidgridsEntityTagsDocument, FluidgridsTagAssignmentFieldsFragmentDoc, ImportWorkflowDocument, ImportWorkflowTemplateDocument, NodeEndpointConfigDocument, NodeEndpointConfigsDocument, NodeManifestDocument, NodeSchemaIntrospectionDocument, NodeStatesDocument, NodeUsageStatusDocument, PauseExecutionDocument, PinNodeDataDocument, RegisterNodeDocument, RemoveNodeFromWorkflowsDocument, ReplaceFluidgridsEntityTagsDocument, RestoreWorkflowVersionDocument, ResumeExecutionDocument, RetryNodeDocument, RunFromNodeDocument, RunLogAppendedDocument, RunLogLineFieldsFragmentDoc, RunLogsDocument, SearchFluidgridsTagsDocument, TagFluidgridsEntityDocument, TerminateExecutionDocument, TriggerWorkflowDocument, UnpinNodeDataDocument, UntagFluidgridsEntityDocument, UpdateWorkflowDefinitionDocument, UsageTimeSeriesDocument, WorkflowDefinitionDocument, WorkflowDefinitionsDocument, WorkflowDefinitionsTagsDocument, WorkflowPinnedDataDocument, WorkflowRunDocument, WorkflowRunsDocument, WorkflowVersionsDocument, useActivateWorkflowDefinitionMutation, useActiveExecutionsLazyQuery, useActiveExecutionsQuery, useActiveExecutionsSuspenseQuery, useAvailableFluidgridsTagsLazyQuery, useAvailableFluidgridsTagsQuery, useAvailableFluidgridsTagsSuspenseQuery, useCancelExecutionMutation, useCreateFluidgridsTagMutation, useCreateWorkflowDefinitionMutation, useCreateWorkflowVersionMutation, useDashboardSummaryLazyQuery, useDashboardSummaryQuery, useDashboardSummarySuspenseQuery, useDeactivateWorkflowDefinitionMutation, useDeleteWorkflowDefinitionMutation, useDuplicateWorkflowMutation, useEventRegistrationsLazyQuery, useEventRegistrationsQuery, useEventRegistrationsSuspenseQuery, useExecutionDetailLazyQuery, useExecutionDetailQuery, useExecutionDetailSuspenseQuery, useExecutionLogsLazyQuery, useExecutionLogsQuery, useExecutionLogsSuspenseQuery, useExecutionStatsLazyQuery, useExecutionStatsQuery, useExecutionStatsSuspenseQuery, useExecutionsListLazyQuery, useExecutionsListQuery, useExecutionsListSuspenseQuery, useFluidgridsEntityTagsLazyQuery, useFluidgridsEntityTagsQuery, useFluidgridsEntityTagsSuspenseQuery, useImportWorkflowMutation, useImportWorkflowTemplateMutation, useNodeEndpointConfigLazyQuery, useNodeEndpointConfigQuery, useNodeEndpointConfigSuspenseQuery, useNodeEndpointConfigsLazyQuery, useNodeEndpointConfigsQuery, useNodeEndpointConfigsSuspenseQuery, useNodeManifestLazyQuery, useNodeManifestQuery, useNodeManifestSuspenseQuery, useNodeSchemaIntrospectionLazyQuery, useNodeSchemaIntrospectionQuery, useNodeSchemaIntrospectionSuspenseQuery, useNodeStatesLazyQuery, useNodeStatesQuery, useNodeStatesSuspenseQuery, useNodeUsageStatusLazyQuery, useNodeUsageStatusQuery, useNodeUsageStatusSuspenseQuery, usePauseExecutionMutation, usePinNodeDataMutation, useRegisterNodeMutation, useRemoveNodeFromWorkflowsMutation, useReplaceFluidgridsEntityTagsMutation, useRestoreWorkflowVersionMutation, useResumeExecutionMutation, useRetryNodeMutation, useRunFromNodeMutation, useRunLogAppendedSubscription, useRunLogsLazyQuery, useRunLogsQuery, useRunLogsSuspenseQuery, useSearchFluidgridsTagsLazyQuery, useSearchFluidgridsTagsQuery, useSearchFluidgridsTagsSuspenseQuery, useTagFluidgridsEntityMutation, useTerminateExecutionMutation, useTriggerWorkflowMutation, useUnpinNodeDataMutation, useUntagFluidgridsEntityMutation, useUpdateWorkflowDefinitionMutation, useUsageTimeSeriesLazyQuery, useUsageTimeSeriesQuery, useUsageTimeSeriesSuspenseQuery, useWorkflowDefinitionLazyQuery, useWorkflowDefinitionQuery, useWorkflowDefinitionSuspenseQuery, useWorkflowDefinitionsLazyQuery, useWorkflowDefinitionsQuery, useWorkflowDefinitionsSuspenseQuery, useWorkflowDefinitionsTagsLazyQuery, useWorkflowDefinitionsTagsQuery, useWorkflowDefinitionsTagsSuspenseQuery, useWorkflowPinnedDataLazyQuery, useWorkflowPinnedDataQuery, useWorkflowPinnedDataSuspenseQuery, useWorkflowRunLazyQuery, useWorkflowRunQuery, useWorkflowRunSuspenseQuery, useWorkflowRunsLazyQuery, useWorkflowRunsQuery, useWorkflowRunsSuspenseQuery, useWorkflowVersionsLazyQuery, useWorkflowVersionsQuery, useWorkflowVersionsSuspenseQuery } from './chunk-UBKJDGYS.js';
|
|
34
|
-
import { throwIfResultError } from './chunk-LMO5ASOE.js';
|
|
34
|
+
import { throwIfResultError, describeOperationError } from './chunk-LMO5ASOE.js';
|
|
35
35
|
export { describeOperationError, throwIfResultError } from './chunk-LMO5ASOE.js';
|
|
36
36
|
import { useTr } from './chunk-7YWBH2A5.js';
|
|
37
37
|
export { ENGLISH_FALLBACK_TRANSLATOR, FluidGridsTranslationProvider, useTr } from './chunk-7YWBH2A5.js';
|
|
38
38
|
import { getFluidGridsClient, createFluidGridsClient } from './chunk-5BDQNSP3.js';
|
|
39
39
|
export { createFluidGridsClient, getFluidGridsClient, getFluidGridsClientSafe, isFluidGridsClientReady, resetFluidGridsClient, setFluidGridsClient, waitForFluidGridsClient } from './chunk-5BDQNSP3.js';
|
|
40
40
|
import * as React4 from 'react';
|
|
41
|
-
import { lazy, Suspense, useMemo, useState, useEffect, useCallback } from 'react';
|
|
41
|
+
import { lazy, Suspense, useMemo, useState, useEffect, useCallback, useRef } from 'react';
|
|
42
42
|
import { Routes, Route, Navigate, useNavigate } from 'react-router';
|
|
43
43
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
44
44
|
import { Icon } from '@iconify/react';
|
|
45
45
|
import { ErrorBoundary } from '@burdenoff/fe-libs/shared/components';
|
|
46
46
|
import '@xyflow/react/dist/style.css';
|
|
47
47
|
import { Button } from '@burdenoff/fe-libs/button';
|
|
48
|
-
import { Tabs, TabsList, TabsTrigger, TabsContent, Card, CardContent, Table, TableHeader, TableRow, TableHead, TableBody, TableCell,
|
|
48
|
+
import { Tabs, TabsList, TabsTrigger, TabsContent, Card, CardContent, CardHeader, CardTitle, Badge, Input, Table, TableHeader, TableRow, TableHead, TableBody, TableCell, Label, Switch, CardDescription, Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Textarea } from '@burdenoff/fe-libs/ui';
|
|
49
49
|
import { toast } from 'sonner';
|
|
50
50
|
import { gql } from '@apollo/client';
|
|
51
|
+
import { ChevronDown, ChevronUp, Check, ChevronRight, Circle, Database, ArrowLeft, Plus, Search, Table2, RefreshCw, Pencil, Trash2, ArrowUpFromLine, ArrowDownToLine } from 'lucide-react';
|
|
51
52
|
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
|
52
53
|
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
|
|
53
54
|
import * as SeparatorPrimitive from '@radix-ui/react-separator';
|
|
54
55
|
import * as SelectPrimitive from '@radix-ui/react-select';
|
|
55
|
-
import { ChevronDown, ChevronUp, Check, ChevronRight, Circle } from 'lucide-react';
|
|
56
56
|
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
|
57
57
|
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
|
|
58
58
|
import * as SwitchPrimitives from '@radix-ui/react-switch';
|
|
@@ -1581,6 +1581,1413 @@ function ApprovalInboxPage() {
|
|
|
1581
1581
|
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-4 lg:grid-cols-2", children: items.map((request) => /* @__PURE__ */ jsx(RequestCard, { request, onDecided: refetch }, request.id)) })
|
|
1582
1582
|
] });
|
|
1583
1583
|
}
|
|
1584
|
+
var DATA_TABLE_FIELDS = gql`
|
|
1585
|
+
fragment DataTableFields on DataTable {
|
|
1586
|
+
id
|
|
1587
|
+
workspaceId
|
|
1588
|
+
name
|
|
1589
|
+
description
|
|
1590
|
+
columns {
|
|
1591
|
+
key
|
|
1592
|
+
label
|
|
1593
|
+
type
|
|
1594
|
+
required
|
|
1595
|
+
defaultValue
|
|
1596
|
+
}
|
|
1597
|
+
rowCount
|
|
1598
|
+
sizeBytes
|
|
1599
|
+
createdAt
|
|
1600
|
+
updatedAt
|
|
1601
|
+
createdBy
|
|
1602
|
+
updatedBy
|
|
1603
|
+
}
|
|
1604
|
+
`;
|
|
1605
|
+
var DATA_TABLE_ROW_FIELDS = gql`
|
|
1606
|
+
fragment DataTableRowFields on DataTableRow {
|
|
1607
|
+
id
|
|
1608
|
+
tableId
|
|
1609
|
+
data
|
|
1610
|
+
createdAt
|
|
1611
|
+
updatedAt
|
|
1612
|
+
}
|
|
1613
|
+
`;
|
|
1614
|
+
var GET_DATA_TABLES = gql`
|
|
1615
|
+
${DATA_TABLE_FIELDS}
|
|
1616
|
+
query GetDataTables($search: String, $limit: Int, $offset: Int) {
|
|
1617
|
+
dataTables(search: $search, limit: $limit, offset: $offset) {
|
|
1618
|
+
tables {
|
|
1619
|
+
...DataTableFields
|
|
1620
|
+
}
|
|
1621
|
+
total
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
`;
|
|
1625
|
+
var GET_DATA_TABLE_ROWS = gql`
|
|
1626
|
+
${DATA_TABLE_ROW_FIELDS}
|
|
1627
|
+
query GetDataTableRows($input: DataTableRowsQueryInput!) {
|
|
1628
|
+
dataTableRows(input: $input) {
|
|
1629
|
+
rows {
|
|
1630
|
+
...DataTableRowFields
|
|
1631
|
+
}
|
|
1632
|
+
total
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
`;
|
|
1636
|
+
var CREATE_DATA_TABLE = gql`
|
|
1637
|
+
${DATA_TABLE_FIELDS}
|
|
1638
|
+
mutation CreateDataTable($input: CreateDataTableInput!) {
|
|
1639
|
+
createDataTable(input: $input) {
|
|
1640
|
+
success
|
|
1641
|
+
message
|
|
1642
|
+
table {
|
|
1643
|
+
...DataTableFields
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
`;
|
|
1648
|
+
var UPDATE_DATA_TABLE = gql`
|
|
1649
|
+
${DATA_TABLE_FIELDS}
|
|
1650
|
+
mutation UpdateDataTable($id: ID!, $input: UpdateDataTableInput!) {
|
|
1651
|
+
updateDataTable(id: $id, input: $input) {
|
|
1652
|
+
success
|
|
1653
|
+
message
|
|
1654
|
+
table {
|
|
1655
|
+
...DataTableFields
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
`;
|
|
1660
|
+
var DELETE_DATA_TABLE = gql`
|
|
1661
|
+
mutation DeleteDataTable($id: ID!) {
|
|
1662
|
+
deleteDataTable(id: $id) {
|
|
1663
|
+
success
|
|
1664
|
+
message
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
`;
|
|
1668
|
+
var INSERT_DATA_TABLE_ROW = gql`
|
|
1669
|
+
${DATA_TABLE_ROW_FIELDS}
|
|
1670
|
+
mutation InsertDataTableRow($tableId: ID!, $data: JSON!) {
|
|
1671
|
+
insertDataTableRow(tableId: $tableId, data: $data) {
|
|
1672
|
+
success
|
|
1673
|
+
message
|
|
1674
|
+
row {
|
|
1675
|
+
...DataTableRowFields
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
`;
|
|
1680
|
+
var UPDATE_DATA_TABLE_ROW = gql`
|
|
1681
|
+
${DATA_TABLE_ROW_FIELDS}
|
|
1682
|
+
mutation UpdateDataTableRow($id: ID!, $data: JSON!) {
|
|
1683
|
+
updateDataTableRow(id: $id, data: $data) {
|
|
1684
|
+
success
|
|
1685
|
+
message
|
|
1686
|
+
row {
|
|
1687
|
+
...DataTableRowFields
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
`;
|
|
1692
|
+
var DELETE_DATA_TABLE_ROWS = gql`
|
|
1693
|
+
mutation DeleteDataTableRows($tableId: ID!, $ids: [ID!]!) {
|
|
1694
|
+
deleteDataTableRows(tableId: $tableId, ids: $ids) {
|
|
1695
|
+
success
|
|
1696
|
+
message
|
|
1697
|
+
affected
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
`;
|
|
1701
|
+
function toRow(row) {
|
|
1702
|
+
return {
|
|
1703
|
+
id: row.id,
|
|
1704
|
+
tableId: row.tableId,
|
|
1705
|
+
data: toRecord(row.data),
|
|
1706
|
+
createdAt: row.createdAt,
|
|
1707
|
+
updatedAt: row.updatedAt
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
function toRecord(value) {
|
|
1711
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1712
|
+
}
|
|
1713
|
+
function operationError(error, fallback) {
|
|
1714
|
+
return new Error(describeOperationError(error, fallback));
|
|
1715
|
+
}
|
|
1716
|
+
function useDataTables(search) {
|
|
1717
|
+
const [tables, setTables] = useState([]);
|
|
1718
|
+
const [total, setTotal] = useState(0);
|
|
1719
|
+
const [loading, setLoading] = useState(true);
|
|
1720
|
+
const [error, setError] = useState(null);
|
|
1721
|
+
const fetchTables = useCallback(async () => {
|
|
1722
|
+
setLoading(true);
|
|
1723
|
+
setError(null);
|
|
1724
|
+
try {
|
|
1725
|
+
const result = throwIfResultError(
|
|
1726
|
+
await getFluidGridsClient().query({
|
|
1727
|
+
query: GET_DATA_TABLES,
|
|
1728
|
+
variables: {
|
|
1729
|
+
search: search?.trim() || void 0,
|
|
1730
|
+
limit: 200,
|
|
1731
|
+
offset: 0
|
|
1732
|
+
},
|
|
1733
|
+
fetchPolicy: "network-only"
|
|
1734
|
+
})
|
|
1735
|
+
);
|
|
1736
|
+
setTables(result.data?.dataTables.tables ?? []);
|
|
1737
|
+
setTotal(result.data?.dataTables.total ?? 0);
|
|
1738
|
+
} catch (err) {
|
|
1739
|
+
setError(operationError(err, "Failed to load data tables"));
|
|
1740
|
+
} finally {
|
|
1741
|
+
setLoading(false);
|
|
1742
|
+
}
|
|
1743
|
+
}, [search]);
|
|
1744
|
+
useEffect(() => {
|
|
1745
|
+
void fetchTables();
|
|
1746
|
+
}, [fetchTables]);
|
|
1747
|
+
return { tables, total, loading, error, refetch: fetchTables };
|
|
1748
|
+
}
|
|
1749
|
+
function useDataTableRows(tableId) {
|
|
1750
|
+
const [rows, setRows] = useState([]);
|
|
1751
|
+
const [total, setTotal] = useState(0);
|
|
1752
|
+
const [loading, setLoading] = useState(false);
|
|
1753
|
+
const [error, setError] = useState(null);
|
|
1754
|
+
const fetchRows = useCallback(async () => {
|
|
1755
|
+
if (!tableId) {
|
|
1756
|
+
setRows([]);
|
|
1757
|
+
setTotal(0);
|
|
1758
|
+
setLoading(false);
|
|
1759
|
+
setError(null);
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
1762
|
+
setLoading(true);
|
|
1763
|
+
setError(null);
|
|
1764
|
+
try {
|
|
1765
|
+
const result = throwIfResultError(
|
|
1766
|
+
await getFluidGridsClient().query({
|
|
1767
|
+
query: GET_DATA_TABLE_ROWS,
|
|
1768
|
+
variables: { input: { tableId, limit: 5e3, offset: 0 } },
|
|
1769
|
+
fetchPolicy: "network-only"
|
|
1770
|
+
})
|
|
1771
|
+
);
|
|
1772
|
+
const response = result.data?.dataTableRows;
|
|
1773
|
+
setRows(response?.rows.map(toRow) ?? []);
|
|
1774
|
+
setTotal(response?.total ?? 0);
|
|
1775
|
+
} catch (err) {
|
|
1776
|
+
setError(operationError(err, "Failed to load data table rows"));
|
|
1777
|
+
} finally {
|
|
1778
|
+
setLoading(false);
|
|
1779
|
+
}
|
|
1780
|
+
}, [tableId]);
|
|
1781
|
+
useEffect(() => {
|
|
1782
|
+
void fetchRows();
|
|
1783
|
+
}, [fetchRows]);
|
|
1784
|
+
return { rows, total, loading, error, refetch: fetchRows };
|
|
1785
|
+
}
|
|
1786
|
+
function useCreateDataTable() {
|
|
1787
|
+
const [loading, setLoading] = useState(false);
|
|
1788
|
+
const createDataTable = useCallback(async (input) => {
|
|
1789
|
+
setLoading(true);
|
|
1790
|
+
try {
|
|
1791
|
+
const result = throwIfResultError(
|
|
1792
|
+
await getFluidGridsClient().mutate({ mutation: CREATE_DATA_TABLE, variables: { input } })
|
|
1793
|
+
);
|
|
1794
|
+
const response = result.data?.createDataTable;
|
|
1795
|
+
if (!response?.success) {
|
|
1796
|
+
throw new Error(response?.message || "Failed to create data table");
|
|
1797
|
+
}
|
|
1798
|
+
return response.table ?? null;
|
|
1799
|
+
} finally {
|
|
1800
|
+
setLoading(false);
|
|
1801
|
+
}
|
|
1802
|
+
}, []);
|
|
1803
|
+
return { createDataTable, loading };
|
|
1804
|
+
}
|
|
1805
|
+
function useUpdateDataTable() {
|
|
1806
|
+
const [loading, setLoading] = useState(false);
|
|
1807
|
+
const updateDataTable = useCallback(
|
|
1808
|
+
async (id, input) => {
|
|
1809
|
+
setLoading(true);
|
|
1810
|
+
try {
|
|
1811
|
+
const result = throwIfResultError(
|
|
1812
|
+
await getFluidGridsClient().mutate({ mutation: UPDATE_DATA_TABLE, variables: { id, input } })
|
|
1813
|
+
);
|
|
1814
|
+
const response = result.data?.updateDataTable;
|
|
1815
|
+
if (!response?.success) {
|
|
1816
|
+
throw new Error(response?.message || "Failed to update data table");
|
|
1817
|
+
}
|
|
1818
|
+
return response.table ?? null;
|
|
1819
|
+
} finally {
|
|
1820
|
+
setLoading(false);
|
|
1821
|
+
}
|
|
1822
|
+
},
|
|
1823
|
+
[]
|
|
1824
|
+
);
|
|
1825
|
+
return { updateDataTable, loading };
|
|
1826
|
+
}
|
|
1827
|
+
function useDeleteDataTable() {
|
|
1828
|
+
const [loading, setLoading] = useState(false);
|
|
1829
|
+
const deleteDataTable = useCallback(async (id) => {
|
|
1830
|
+
setLoading(true);
|
|
1831
|
+
try {
|
|
1832
|
+
const result = throwIfResultError(
|
|
1833
|
+
await getFluidGridsClient().mutate({ mutation: DELETE_DATA_TABLE, variables: { id } })
|
|
1834
|
+
);
|
|
1835
|
+
const response = result.data?.deleteDataTable;
|
|
1836
|
+
if (!response?.success) {
|
|
1837
|
+
throw new Error(response?.message || "Failed to delete data table");
|
|
1838
|
+
}
|
|
1839
|
+
return true;
|
|
1840
|
+
} finally {
|
|
1841
|
+
setLoading(false);
|
|
1842
|
+
}
|
|
1843
|
+
}, []);
|
|
1844
|
+
return { deleteDataTable, loading };
|
|
1845
|
+
}
|
|
1846
|
+
function useInsertDataTableRow() {
|
|
1847
|
+
const [loading, setLoading] = useState(false);
|
|
1848
|
+
const insertDataTableRow = useCallback(
|
|
1849
|
+
async (tableId, data) => {
|
|
1850
|
+
setLoading(true);
|
|
1851
|
+
try {
|
|
1852
|
+
const result = throwIfResultError(
|
|
1853
|
+
await getFluidGridsClient().mutate({
|
|
1854
|
+
mutation: INSERT_DATA_TABLE_ROW,
|
|
1855
|
+
variables: { tableId, data }
|
|
1856
|
+
})
|
|
1857
|
+
);
|
|
1858
|
+
const response = result.data?.insertDataTableRow;
|
|
1859
|
+
if (!response?.success) {
|
|
1860
|
+
throw new Error(response?.message || "Failed to insert row");
|
|
1861
|
+
}
|
|
1862
|
+
return response.row ? toRow(response.row) : null;
|
|
1863
|
+
} finally {
|
|
1864
|
+
setLoading(false);
|
|
1865
|
+
}
|
|
1866
|
+
},
|
|
1867
|
+
[]
|
|
1868
|
+
);
|
|
1869
|
+
return { insertDataTableRow, loading };
|
|
1870
|
+
}
|
|
1871
|
+
function useUpdateDataTableRow() {
|
|
1872
|
+
const [loading, setLoading] = useState(false);
|
|
1873
|
+
const updateDataTableRow = useCallback(
|
|
1874
|
+
async (id, data) => {
|
|
1875
|
+
setLoading(true);
|
|
1876
|
+
try {
|
|
1877
|
+
const result = throwIfResultError(
|
|
1878
|
+
await getFluidGridsClient().mutate({ mutation: UPDATE_DATA_TABLE_ROW, variables: { id, data } })
|
|
1879
|
+
);
|
|
1880
|
+
const response = result.data?.updateDataTableRow;
|
|
1881
|
+
if (!response?.success) {
|
|
1882
|
+
throw new Error(response?.message || "Failed to update row");
|
|
1883
|
+
}
|
|
1884
|
+
return response.row ? toRow(response.row) : null;
|
|
1885
|
+
} finally {
|
|
1886
|
+
setLoading(false);
|
|
1887
|
+
}
|
|
1888
|
+
},
|
|
1889
|
+
[]
|
|
1890
|
+
);
|
|
1891
|
+
return { updateDataTableRow, loading };
|
|
1892
|
+
}
|
|
1893
|
+
function useDeleteDataTableRows() {
|
|
1894
|
+
const [loading, setLoading] = useState(false);
|
|
1895
|
+
const deleteDataTableRows = useCallback(
|
|
1896
|
+
async (tableId, ids) => {
|
|
1897
|
+
setLoading(true);
|
|
1898
|
+
try {
|
|
1899
|
+
const result = throwIfResultError(
|
|
1900
|
+
await getFluidGridsClient().mutate({
|
|
1901
|
+
mutation: DELETE_DATA_TABLE_ROWS,
|
|
1902
|
+
variables: { tableId, ids }
|
|
1903
|
+
})
|
|
1904
|
+
);
|
|
1905
|
+
const response = result.data?.deleteDataTableRows;
|
|
1906
|
+
if (!response?.success) {
|
|
1907
|
+
throw new Error(response?.message || "Failed to delete rows");
|
|
1908
|
+
}
|
|
1909
|
+
return response.affected;
|
|
1910
|
+
} finally {
|
|
1911
|
+
setLoading(false);
|
|
1912
|
+
}
|
|
1913
|
+
},
|
|
1914
|
+
[]
|
|
1915
|
+
);
|
|
1916
|
+
return { deleteDataTableRows, loading };
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
// src/modules/data-tables/utils/csv.ts
|
|
1920
|
+
function parseCsv(text) {
|
|
1921
|
+
const rows = [];
|
|
1922
|
+
let row = [];
|
|
1923
|
+
let cell = "";
|
|
1924
|
+
let quoted = false;
|
|
1925
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
1926
|
+
const character = text[index];
|
|
1927
|
+
const next = text[index + 1];
|
|
1928
|
+
if (quoted) {
|
|
1929
|
+
if (character === '"' && next === '"') {
|
|
1930
|
+
cell += '"';
|
|
1931
|
+
index += 1;
|
|
1932
|
+
} else if (character === '"') {
|
|
1933
|
+
quoted = false;
|
|
1934
|
+
} else {
|
|
1935
|
+
cell += character;
|
|
1936
|
+
}
|
|
1937
|
+
continue;
|
|
1938
|
+
}
|
|
1939
|
+
if (character === '"' && cell.length === 0) {
|
|
1940
|
+
quoted = true;
|
|
1941
|
+
} else if (character === ",") {
|
|
1942
|
+
row.push(cell);
|
|
1943
|
+
cell = "";
|
|
1944
|
+
} else if (character === "\n" || character === "\r") {
|
|
1945
|
+
if (character === "\r" && next === "\n") index += 1;
|
|
1946
|
+
row.push(cell);
|
|
1947
|
+
cell = "";
|
|
1948
|
+
if (row.some((value) => value.length > 0)) rows.push(row);
|
|
1949
|
+
row = [];
|
|
1950
|
+
} else {
|
|
1951
|
+
cell += character;
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
if (quoted) {
|
|
1955
|
+
throw new Error("CSV contains an unterminated quoted value");
|
|
1956
|
+
}
|
|
1957
|
+
if (cell.length > 0 || row.length > 0) {
|
|
1958
|
+
row.push(cell);
|
|
1959
|
+
if (row.some((value) => value.length > 0)) rows.push(row);
|
|
1960
|
+
}
|
|
1961
|
+
return rows;
|
|
1962
|
+
}
|
|
1963
|
+
function parseValue(value, type) {
|
|
1964
|
+
if (value === "") {
|
|
1965
|
+
return type === "STRING" ? "" : null;
|
|
1966
|
+
}
|
|
1967
|
+
switch (type) {
|
|
1968
|
+
case "NUMBER": {
|
|
1969
|
+
const parsed = Number(value);
|
|
1970
|
+
if (!Number.isFinite(parsed))
|
|
1971
|
+
throw new Error(`Expected a number, got "${value}"`);
|
|
1972
|
+
return parsed;
|
|
1973
|
+
}
|
|
1974
|
+
case "BOOLEAN": {
|
|
1975
|
+
const normalized = value.trim().toLowerCase();
|
|
1976
|
+
if (normalized === "true") return true;
|
|
1977
|
+
if (normalized === "false") return false;
|
|
1978
|
+
throw new Error(`Expected true or false, got "${value}"`);
|
|
1979
|
+
}
|
|
1980
|
+
case "JSON":
|
|
1981
|
+
try {
|
|
1982
|
+
return JSON.parse(value);
|
|
1983
|
+
} catch {
|
|
1984
|
+
throw new Error(`Expected valid JSON, got "${value}"`);
|
|
1985
|
+
}
|
|
1986
|
+
case "DATE":
|
|
1987
|
+
case "STRING":
|
|
1988
|
+
return value;
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
function csvToRows(text, columns) {
|
|
1992
|
+
const parsed = parseCsv(text);
|
|
1993
|
+
if (parsed.length < 2) return [];
|
|
1994
|
+
const headers = parsed[0].map((header) => header.trim());
|
|
1995
|
+
const columnByKey = new Map(columns.map((column) => [column.key, column]));
|
|
1996
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1997
|
+
for (const header of headers) {
|
|
1998
|
+
if (!header || seen.has(header)) {
|
|
1999
|
+
throw new Error("CSV must have unique, non-empty column headers");
|
|
2000
|
+
}
|
|
2001
|
+
if (!columnByKey.has(header)) {
|
|
2002
|
+
throw new Error(`CSV contains an unknown column "${header}"`);
|
|
2003
|
+
}
|
|
2004
|
+
seen.add(header);
|
|
2005
|
+
}
|
|
2006
|
+
return parsed.slice(1).map((values, rowIndex) => {
|
|
2007
|
+
const row = {};
|
|
2008
|
+
headers.forEach((header, columnIndex) => {
|
|
2009
|
+
const column = columnByKey.get(header);
|
|
2010
|
+
if (!column) return;
|
|
2011
|
+
const rawValue = values[columnIndex] ?? "";
|
|
2012
|
+
try {
|
|
2013
|
+
row[header] = parseValue(rawValue, column.type);
|
|
2014
|
+
} catch (error) {
|
|
2015
|
+
const message = error instanceof Error ? error.message : "Invalid value";
|
|
2016
|
+
throw new Error(`Row ${rowIndex + 2}, ${column.label}: ${message}`);
|
|
2017
|
+
}
|
|
2018
|
+
});
|
|
2019
|
+
return row;
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
2022
|
+
function csvCell(value) {
|
|
2023
|
+
if (value === null || value === void 0) return "";
|
|
2024
|
+
const text = typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
2025
|
+
return /[",\r\n]/.test(text) ? `"${text.replaceAll('"', '""')}"` : text;
|
|
2026
|
+
}
|
|
2027
|
+
function rowsToCsv(columns, rows) {
|
|
2028
|
+
const header = columns.map((column) => csvCell(column.key)).join(",");
|
|
2029
|
+
const values = rows.map(
|
|
2030
|
+
(row) => columns.map((column) => csvCell(row.data[column.key])).join(",")
|
|
2031
|
+
);
|
|
2032
|
+
return [header, ...values].join("\r\n");
|
|
2033
|
+
}
|
|
2034
|
+
var COLUMN_TYPES = [
|
|
2035
|
+
"STRING",
|
|
2036
|
+
"NUMBER",
|
|
2037
|
+
"BOOLEAN",
|
|
2038
|
+
"DATE",
|
|
2039
|
+
"JSON"
|
|
2040
|
+
];
|
|
2041
|
+
function emptyColumn(index) {
|
|
2042
|
+
return {
|
|
2043
|
+
key: `column_${index}`,
|
|
2044
|
+
label: `Column ${index}`,
|
|
2045
|
+
type: "STRING",
|
|
2046
|
+
required: false
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
2049
|
+
function formatBytes(bytes) {
|
|
2050
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
2051
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
2052
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
2053
|
+
}
|
|
2054
|
+
function formatValue(value) {
|
|
2055
|
+
if (value === null || value === void 0) return "\u2014";
|
|
2056
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
2057
|
+
return String(value);
|
|
2058
|
+
}
|
|
2059
|
+
function columnTypeLabel(type) {
|
|
2060
|
+
return type === "JSON" ? "JSON" : type[0] + type.slice(1).toLowerCase();
|
|
2061
|
+
}
|
|
2062
|
+
function asFormValue(value, type) {
|
|
2063
|
+
if (value === null || value === void 0) return "";
|
|
2064
|
+
if (type === "JSON") return JSON.stringify(value, null, 2);
|
|
2065
|
+
return String(value);
|
|
2066
|
+
}
|
|
2067
|
+
function parseFormValue(value, type) {
|
|
2068
|
+
if (type === "STRING" || type === "DATE") return value;
|
|
2069
|
+
if (value.trim() === "") return null;
|
|
2070
|
+
if (type === "NUMBER") {
|
|
2071
|
+
const number = Number(value);
|
|
2072
|
+
if (!Number.isFinite(number)) throw new Error(`"${value}" is not a number`);
|
|
2073
|
+
return number;
|
|
2074
|
+
}
|
|
2075
|
+
if (type === "BOOLEAN") {
|
|
2076
|
+
if (value === "true") return true;
|
|
2077
|
+
if (value === "false") return false;
|
|
2078
|
+
throw new Error("Boolean values must be true or false");
|
|
2079
|
+
}
|
|
2080
|
+
try {
|
|
2081
|
+
return JSON.parse(value);
|
|
2082
|
+
} catch {
|
|
2083
|
+
throw new Error("JSON values must contain valid JSON");
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
function TableFormDialog({
|
|
2087
|
+
open,
|
|
2088
|
+
table,
|
|
2089
|
+
onOpenChange,
|
|
2090
|
+
onSaved
|
|
2091
|
+
}) {
|
|
2092
|
+
const tr = useTr();
|
|
2093
|
+
const { createDataTable, loading: creating } = useCreateDataTable();
|
|
2094
|
+
const { updateDataTable, loading: updating } = useUpdateDataTable();
|
|
2095
|
+
const [name, setName] = useState("");
|
|
2096
|
+
const [description, setDescription] = useState("");
|
|
2097
|
+
const [columns, setColumns] = useState([]);
|
|
2098
|
+
const editing = table !== null;
|
|
2099
|
+
const saving = creating || updating;
|
|
2100
|
+
useEffect(() => {
|
|
2101
|
+
if (!open) return;
|
|
2102
|
+
setName(table?.name ?? "");
|
|
2103
|
+
setDescription(table?.description ?? "");
|
|
2104
|
+
setColumns(
|
|
2105
|
+
table?.columns.map((column) => ({
|
|
2106
|
+
key: column.key,
|
|
2107
|
+
label: column.label,
|
|
2108
|
+
type: column.type,
|
|
2109
|
+
required: column.required,
|
|
2110
|
+
...column.defaultValue !== void 0 ? { defaultValue: column.defaultValue } : {}
|
|
2111
|
+
})) ?? [emptyColumn(1)]
|
|
2112
|
+
);
|
|
2113
|
+
}, [open, table]);
|
|
2114
|
+
const updateColumn = useCallback(
|
|
2115
|
+
(index, patch) => {
|
|
2116
|
+
setColumns(
|
|
2117
|
+
(current) => current.map(
|
|
2118
|
+
(column, columnIndex) => columnIndex === index ? { ...column, ...patch } : column
|
|
2119
|
+
)
|
|
2120
|
+
);
|
|
2121
|
+
},
|
|
2122
|
+
[]
|
|
2123
|
+
);
|
|
2124
|
+
const handleSubmit = useCallback(async () => {
|
|
2125
|
+
const trimmedName = name.trim();
|
|
2126
|
+
if (!trimmedName) {
|
|
2127
|
+
toast.error(
|
|
2128
|
+
tr("fluidgrids.dataTables.nameRequired", "Table name is required")
|
|
2129
|
+
);
|
|
2130
|
+
return;
|
|
2131
|
+
}
|
|
2132
|
+
if (columns.length === 0 || columns.some((column) => !column.key.trim() || !column.label.trim())) {
|
|
2133
|
+
toast.error(
|
|
2134
|
+
tr(
|
|
2135
|
+
"fluidgrids.dataTables.columnsRequired",
|
|
2136
|
+
"Add at least one column with a key and label"
|
|
2137
|
+
)
|
|
2138
|
+
);
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
2141
|
+
const normalizedColumns = columns.map((column) => ({
|
|
2142
|
+
...column,
|
|
2143
|
+
key: column.key.trim(),
|
|
2144
|
+
label: column.label.trim()
|
|
2145
|
+
}));
|
|
2146
|
+
const keys = new Set(normalizedColumns.map((column) => column.key));
|
|
2147
|
+
if (keys.size !== normalizedColumns.length) {
|
|
2148
|
+
toast.error(
|
|
2149
|
+
tr("fluidgrids.dataTables.uniqueKeys", "Column keys must be unique")
|
|
2150
|
+
);
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
try {
|
|
2154
|
+
const saved = editing ? await updateDataTable(table.id, {
|
|
2155
|
+
name: trimmedName,
|
|
2156
|
+
description: description.trim() || void 0,
|
|
2157
|
+
columns: normalizedColumns
|
|
2158
|
+
}) : await createDataTable({
|
|
2159
|
+
name: trimmedName,
|
|
2160
|
+
description: description.trim() || void 0,
|
|
2161
|
+
columns: normalizedColumns
|
|
2162
|
+
});
|
|
2163
|
+
toast.success(
|
|
2164
|
+
editing ? tr("fluidgrids.dataTables.updated", "Data table updated") : tr("fluidgrids.dataTables.created", "Data table created")
|
|
2165
|
+
);
|
|
2166
|
+
onSaved(saved);
|
|
2167
|
+
onOpenChange(false);
|
|
2168
|
+
} catch (error) {
|
|
2169
|
+
toast.error(
|
|
2170
|
+
describeOperationError(
|
|
2171
|
+
error,
|
|
2172
|
+
tr("fluidgrids.dataTables.saveFailed", "Failed to save data table")
|
|
2173
|
+
)
|
|
2174
|
+
);
|
|
2175
|
+
}
|
|
2176
|
+
}, [
|
|
2177
|
+
columns,
|
|
2178
|
+
createDataTable,
|
|
2179
|
+
description,
|
|
2180
|
+
editing,
|
|
2181
|
+
name,
|
|
2182
|
+
onOpenChange,
|
|
2183
|
+
onSaved,
|
|
2184
|
+
table,
|
|
2185
|
+
tr,
|
|
2186
|
+
updateDataTable
|
|
2187
|
+
]);
|
|
2188
|
+
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-h-[90vh] max-w-3xl overflow-y-auto", children: [
|
|
2189
|
+
/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: editing ? tr("fluidgrids.dataTables.editTable", "Edit data table") : tr("fluidgrids.dataTables.newTable", "New data table") }) }),
|
|
2190
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-5 py-2", children: [
|
|
2191
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
|
|
2192
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
2193
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "data-table-name", children: tr("fluidgrids.dataTables.name", "Name") }),
|
|
2194
|
+
/* @__PURE__ */ jsx(
|
|
2195
|
+
Input,
|
|
2196
|
+
{
|
|
2197
|
+
id: "data-table-name",
|
|
2198
|
+
value: name,
|
|
2199
|
+
onChange: (event) => setName(event.target.value),
|
|
2200
|
+
placeholder: "contacts",
|
|
2201
|
+
autoFocus: true
|
|
2202
|
+
}
|
|
2203
|
+
)
|
|
2204
|
+
] }),
|
|
2205
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
2206
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "data-table-description", children: tr("fluidgrids.dataTables.description", "Description") }),
|
|
2207
|
+
/* @__PURE__ */ jsx(
|
|
2208
|
+
Input,
|
|
2209
|
+
{
|
|
2210
|
+
id: "data-table-description",
|
|
2211
|
+
value: description,
|
|
2212
|
+
onChange: (event) => setDescription(event.target.value),
|
|
2213
|
+
placeholder: tr(
|
|
2214
|
+
"fluidgrids.dataTables.descriptionPlaceholder",
|
|
2215
|
+
"Optional"
|
|
2216
|
+
)
|
|
2217
|
+
}
|
|
2218
|
+
)
|
|
2219
|
+
] })
|
|
2220
|
+
] }),
|
|
2221
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
2222
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
|
|
2223
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2224
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-text-primary", children: tr("fluidgrids.dataTables.columns", "Columns") }),
|
|
2225
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary", children: tr(
|
|
2226
|
+
"fluidgrids.dataTables.columnsHint",
|
|
2227
|
+
"Column keys are used by workflow nodes and must be unique."
|
|
2228
|
+
) })
|
|
2229
|
+
] }),
|
|
2230
|
+
/* @__PURE__ */ jsxs(
|
|
2231
|
+
Button,
|
|
2232
|
+
{
|
|
2233
|
+
type: "button",
|
|
2234
|
+
variant: "outline",
|
|
2235
|
+
size: "sm",
|
|
2236
|
+
onClick: () => setColumns((current) => [
|
|
2237
|
+
...current,
|
|
2238
|
+
emptyColumn(current.length + 1)
|
|
2239
|
+
]),
|
|
2240
|
+
children: [
|
|
2241
|
+
/* @__PURE__ */ jsx(Plus, { className: "mr-1.5 size-4", "aria-hidden": "true" }),
|
|
2242
|
+
tr("fluidgrids.dataTables.addColumn", "Add column")
|
|
2243
|
+
]
|
|
2244
|
+
}
|
|
2245
|
+
)
|
|
2246
|
+
] }),
|
|
2247
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-2", children: columns.map((column, index) => /* @__PURE__ */ jsxs(
|
|
2248
|
+
"div",
|
|
2249
|
+
{
|
|
2250
|
+
className: "grid gap-2 rounded-md border border-border-secondary p-3 sm:grid-cols-[1fr_1fr_8rem_auto] sm:items-end",
|
|
2251
|
+
children: [
|
|
2252
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
2253
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: `data-table-column-key-${index}`, children: tr("fluidgrids.dataTables.key", "Key") }),
|
|
2254
|
+
/* @__PURE__ */ jsx(
|
|
2255
|
+
Input,
|
|
2256
|
+
{
|
|
2257
|
+
id: `data-table-column-key-${index}`,
|
|
2258
|
+
value: column.key,
|
|
2259
|
+
onChange: (event) => updateColumn(index, { key: event.target.value })
|
|
2260
|
+
}
|
|
2261
|
+
)
|
|
2262
|
+
] }),
|
|
2263
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
2264
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: `data-table-column-label-${index}`, children: tr("fluidgrids.dataTables.label", "Label") }),
|
|
2265
|
+
/* @__PURE__ */ jsx(
|
|
2266
|
+
Input,
|
|
2267
|
+
{
|
|
2268
|
+
id: `data-table-column-label-${index}`,
|
|
2269
|
+
value: column.label,
|
|
2270
|
+
onChange: (event) => updateColumn(index, { label: event.target.value })
|
|
2271
|
+
}
|
|
2272
|
+
)
|
|
2273
|
+
] }),
|
|
2274
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
2275
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: `data-table-column-type-${index}`, children: tr("fluidgrids.dataTables.type", "Type") }),
|
|
2276
|
+
/* @__PURE__ */ jsx(
|
|
2277
|
+
"select",
|
|
2278
|
+
{
|
|
2279
|
+
id: `data-table-column-type-${index}`,
|
|
2280
|
+
className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-text-primary",
|
|
2281
|
+
value: column.type,
|
|
2282
|
+
onChange: (event) => updateColumn(index, {
|
|
2283
|
+
type: event.target.value
|
|
2284
|
+
}),
|
|
2285
|
+
children: COLUMN_TYPES.map((type) => /* @__PURE__ */ jsx("option", { value: type, children: columnTypeLabel(type) }, type))
|
|
2286
|
+
}
|
|
2287
|
+
)
|
|
2288
|
+
] }),
|
|
2289
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pb-2 sm:flex-col sm:items-center sm:gap-1", children: [
|
|
2290
|
+
/* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-xs text-text-secondary", children: [
|
|
2291
|
+
/* @__PURE__ */ jsx(
|
|
2292
|
+
"input",
|
|
2293
|
+
{
|
|
2294
|
+
type: "checkbox",
|
|
2295
|
+
checked: column.required ?? false,
|
|
2296
|
+
onChange: (event) => updateColumn(index, {
|
|
2297
|
+
required: event.target.checked
|
|
2298
|
+
})
|
|
2299
|
+
}
|
|
2300
|
+
),
|
|
2301
|
+
tr("fluidgrids.dataTables.required", "Required")
|
|
2302
|
+
] }),
|
|
2303
|
+
/* @__PURE__ */ jsx(
|
|
2304
|
+
"button",
|
|
2305
|
+
{
|
|
2306
|
+
type: "button",
|
|
2307
|
+
className: "rounded p-1 text-text-secondary hover:bg-background-tertiary hover:text-text-primary disabled:opacity-40",
|
|
2308
|
+
onClick: () => setColumns(
|
|
2309
|
+
(current) => current.filter(
|
|
2310
|
+
(_, columnIndex) => columnIndex !== index
|
|
2311
|
+
)
|
|
2312
|
+
),
|
|
2313
|
+
disabled: columns.length === 1,
|
|
2314
|
+
title: tr(
|
|
2315
|
+
"fluidgrids.dataTables.removeColumn",
|
|
2316
|
+
"Remove column"
|
|
2317
|
+
),
|
|
2318
|
+
"aria-label": tr(
|
|
2319
|
+
"fluidgrids.dataTables.removeColumn",
|
|
2320
|
+
"Remove column"
|
|
2321
|
+
),
|
|
2322
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "size-4", "aria-hidden": "true" })
|
|
2323
|
+
}
|
|
2324
|
+
)
|
|
2325
|
+
] })
|
|
2326
|
+
]
|
|
2327
|
+
},
|
|
2328
|
+
`${column.key}-${index}`
|
|
2329
|
+
)) })
|
|
2330
|
+
] })
|
|
2331
|
+
] }),
|
|
2332
|
+
/* @__PURE__ */ jsxs(DialogFooter, { children: [
|
|
2333
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), children: tr("fluidgrids.dataTables.cancel", "Cancel") }),
|
|
2334
|
+
/* @__PURE__ */ jsx(Button, { onClick: handleSubmit, disabled: saving, children: saving ? tr("fluidgrids.dataTables.saving", "Saving\u2026") : tr("fluidgrids.dataTables.save", "Save table") })
|
|
2335
|
+
] })
|
|
2336
|
+
] }) });
|
|
2337
|
+
}
|
|
2338
|
+
function RowFormDialog({
|
|
2339
|
+
open,
|
|
2340
|
+
table,
|
|
2341
|
+
row,
|
|
2342
|
+
onOpenChange,
|
|
2343
|
+
onSaved
|
|
2344
|
+
}) {
|
|
2345
|
+
const tr = useTr();
|
|
2346
|
+
const { insertDataTableRow, loading: inserting } = useInsertDataTableRow();
|
|
2347
|
+
const { updateDataTableRow, loading: updating } = useUpdateDataTableRow();
|
|
2348
|
+
const [values, setValues] = useState({});
|
|
2349
|
+
const editing = row !== null;
|
|
2350
|
+
const saving = inserting || updating;
|
|
2351
|
+
useEffect(() => {
|
|
2352
|
+
if (!open || !table) return;
|
|
2353
|
+
const next = {};
|
|
2354
|
+
for (const column of table.columns) {
|
|
2355
|
+
next[column.key] = asFormValue(row?.data[column.key], column.type);
|
|
2356
|
+
}
|
|
2357
|
+
setValues(next);
|
|
2358
|
+
}, [open, row, table]);
|
|
2359
|
+
const handleSubmit = useCallback(async () => {
|
|
2360
|
+
if (!table) return;
|
|
2361
|
+
try {
|
|
2362
|
+
const data = {};
|
|
2363
|
+
for (const column of table.columns) {
|
|
2364
|
+
const value = values[column.key] ?? "";
|
|
2365
|
+
if (column.required && value.trim() === "") {
|
|
2366
|
+
throw new Error(`${column.label} is required`);
|
|
2367
|
+
}
|
|
2368
|
+
data[column.key] = parseFormValue(value, column.type);
|
|
2369
|
+
}
|
|
2370
|
+
if (editing && row) {
|
|
2371
|
+
await updateDataTableRow(row.id, data);
|
|
2372
|
+
} else {
|
|
2373
|
+
await insertDataTableRow(table.id, data);
|
|
2374
|
+
}
|
|
2375
|
+
toast.success(
|
|
2376
|
+
editing ? tr("fluidgrids.dataTables.rowUpdated", "Row updated") : tr("fluidgrids.dataTables.rowCreated", "Row added")
|
|
2377
|
+
);
|
|
2378
|
+
onSaved();
|
|
2379
|
+
onOpenChange(false);
|
|
2380
|
+
} catch (error) {
|
|
2381
|
+
toast.error(
|
|
2382
|
+
describeOperationError(
|
|
2383
|
+
error,
|
|
2384
|
+
tr("fluidgrids.dataTables.rowSaveFailed", "Failed to save row")
|
|
2385
|
+
)
|
|
2386
|
+
);
|
|
2387
|
+
}
|
|
2388
|
+
}, [
|
|
2389
|
+
editing,
|
|
2390
|
+
insertDataTableRow,
|
|
2391
|
+
onOpenChange,
|
|
2392
|
+
onSaved,
|
|
2393
|
+
row,
|
|
2394
|
+
table,
|
|
2395
|
+
tr,
|
|
2396
|
+
updateDataTableRow,
|
|
2397
|
+
values
|
|
2398
|
+
]);
|
|
2399
|
+
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-h-[90vh] max-w-2xl overflow-y-auto", children: [
|
|
2400
|
+
/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: editing ? tr("fluidgrids.dataTables.editRow", "Edit row") : tr("fluidgrids.dataTables.addRow", "Add row") }) }),
|
|
2401
|
+
table && /* @__PURE__ */ jsx("div", { className: "space-y-4 py-2", children: table.columns.map((column) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
2402
|
+
/* @__PURE__ */ jsxs(Label, { htmlFor: `data-table-row-${column.key}`, children: [
|
|
2403
|
+
column.label,
|
|
2404
|
+
/* @__PURE__ */ jsxs("span", { className: "ml-1 text-xs text-text-secondary", children: [
|
|
2405
|
+
"(",
|
|
2406
|
+
column.key,
|
|
2407
|
+
" \xB7 ",
|
|
2408
|
+
columnTypeLabel(column.type),
|
|
2409
|
+
")"
|
|
2410
|
+
] }),
|
|
2411
|
+
column.required && /* @__PURE__ */ jsx("span", { className: "ml-1 text-text-secondary", children: "*" })
|
|
2412
|
+
] }),
|
|
2413
|
+
column.type === "JSON" ? /* @__PURE__ */ jsx(
|
|
2414
|
+
Textarea,
|
|
2415
|
+
{
|
|
2416
|
+
id: `data-table-row-${column.key}`,
|
|
2417
|
+
value: values[column.key] ?? "",
|
|
2418
|
+
onChange: (event) => setValues((current) => ({
|
|
2419
|
+
...current,
|
|
2420
|
+
[column.key]: event.target.value
|
|
2421
|
+
})),
|
|
2422
|
+
rows: 4,
|
|
2423
|
+
placeholder: "{ }"
|
|
2424
|
+
}
|
|
2425
|
+
) : /* @__PURE__ */ jsx(
|
|
2426
|
+
Input,
|
|
2427
|
+
{
|
|
2428
|
+
id: `data-table-row-${column.key}`,
|
|
2429
|
+
type: column.type === "NUMBER" ? "number" : column.type === "DATE" ? "datetime-local" : "text",
|
|
2430
|
+
value: values[column.key] ?? "",
|
|
2431
|
+
onChange: (event) => setValues((current) => ({
|
|
2432
|
+
...current,
|
|
2433
|
+
[column.key]: event.target.value
|
|
2434
|
+
}))
|
|
2435
|
+
}
|
|
2436
|
+
)
|
|
2437
|
+
] }, column.key)) }),
|
|
2438
|
+
/* @__PURE__ */ jsxs(DialogFooter, { children: [
|
|
2439
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), children: tr("fluidgrids.dataTables.cancel", "Cancel") }),
|
|
2440
|
+
/* @__PURE__ */ jsx(Button, { onClick: handleSubmit, disabled: saving || !table, children: saving ? tr("fluidgrids.dataTables.saving", "Saving\u2026") : tr("fluidgrids.dataTables.saveRow", "Save row") })
|
|
2441
|
+
] })
|
|
2442
|
+
] }) });
|
|
2443
|
+
}
|
|
2444
|
+
function TableRowsPanel({ table, onTableChanged }) {
|
|
2445
|
+
const tr = useTr();
|
|
2446
|
+
const { rows, total, loading, error, refetch } = useDataTableRows(table.id);
|
|
2447
|
+
const { deleteDataTableRows, loading: deleting } = useDeleteDataTableRows();
|
|
2448
|
+
const { insertDataTableRow } = useInsertDataTableRow();
|
|
2449
|
+
const [selectedIds, setSelectedIds] = useState(/* @__PURE__ */ new Set());
|
|
2450
|
+
const [rowDialogOpen, setRowDialogOpen] = useState(false);
|
|
2451
|
+
const [editingRow, setEditingRow] = useState(null);
|
|
2452
|
+
const fileInputRef = useRef(null);
|
|
2453
|
+
useEffect(() => {
|
|
2454
|
+
setSelectedIds((current) => {
|
|
2455
|
+
const visible = new Set(rows.map((row) => row.id));
|
|
2456
|
+
return new Set([...current].filter((id) => visible.has(id)));
|
|
2457
|
+
});
|
|
2458
|
+
}, [rows]);
|
|
2459
|
+
const allVisibleSelected = rows.length > 0 && selectedIds.size === rows.length;
|
|
2460
|
+
const toggleAll = useCallback(() => {
|
|
2461
|
+
setSelectedIds(
|
|
2462
|
+
allVisibleSelected ? /* @__PURE__ */ new Set() : new Set(rows.map((row) => row.id))
|
|
2463
|
+
);
|
|
2464
|
+
}, [allVisibleSelected, rows]);
|
|
2465
|
+
const handleDeleteRows = useCallback(async () => {
|
|
2466
|
+
if (selectedIds.size === 0) return;
|
|
2467
|
+
if (!window.confirm(
|
|
2468
|
+
tr(
|
|
2469
|
+
"fluidgrids.dataTables.confirmDeleteRows",
|
|
2470
|
+
`Delete ${selectedIds.size} selected row(s)?`
|
|
2471
|
+
)
|
|
2472
|
+
)) {
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2475
|
+
try {
|
|
2476
|
+
const affected = await deleteDataTableRows(table.id, [...selectedIds]);
|
|
2477
|
+
toast.success(
|
|
2478
|
+
tr("fluidgrids.dataTables.rowsDeleted", `${affected} row(s) deleted`)
|
|
2479
|
+
);
|
|
2480
|
+
setSelectedIds(/* @__PURE__ */ new Set());
|
|
2481
|
+
await refetch();
|
|
2482
|
+
onTableChanged();
|
|
2483
|
+
} catch (error2) {
|
|
2484
|
+
toast.error(
|
|
2485
|
+
describeOperationError(
|
|
2486
|
+
error2,
|
|
2487
|
+
tr("fluidgrids.dataTables.deleteRowsFailed", "Failed to delete rows")
|
|
2488
|
+
)
|
|
2489
|
+
);
|
|
2490
|
+
}
|
|
2491
|
+
}, [deleteDataTableRows, onTableChanged, refetch, selectedIds, table.id, tr]);
|
|
2492
|
+
const handleExport = useCallback(() => {
|
|
2493
|
+
const csv = rowsToCsv(table.columns, rows);
|
|
2494
|
+
const blob = new Blob([csv], { type: "text/csv;charset=utf-8" });
|
|
2495
|
+
const url = URL.createObjectURL(blob);
|
|
2496
|
+
const anchor = document.createElement("a");
|
|
2497
|
+
anchor.href = url;
|
|
2498
|
+
anchor.download = `${table.name}.csv`;
|
|
2499
|
+
anchor.click();
|
|
2500
|
+
URL.revokeObjectURL(url);
|
|
2501
|
+
toast.success(
|
|
2502
|
+
tr("fluidgrids.dataTables.exported", "Visible rows exported to CSV")
|
|
2503
|
+
);
|
|
2504
|
+
}, [rows, table.columns, table.name, tr]);
|
|
2505
|
+
const handleImport = useCallback(
|
|
2506
|
+
async (event) => {
|
|
2507
|
+
const file = event.target.files?.[0];
|
|
2508
|
+
event.target.value = "";
|
|
2509
|
+
if (!file) return;
|
|
2510
|
+
try {
|
|
2511
|
+
const importedRows = csvToRows(await file.text(), table.columns);
|
|
2512
|
+
let inserted = 0;
|
|
2513
|
+
for (const data of importedRows) {
|
|
2514
|
+
await insertDataTableRow(table.id, data);
|
|
2515
|
+
inserted += 1;
|
|
2516
|
+
}
|
|
2517
|
+
toast.success(
|
|
2518
|
+
tr("fluidgrids.dataTables.imported", `${inserted} row(s) imported`)
|
|
2519
|
+
);
|
|
2520
|
+
await refetch();
|
|
2521
|
+
onTableChanged();
|
|
2522
|
+
} catch (error2) {
|
|
2523
|
+
toast.error(
|
|
2524
|
+
describeOperationError(
|
|
2525
|
+
error2,
|
|
2526
|
+
tr("fluidgrids.dataTables.importFailed", "Failed to import CSV")
|
|
2527
|
+
)
|
|
2528
|
+
);
|
|
2529
|
+
}
|
|
2530
|
+
},
|
|
2531
|
+
[insertDataTableRow, onTableChanged, refetch, table.columns, table.id, tr]
|
|
2532
|
+
);
|
|
2533
|
+
const openNewRow = useCallback(() => {
|
|
2534
|
+
setEditingRow(null);
|
|
2535
|
+
setRowDialogOpen(true);
|
|
2536
|
+
}, []);
|
|
2537
|
+
const openEditRow = useCallback((row) => {
|
|
2538
|
+
setEditingRow(row);
|
|
2539
|
+
setRowDialogOpen(true);
|
|
2540
|
+
}, []);
|
|
2541
|
+
return /* @__PURE__ */ jsxs(Card, { className: "min-w-0 border-border-secondary", children: [
|
|
2542
|
+
/* @__PURE__ */ jsxs(CardHeader, { className: "gap-3 border-b border-border-secondary p-4 sm:p-5", children: [
|
|
2543
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between", children: [
|
|
2544
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
2545
|
+
/* @__PURE__ */ jsxs(CardTitle, { className: "flex items-center gap-2 text-lg", children: [
|
|
2546
|
+
/* @__PURE__ */ jsx(
|
|
2547
|
+
Table2,
|
|
2548
|
+
{
|
|
2549
|
+
className: "size-5 text-text-secondary",
|
|
2550
|
+
"aria-hidden": "true"
|
|
2551
|
+
}
|
|
2552
|
+
),
|
|
2553
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: table.name })
|
|
2554
|
+
] }),
|
|
2555
|
+
/* @__PURE__ */ jsx(CardDescription, { className: "mt-1", children: table.description || tr("fluidgrids.dataTables.noDescription", "No description") })
|
|
2556
|
+
] }),
|
|
2557
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
2558
|
+
/* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: openNewRow, children: [
|
|
2559
|
+
/* @__PURE__ */ jsx(Plus, { className: "mr-1.5 size-4", "aria-hidden": "true" }),
|
|
2560
|
+
tr("fluidgrids.dataTables.addRow", "Add row")
|
|
2561
|
+
] }),
|
|
2562
|
+
/* @__PURE__ */ jsxs(
|
|
2563
|
+
Button,
|
|
2564
|
+
{
|
|
2565
|
+
variant: "outline",
|
|
2566
|
+
size: "sm",
|
|
2567
|
+
onClick: () => fileInputRef.current?.click(),
|
|
2568
|
+
children: [
|
|
2569
|
+
/* @__PURE__ */ jsx(ArrowUpFromLine, { className: "mr-1.5 size-4", "aria-hidden": "true" }),
|
|
2570
|
+
tr("fluidgrids.dataTables.import", "Import CSV")
|
|
2571
|
+
]
|
|
2572
|
+
}
|
|
2573
|
+
),
|
|
2574
|
+
/* @__PURE__ */ jsx(
|
|
2575
|
+
"input",
|
|
2576
|
+
{
|
|
2577
|
+
ref: fileInputRef,
|
|
2578
|
+
type: "file",
|
|
2579
|
+
accept: ".csv,text/csv",
|
|
2580
|
+
className: "hidden",
|
|
2581
|
+
onChange: handleImport
|
|
2582
|
+
}
|
|
2583
|
+
),
|
|
2584
|
+
/* @__PURE__ */ jsxs(
|
|
2585
|
+
Button,
|
|
2586
|
+
{
|
|
2587
|
+
variant: "outline",
|
|
2588
|
+
size: "sm",
|
|
2589
|
+
onClick: handleExport,
|
|
2590
|
+
disabled: rows.length === 0,
|
|
2591
|
+
children: [
|
|
2592
|
+
/* @__PURE__ */ jsx(ArrowDownToLine, { className: "mr-1.5 size-4", "aria-hidden": "true" }),
|
|
2593
|
+
tr("fluidgrids.dataTables.export", "Export CSV")
|
|
2594
|
+
]
|
|
2595
|
+
}
|
|
2596
|
+
)
|
|
2597
|
+
] })
|
|
2598
|
+
] }),
|
|
2599
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 text-xs text-text-secondary", children: [
|
|
2600
|
+
/* @__PURE__ */ jsxs(Badge, { variant: "outline", children: [
|
|
2601
|
+
total,
|
|
2602
|
+
" ",
|
|
2603
|
+
tr("fluidgrids.dataTables.rows", "rows")
|
|
2604
|
+
] }),
|
|
2605
|
+
/* @__PURE__ */ jsxs(Badge, { variant: "outline", children: [
|
|
2606
|
+
table.columns.length,
|
|
2607
|
+
" ",
|
|
2608
|
+
tr("fluidgrids.dataTables.columns", "columns")
|
|
2609
|
+
] }),
|
|
2610
|
+
/* @__PURE__ */ jsx(Badge, { variant: "outline", children: formatBytes(table.sizeBytes) }),
|
|
2611
|
+
selectedIds.size > 0 && /* @__PURE__ */ jsxs(
|
|
2612
|
+
Button,
|
|
2613
|
+
{
|
|
2614
|
+
variant: "outline",
|
|
2615
|
+
size: "sm",
|
|
2616
|
+
onClick: handleDeleteRows,
|
|
2617
|
+
disabled: deleting,
|
|
2618
|
+
children: [
|
|
2619
|
+
/* @__PURE__ */ jsx(Trash2, { className: "mr-1.5 size-4", "aria-hidden": "true" }),
|
|
2620
|
+
deleting ? tr("fluidgrids.dataTables.deleting", "Deleting\u2026") : tr(
|
|
2621
|
+
"fluidgrids.dataTables.deleteSelected",
|
|
2622
|
+
`Delete ${selectedIds.size} selected`
|
|
2623
|
+
)
|
|
2624
|
+
]
|
|
2625
|
+
}
|
|
2626
|
+
)
|
|
2627
|
+
] })
|
|
2628
|
+
] }),
|
|
2629
|
+
/* @__PURE__ */ jsxs(CardContent, { className: "p-0", children: [
|
|
2630
|
+
error && /* @__PURE__ */ jsx("div", { className: "m-4 rounded-md border border-border-secondary bg-background-secondary p-3 text-sm text-text-secondary", children: error.message }),
|
|
2631
|
+
/* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs(Table, { children: [
|
|
2632
|
+
/* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
|
|
2633
|
+
/* @__PURE__ */ jsx(TableHead, { className: "w-10", children: /* @__PURE__ */ jsx(
|
|
2634
|
+
"input",
|
|
2635
|
+
{
|
|
2636
|
+
type: "checkbox",
|
|
2637
|
+
checked: allVisibleSelected,
|
|
2638
|
+
onChange: toggleAll,
|
|
2639
|
+
"aria-label": tr(
|
|
2640
|
+
"fluidgrids.dataTables.selectAll",
|
|
2641
|
+
"Select all rows"
|
|
2642
|
+
)
|
|
2643
|
+
}
|
|
2644
|
+
) }),
|
|
2645
|
+
/* @__PURE__ */ jsx(TableHead, { children: tr("fluidgrids.dataTables.rowId", "Row") }),
|
|
2646
|
+
table.columns.map((column) => /* @__PURE__ */ jsx(TableHead, { children: column.label }, column.key)),
|
|
2647
|
+
/* @__PURE__ */ jsx(TableHead, { className: "text-right", children: tr("fluidgrids.dataTables.actions", "Actions") })
|
|
2648
|
+
] }) }),
|
|
2649
|
+
/* @__PURE__ */ jsxs(TableBody, { children: [
|
|
2650
|
+
loading && /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(
|
|
2651
|
+
TableCell,
|
|
2652
|
+
{
|
|
2653
|
+
colSpan: table.columns.length + 3,
|
|
2654
|
+
className: "py-10 text-center text-text-secondary",
|
|
2655
|
+
children: tr("fluidgrids.dataTables.loadingRows", "Loading rows\u2026")
|
|
2656
|
+
}
|
|
2657
|
+
) }),
|
|
2658
|
+
!loading && rows.length === 0 && /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(
|
|
2659
|
+
TableCell,
|
|
2660
|
+
{
|
|
2661
|
+
colSpan: table.columns.length + 3,
|
|
2662
|
+
className: "py-10 text-center text-text-secondary",
|
|
2663
|
+
children: tr(
|
|
2664
|
+
"fluidgrids.dataTables.emptyRows",
|
|
2665
|
+
"No rows yet. Add a row or import a CSV to get started."
|
|
2666
|
+
)
|
|
2667
|
+
}
|
|
2668
|
+
) }),
|
|
2669
|
+
!loading && rows.map((row) => /* @__PURE__ */ jsxs(TableRow, { children: [
|
|
2670
|
+
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(
|
|
2671
|
+
"input",
|
|
2672
|
+
{
|
|
2673
|
+
type: "checkbox",
|
|
2674
|
+
checked: selectedIds.has(row.id),
|
|
2675
|
+
onChange: () => setSelectedIds((current) => {
|
|
2676
|
+
const next = new Set(current);
|
|
2677
|
+
if (next.has(row.id)) next.delete(row.id);
|
|
2678
|
+
else next.add(row.id);
|
|
2679
|
+
return next;
|
|
2680
|
+
}),
|
|
2681
|
+
"aria-label": tr(
|
|
2682
|
+
"fluidgrids.dataTables.selectRow",
|
|
2683
|
+
"Select row"
|
|
2684
|
+
)
|
|
2685
|
+
}
|
|
2686
|
+
) }),
|
|
2687
|
+
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx("code", { className: "text-xs text-text-secondary", children: row.id.slice(0, 8) }) }),
|
|
2688
|
+
table.columns.map((column) => /* @__PURE__ */ jsx(
|
|
2689
|
+
TableCell,
|
|
2690
|
+
{
|
|
2691
|
+
className: "max-w-[16rem] truncate text-sm",
|
|
2692
|
+
title: formatValue(row.data[column.key]),
|
|
2693
|
+
children: formatValue(row.data[column.key])
|
|
2694
|
+
},
|
|
2695
|
+
column.key
|
|
2696
|
+
)),
|
|
2697
|
+
/* @__PURE__ */ jsx(TableCell, { className: "text-right", children: /* @__PURE__ */ jsx(
|
|
2698
|
+
Button,
|
|
2699
|
+
{
|
|
2700
|
+
variant: "ghost",
|
|
2701
|
+
size: "icon",
|
|
2702
|
+
onClick: () => openEditRow(row),
|
|
2703
|
+
title: tr("fluidgrids.dataTables.editRow", "Edit row"),
|
|
2704
|
+
"aria-label": tr(
|
|
2705
|
+
"fluidgrids.dataTables.editRow",
|
|
2706
|
+
"Edit row"
|
|
2707
|
+
),
|
|
2708
|
+
children: /* @__PURE__ */ jsx(Pencil, { className: "size-4", "aria-hidden": "true" })
|
|
2709
|
+
}
|
|
2710
|
+
) })
|
|
2711
|
+
] }, row.id))
|
|
2712
|
+
] })
|
|
2713
|
+
] }) }),
|
|
2714
|
+
total > rows.length && !loading && /* @__PURE__ */ jsx("p", { className: "border-t border-border-secondary px-4 py-3 text-xs text-text-secondary", children: tr(
|
|
2715
|
+
"fluidgrids.dataTables.rowLimitNote",
|
|
2716
|
+
"Showing the first 5,000 rows. Use workflow filters for larger tables."
|
|
2717
|
+
) })
|
|
2718
|
+
] }),
|
|
2719
|
+
/* @__PURE__ */ jsx(
|
|
2720
|
+
RowFormDialog,
|
|
2721
|
+
{
|
|
2722
|
+
open: rowDialogOpen,
|
|
2723
|
+
table,
|
|
2724
|
+
row: editingRow,
|
|
2725
|
+
onOpenChange: setRowDialogOpen,
|
|
2726
|
+
onSaved: async () => {
|
|
2727
|
+
await refetch();
|
|
2728
|
+
onTableChanged();
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
)
|
|
2732
|
+
] });
|
|
2733
|
+
}
|
|
2734
|
+
function DataTablesPage() {
|
|
2735
|
+
const tr = useTr();
|
|
2736
|
+
const navigate = useNavigate();
|
|
2737
|
+
const [searchInput, setSearchInput] = useState("");
|
|
2738
|
+
const [search, setSearch] = useState("");
|
|
2739
|
+
const { tables, total, loading, error, refetch } = useDataTables(search);
|
|
2740
|
+
const { deleteDataTable, loading: deleting } = useDeleteDataTable();
|
|
2741
|
+
const [selectedId, setSelectedId] = useState(null);
|
|
2742
|
+
const [tableDialogOpen, setTableDialogOpen] = useState(false);
|
|
2743
|
+
const [editingTable, setEditingTable] = useState(null);
|
|
2744
|
+
const selectedTable = useMemo(
|
|
2745
|
+
() => tables.find((table) => table.id === selectedId) ?? null,
|
|
2746
|
+
[selectedId, tables]
|
|
2747
|
+
);
|
|
2748
|
+
useEffect(() => {
|
|
2749
|
+
if (tables.length === 0) {
|
|
2750
|
+
setSelectedId(null);
|
|
2751
|
+
return;
|
|
2752
|
+
}
|
|
2753
|
+
if (!selectedId || !tables.some((table) => table.id === selectedId)) {
|
|
2754
|
+
setSelectedId(tables[0].id);
|
|
2755
|
+
}
|
|
2756
|
+
}, [selectedId, tables]);
|
|
2757
|
+
const refresh = useCallback(async () => {
|
|
2758
|
+
await refetch();
|
|
2759
|
+
}, [refetch]);
|
|
2760
|
+
const handleDeleteTable = useCallback(async () => {
|
|
2761
|
+
if (!selectedTable) return;
|
|
2762
|
+
if (!window.confirm(
|
|
2763
|
+
tr(
|
|
2764
|
+
"fluidgrids.dataTables.confirmDeleteTable",
|
|
2765
|
+
`Delete table "${selectedTable.name}" and all of its rows?`
|
|
2766
|
+
)
|
|
2767
|
+
)) {
|
|
2768
|
+
return;
|
|
2769
|
+
}
|
|
2770
|
+
try {
|
|
2771
|
+
await deleteDataTable(selectedTable.id);
|
|
2772
|
+
toast.success(tr("fluidgrids.dataTables.deleted", "Data table deleted"));
|
|
2773
|
+
setSelectedId(null);
|
|
2774
|
+
await refetch();
|
|
2775
|
+
} catch (error2) {
|
|
2776
|
+
toast.error(
|
|
2777
|
+
describeOperationError(
|
|
2778
|
+
error2,
|
|
2779
|
+
tr(
|
|
2780
|
+
"fluidgrids.dataTables.deleteFailed",
|
|
2781
|
+
"Failed to delete data table"
|
|
2782
|
+
)
|
|
2783
|
+
)
|
|
2784
|
+
);
|
|
2785
|
+
}
|
|
2786
|
+
}, [deleteDataTable, refetch, selectedTable, tr]);
|
|
2787
|
+
const handleTableSaved = useCallback(
|
|
2788
|
+
(table) => {
|
|
2789
|
+
if (table) setSelectedId(table.id);
|
|
2790
|
+
void refetch();
|
|
2791
|
+
},
|
|
2792
|
+
[refetch]
|
|
2793
|
+
);
|
|
2794
|
+
const submitSearch = useCallback(
|
|
2795
|
+
(event) => {
|
|
2796
|
+
event.preventDefault();
|
|
2797
|
+
setSearch(searchInput);
|
|
2798
|
+
},
|
|
2799
|
+
[searchInput]
|
|
2800
|
+
);
|
|
2801
|
+
return /* @__PURE__ */ jsxs("div", { className: "min-h-screen bg-background-primary p-4 sm:p-6", children: [
|
|
2802
|
+
/* @__PURE__ */ jsxs("div", { className: "mx-auto flex max-w-[1500px] flex-col gap-5", children: [
|
|
2803
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between", children: [
|
|
2804
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2805
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center gap-2 text-sm text-text-secondary", children: [
|
|
2806
|
+
/* @__PURE__ */ jsx(Database, { className: "size-4", "aria-hidden": "true" }),
|
|
2807
|
+
tr("fluidgrids.dataTables.workflowData", "Workflow data")
|
|
2808
|
+
] }),
|
|
2809
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold text-text-primary sm:text-3xl", children: tr("fluidgrids.dataTables.pageTitle", "Data Tables") }),
|
|
2810
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 max-w-2xl text-text-secondary", children: tr(
|
|
2811
|
+
"fluidgrids.dataTables.pageSubtitle",
|
|
2812
|
+
"Store structured workspace data that your workflows can read and write across runs."
|
|
2813
|
+
) })
|
|
2814
|
+
] }),
|
|
2815
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
|
|
2816
|
+
/* @__PURE__ */ jsxs(Button, { variant: "outline", onClick: () => navigate("../list"), children: [
|
|
2817
|
+
/* @__PURE__ */ jsx(ArrowLeft, { className: "mr-2 size-4", "aria-hidden": "true" }),
|
|
2818
|
+
tr("fluidgrids.dataTables.backToWorkflows", "Back to Workflows")
|
|
2819
|
+
] }),
|
|
2820
|
+
/* @__PURE__ */ jsxs(
|
|
2821
|
+
Button,
|
|
2822
|
+
{
|
|
2823
|
+
onClick: () => {
|
|
2824
|
+
setEditingTable(null);
|
|
2825
|
+
setTableDialogOpen(true);
|
|
2826
|
+
},
|
|
2827
|
+
children: [
|
|
2828
|
+
/* @__PURE__ */ jsx(Plus, { className: "mr-2 size-4", "aria-hidden": "true" }),
|
|
2829
|
+
tr("fluidgrids.dataTables.newTable", "New data table")
|
|
2830
|
+
]
|
|
2831
|
+
}
|
|
2832
|
+
)
|
|
2833
|
+
] })
|
|
2834
|
+
] }),
|
|
2835
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-5 lg:grid-cols-[minmax(16rem,22rem)_minmax(0,1fr)]", children: [
|
|
2836
|
+
/* @__PURE__ */ jsxs(Card, { className: "h-fit border-border-secondary", children: [
|
|
2837
|
+
/* @__PURE__ */ jsxs(CardHeader, { className: "gap-3 border-b border-border-secondary p-4", children: [
|
|
2838
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
2839
|
+
/* @__PURE__ */ jsx(CardTitle, { className: "text-base", children: tr("fluidgrids.dataTables.tables", "Tables") }),
|
|
2840
|
+
/* @__PURE__ */ jsx(Badge, { variant: "outline", children: total })
|
|
2841
|
+
] }),
|
|
2842
|
+
/* @__PURE__ */ jsxs("form", { className: "flex gap-2", onSubmit: submitSearch, children: [
|
|
2843
|
+
/* @__PURE__ */ jsxs("div", { className: "relative min-w-0 flex-1", children: [
|
|
2844
|
+
/* @__PURE__ */ jsx(
|
|
2845
|
+
Search,
|
|
2846
|
+
{
|
|
2847
|
+
className: "pointer-events-none absolute left-2.5 top-2.5 size-4 text-text-secondary",
|
|
2848
|
+
"aria-hidden": "true"
|
|
2849
|
+
}
|
|
2850
|
+
),
|
|
2851
|
+
/* @__PURE__ */ jsx(
|
|
2852
|
+
Input,
|
|
2853
|
+
{
|
|
2854
|
+
className: "pl-8",
|
|
2855
|
+
value: searchInput,
|
|
2856
|
+
onChange: (event) => setSearchInput(event.target.value),
|
|
2857
|
+
placeholder: tr(
|
|
2858
|
+
"fluidgrids.dataTables.search",
|
|
2859
|
+
"Search tables"
|
|
2860
|
+
),
|
|
2861
|
+
"aria-label": tr(
|
|
2862
|
+
"fluidgrids.dataTables.search",
|
|
2863
|
+
"Search tables"
|
|
2864
|
+
)
|
|
2865
|
+
}
|
|
2866
|
+
)
|
|
2867
|
+
] }),
|
|
2868
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", variant: "outline", size: "sm", children: tr("fluidgrids.dataTables.searchAction", "Search") })
|
|
2869
|
+
] })
|
|
2870
|
+
] }),
|
|
2871
|
+
/* @__PURE__ */ jsxs(CardContent, { className: "p-2", children: [
|
|
2872
|
+
loading && /* @__PURE__ */ jsx("p", { className: "p-4 text-sm text-text-secondary", children: tr("fluidgrids.dataTables.loading", "Loading tables\u2026") }),
|
|
2873
|
+
!loading && tables.length === 0 && /* @__PURE__ */ jsxs("div", { className: "p-4 text-sm text-text-secondary", children: [
|
|
2874
|
+
/* @__PURE__ */ jsx("p", { children: tr("fluidgrids.dataTables.empty", "No data tables yet") }),
|
|
2875
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs", children: tr(
|
|
2876
|
+
"fluidgrids.dataTables.emptyHint",
|
|
2877
|
+
"Create a table to give your workflows a shared data store."
|
|
2878
|
+
) })
|
|
2879
|
+
] }),
|
|
2880
|
+
!loading && tables.map((table) => /* @__PURE__ */ jsxs(
|
|
2881
|
+
"button",
|
|
2882
|
+
{
|
|
2883
|
+
type: "button",
|
|
2884
|
+
onClick: () => setSelectedId(table.id),
|
|
2885
|
+
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"}`,
|
|
2886
|
+
children: [
|
|
2887
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
|
|
2888
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-medium", children: table.name }),
|
|
2889
|
+
/* @__PURE__ */ jsxs("span", { className: "mt-1 block text-xs text-text-secondary", children: [
|
|
2890
|
+
table.rowCount,
|
|
2891
|
+
" ",
|
|
2892
|
+
tr("fluidgrids.dataTables.rows", "rows"),
|
|
2893
|
+
" \xB7",
|
|
2894
|
+
" ",
|
|
2895
|
+
table.columns.length,
|
|
2896
|
+
" ",
|
|
2897
|
+
tr("fluidgrids.dataTables.columns", "columns")
|
|
2898
|
+
] })
|
|
2899
|
+
] }),
|
|
2900
|
+
/* @__PURE__ */ jsx(
|
|
2901
|
+
Table2,
|
|
2902
|
+
{
|
|
2903
|
+
className: "mt-0.5 size-4 shrink-0",
|
|
2904
|
+
"aria-hidden": "true"
|
|
2905
|
+
}
|
|
2906
|
+
)
|
|
2907
|
+
]
|
|
2908
|
+
},
|
|
2909
|
+
table.id
|
|
2910
|
+
))
|
|
2911
|
+
] })
|
|
2912
|
+
] }),
|
|
2913
|
+
error && !selectedTable && /* @__PURE__ */ jsx(Card, { className: "border-border-secondary", children: /* @__PURE__ */ jsxs(CardContent, { className: "p-6 text-sm text-text-secondary", children: [
|
|
2914
|
+
/* @__PURE__ */ jsx("p", { children: error.message }),
|
|
2915
|
+
/* @__PURE__ */ jsxs(
|
|
2916
|
+
Button,
|
|
2917
|
+
{
|
|
2918
|
+
className: "mt-4",
|
|
2919
|
+
variant: "outline",
|
|
2920
|
+
onClick: () => void refresh(),
|
|
2921
|
+
children: [
|
|
2922
|
+
/* @__PURE__ */ jsx(RefreshCw, { className: "mr-2 size-4", "aria-hidden": "true" }),
|
|
2923
|
+
tr("fluidgrids.dataTables.retry", "Try again")
|
|
2924
|
+
]
|
|
2925
|
+
}
|
|
2926
|
+
)
|
|
2927
|
+
] }) }),
|
|
2928
|
+
!error && !selectedTable && !loading && /* @__PURE__ */ jsx(Card, { className: "border-border-secondary", children: /* @__PURE__ */ jsxs(CardContent, { className: "flex min-h-[22rem] flex-col items-center justify-center p-6 text-center", children: [
|
|
2929
|
+
/* @__PURE__ */ jsx(
|
|
2930
|
+
Database,
|
|
2931
|
+
{
|
|
2932
|
+
className: "mb-3 size-10 text-text-secondary",
|
|
2933
|
+
"aria-hidden": "true"
|
|
2934
|
+
}
|
|
2935
|
+
),
|
|
2936
|
+
/* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-text-primary", children: tr(
|
|
2937
|
+
"fluidgrids.dataTables.selectTitle",
|
|
2938
|
+
"Select a data table"
|
|
2939
|
+
) }),
|
|
2940
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 max-w-sm text-sm text-text-secondary", children: tr(
|
|
2941
|
+
"fluidgrids.dataTables.selectDescription",
|
|
2942
|
+
"Choose a table to view and edit its rows, or create your first table."
|
|
2943
|
+
) })
|
|
2944
|
+
] }) }),
|
|
2945
|
+
selectedTable && /* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-3", children: [
|
|
2946
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap justify-end gap-2", children: [
|
|
2947
|
+
/* @__PURE__ */ jsxs(
|
|
2948
|
+
Button,
|
|
2949
|
+
{
|
|
2950
|
+
variant: "outline",
|
|
2951
|
+
size: "sm",
|
|
2952
|
+
onClick: () => {
|
|
2953
|
+
setEditingTable(selectedTable);
|
|
2954
|
+
setTableDialogOpen(true);
|
|
2955
|
+
},
|
|
2956
|
+
children: [
|
|
2957
|
+
/* @__PURE__ */ jsx(Pencil, { className: "mr-1.5 size-4", "aria-hidden": "true" }),
|
|
2958
|
+
tr("fluidgrids.dataTables.editTable", "Edit table")
|
|
2959
|
+
]
|
|
2960
|
+
}
|
|
2961
|
+
),
|
|
2962
|
+
/* @__PURE__ */ jsxs(
|
|
2963
|
+
Button,
|
|
2964
|
+
{
|
|
2965
|
+
variant: "outline",
|
|
2966
|
+
size: "sm",
|
|
2967
|
+
onClick: handleDeleteTable,
|
|
2968
|
+
disabled: deleting,
|
|
2969
|
+
children: [
|
|
2970
|
+
/* @__PURE__ */ jsx(Trash2, { className: "mr-1.5 size-4", "aria-hidden": "true" }),
|
|
2971
|
+
deleting ? tr("fluidgrids.dataTables.deleting", "Deleting\u2026") : tr("fluidgrids.dataTables.deleteTable", "Delete table")
|
|
2972
|
+
]
|
|
2973
|
+
}
|
|
2974
|
+
)
|
|
2975
|
+
] }),
|
|
2976
|
+
/* @__PURE__ */ jsx(TableRowsPanel, { table: selectedTable, onTableChanged: refresh })
|
|
2977
|
+
] })
|
|
2978
|
+
] })
|
|
2979
|
+
] }),
|
|
2980
|
+
/* @__PURE__ */ jsx(
|
|
2981
|
+
TableFormDialog,
|
|
2982
|
+
{
|
|
2983
|
+
open: tableDialogOpen,
|
|
2984
|
+
table: editingTable,
|
|
2985
|
+
onOpenChange: setTableDialogOpen,
|
|
2986
|
+
onSaved: handleTableSaved
|
|
2987
|
+
}
|
|
2988
|
+
)
|
|
2989
|
+
] });
|
|
2990
|
+
}
|
|
1584
2991
|
|
|
1585
2992
|
// src/graphql/client-helpers.ts
|
|
1586
2993
|
function createFluidGridsClients(config2) {
|
|
@@ -1644,7 +3051,7 @@ var NODE_STATUS_CONFIG = {
|
|
|
1644
3051
|
error: { label: "Error", color: "text-red-600", bgColor: "bg-red-100" },
|
|
1645
3052
|
skipped: { label: "Skipped", color: "text-gray-400", bgColor: "bg-gray-50" }
|
|
1646
3053
|
};
|
|
1647
|
-
var
|
|
3054
|
+
var Textarea3 = React4.forwardRef(
|
|
1648
3055
|
({ className, ...props }, ref) => {
|
|
1649
3056
|
return /* @__PURE__ */ jsx(
|
|
1650
3057
|
"textarea",
|
|
@@ -1659,7 +3066,7 @@ var Textarea2 = React4.forwardRef(
|
|
|
1659
3066
|
);
|
|
1660
3067
|
}
|
|
1661
3068
|
);
|
|
1662
|
-
|
|
3069
|
+
Textarea3.displayName = "Textarea";
|
|
1663
3070
|
function Tabs2({
|
|
1664
3071
|
className,
|
|
1665
3072
|
...props
|
|
@@ -2080,6 +3487,6 @@ var PopoverContent = React4.forwardRef(({ className, align = "center", sideOffse
|
|
|
2080
3487
|
) }));
|
|
2081
3488
|
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
|
2082
3489
|
|
|
2083
|
-
export { ApprovalInboxPage, Checkbox, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, FluidGridsRoot, NODE_STATUS_CONFIG, Popover, PopoverContent, PopoverTrigger, RUN_STATUS_CONFIG, ScrollArea, ScrollBar, Select2 as Select, SelectContent2 as SelectContent, SelectGroup, SelectItem2 as SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger2 as SelectTrigger, SelectValue2 as SelectValue, Separator, Switch2 as Switch, Tabs2 as Tabs, TabsContent2 as TabsContent, TabsList2 as TabsList, TabsTrigger2 as TabsTrigger,
|
|
3490
|
+
export { ApprovalInboxPage, Checkbox, DataTablesPage, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, FluidGridsRoot, NODE_STATUS_CONFIG, Popover, PopoverContent, PopoverTrigger, RUN_STATUS_CONFIG, ScrollArea, ScrollBar, Select2 as Select, SelectContent2 as SelectContent, SelectGroup, SelectItem2 as SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger2 as SelectTrigger, SelectValue2 as SelectValue, Separator, Switch2 as Switch, Tabs2 as Tabs, TabsContent2 as TabsContent, TabsList2 as TabsList, TabsTrigger2 as TabsTrigger, Textarea3 as Textarea, VariablesPage, WorkflowEmbedPage, createFluidGridsClients, csvToRows, parseCsv, rowsToCsv, useCreateDataTable, useCreateVariable, useDataTableRows, useDataTables, useDecideToolApproval, useDeleteDataTable, useDeleteDataTableRows, useDeleteVariable, useInsertDataTableRow, usePendingToolApprovals, useRevealVariableValue, useSaveSecretsProviderConfig, useSecretsProviderConfigs, useTestSecretsProviderConnection, useUpdateDataTable, useUpdateDataTableRow, useUpdateVariable, useVariables };
|
|
2084
3491
|
//# sourceMappingURL=index.js.map
|
|
2085
3492
|
//# sourceMappingURL=index.js.map
|