@cryptlex/web-components 5.2.0 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/data-table/data-table-filter.d.ts +27 -0
- package/dist/components/data-table/data-table-filter.js +112 -0
- package/dist/components/data-table/data-table.d.ts +73 -0
- package/dist/components/data-table/data-table.js +265 -0
- package/dist/components/data-table/table-commons.d.ts +56 -0
- package/dist/components/data-table/table-commons.js +137 -0
- package/dist/components/inputs/checkbox.d.ts +8 -0
- package/dist/components/inputs/checkbox.js +25 -0
- package/dist/components/inputs/date-picker.d.ts +11 -0
- package/dist/components/inputs/date-picker.js +22 -0
- package/dist/components/inputs/datefield.d.ts +14 -0
- package/dist/components/inputs/datefield.js +25 -0
- package/dist/components/inputs/field.d.ts +21 -0
- package/dist/components/inputs/field.js +48 -0
- package/dist/components/inputs/id-search.d.ts +20 -0
- package/dist/components/inputs/id-search.js +40 -0
- package/dist/components/inputs/input-otp.d.ts +8 -0
- package/dist/components/inputs/input-otp.js +19 -0
- package/dist/components/inputs/multi-select.d.ts +17 -0
- package/dist/components/inputs/multi-select.js +18 -0
- package/dist/components/inputs/numberfield.d.ts +7 -0
- package/dist/components/inputs/numberfield.js +25 -0
- package/dist/components/inputs/searchfield.d.ts +5 -0
- package/dist/components/inputs/searchfield.js +24 -0
- package/dist/components/inputs/select-options.d.ts +8 -0
- package/dist/components/inputs/select-options.js +286 -0
- package/dist/components/inputs/select.d.ts +17 -0
- package/dist/components/inputs/select.js +34 -0
- package/dist/components/inputs/textfield.d.ts +7 -0
- package/dist/components/inputs/textfield.js +28 -0
- package/dist/components/key-value-card/key-value-card.d.ts +17 -0
- package/dist/components/key-value-card/key-value-card.js +40 -0
- package/dist/components/ui/alert.d.ts +8 -0
- package/dist/components/ui/alert.js +18 -0
- package/dist/components/ui/avatar.d.ts +8 -0
- package/dist/components/ui/avatar.js +5 -0
- package/dist/components/ui/badge.d.ts +2 -0
- package/dist/components/ui/badge.js +5 -0
- package/dist/components/ui/breadcrumbs.d.ts +10 -0
- package/dist/components/ui/breadcrumbs.js +28 -0
- package/dist/components/ui/button.d.ts +11 -0
- package/dist/components/ui/button.js +34 -0
- package/dist/components/ui/calendar.d.ts +17 -0
- package/dist/components/ui/calendar.js +63 -0
- package/dist/components/ui/card.d.ts +7 -0
- package/dist/components/ui/card.js +20 -0
- package/dist/components/ui/dialog.d.ts +19 -0
- package/dist/components/ui/dialog.js +42 -0
- package/dist/components/ui/disclosure.d.ts +19 -0
- package/dist/components/ui/disclosure.js +20 -0
- package/dist/components/ui/list-box.d.ts +5 -0
- package/dist/components/ui/list-box.js +24 -0
- package/dist/components/ui/loader.d.ts +5 -0
- package/dist/components/ui/loader.js +6 -0
- package/dist/components/ui/menu.d.ts +25 -0
- package/dist/components/ui/menu.js +38 -0
- package/dist/components/ui/popover.d.ts +4 -0
- package/dist/components/ui/popover.js +14 -0
- package/dist/components/ui/sidebar.d.ts +53 -0
- package/dist/components/ui/sidebar.js +177 -0
- package/dist/components/ui/skeleton.d.ts +1 -0
- package/dist/components/ui/skeleton.js +5 -0
- package/dist/components/ui/sonner.d.ts +4 -0
- package/dist/components/ui/sonner.js +14 -0
- package/dist/components/ui/table.d.ts +9 -0
- package/dist/components/ui/table.js +26 -0
- package/dist/components/ui/tabs.d.ts +5 -0
- package/dist/components/ui/tabs.js +25 -0
- package/dist/components/ui/timeline.d.ts +15 -0
- package/dist/components/ui/timeline.js +31 -0
- package/dist/components/ui/tooltip.d.ts +4 -0
- package/dist/components/ui/tooltip.js +12 -0
- package/dist/utils/form-context.d.ts +4 -0
- package/{lib/utils/form-context.tsx → dist/utils/form-context.js} +1 -3
- package/dist/utils/form-hook.d.ts +25 -0
- package/{lib/utils/form-hook.tsx → dist/utils/form-hook.js} +15 -18
- package/dist/utils/primitives.d.ts +30 -0
- package/{lib/utils/primitives.ts → dist/utils/primitives.js} +8 -37
- package/dist/utils/resource-names.d.ts +23 -0
- package/{lib/utils/resource-names.tsx → dist/utils/resource-names.js} +42 -75
- package/dist/utils/use-mobile.d.ts +1 -0
- package/dist/utils/use-mobile.js +15 -0
- package/package.json +10 -5
- package/lib/components/data-table/data-table-filter.tsx +0 -220
- package/lib/components/data-table/data-table.tsx +0 -593
- package/lib/components/data-table/table-commons.tsx +0 -233
- package/lib/components/inputs/checkbox.tsx +0 -72
- package/lib/components/inputs/date-picker.tsx +0 -130
- package/lib/components/inputs/datefield.tsx +0 -109
- package/lib/components/inputs/field.tsx +0 -106
- package/lib/components/inputs/id-search.tsx +0 -83
- package/lib/components/inputs/input-otp.tsx +0 -63
- package/lib/components/inputs/multi-select.tsx +0 -62
- package/lib/components/inputs/numberfield.tsx +0 -110
- package/lib/components/inputs/searchfield.tsx +0 -87
- package/lib/components/inputs/select-options.tsx +0 -303
- package/lib/components/inputs/select.tsx +0 -140
- package/lib/components/inputs/textfield.tsx +0 -96
- package/lib/components/key-value-card/key-value-card.tsx +0 -115
- package/lib/components/ui/alert.tsx +0 -32
- package/lib/components/ui/avatar.tsx +0 -22
- package/lib/components/ui/badge.tsx +0 -19
- package/lib/components/ui/breadcrumbs.tsx +0 -104
- package/lib/components/ui/button.tsx +0 -66
- package/lib/components/ui/calendar.tsx +0 -220
- package/lib/components/ui/card.tsx +0 -58
- package/lib/components/ui/dialog.tsx +0 -172
- package/lib/components/ui/disclosure.tsx +0 -113
- package/lib/components/ui/list-box.tsx +0 -86
- package/lib/components/ui/loader.tsx +0 -10
- package/lib/components/ui/menu.tsx +0 -168
- package/lib/components/ui/popover.tsx +0 -37
- package/lib/components/ui/sidebar.tsx +0 -552
- package/lib/components/ui/skeleton.tsx +0 -7
- package/lib/components/ui/sonner.tsx +0 -26
- package/lib/components/ui/table.tsx +0 -79
- package/lib/components/ui/tabs.tsx +0 -82
- package/lib/components/ui/timeline.tsx +0 -52
- package/lib/components/ui/tooltip.tsx +0 -30
- package/lib/tokens.scss +0 -89
- package/lib/utils/use-mobile.tsx +0 -21
|
@@ -1,593 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import type { components, operations } from "@cryptlex/web-api-types";
|
|
3
|
-
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
|
4
|
-
import {
|
|
5
|
-
type AccessorFnColumnDef,
|
|
6
|
-
type Column,
|
|
7
|
-
type ColumnDef,
|
|
8
|
-
type ColumnOrderState,
|
|
9
|
-
createColumnHelper,
|
|
10
|
-
getCoreRowModel,
|
|
11
|
-
type TableState,
|
|
12
|
-
type Updater,
|
|
13
|
-
useReactTable,
|
|
14
|
-
type VisibilityState
|
|
15
|
-
} from "@tanstack/react-table";
|
|
16
|
-
import { ArrowDownNarrowWide, ArrowDownWideNarrow, ArrowUpDown, Columns3, GripVertical, Info } from "lucide-react";
|
|
17
|
-
import React, { createContext, useContext, useEffect, useId, useMemo, useState } from "react";
|
|
18
|
-
|
|
19
|
-
/** Reserved name for actions column */
|
|
20
|
-
export const ACTIONS_COLUMN_ID = "tableActions";
|
|
21
|
-
|
|
22
|
-
export type Schemas = ApiSchema<keyof components['schemas']>;
|
|
23
|
-
export type OperationKeys = keyof operations;
|
|
24
|
-
|
|
25
|
-
type DataTableFactory<TData extends Schemas> = {
|
|
26
|
-
fetchFn: TableFetchFn<TData, OperationKeys>;
|
|
27
|
-
columns: ColumnDef<TData, any>[]
|
|
28
|
-
allowSelection?: boolean;
|
|
29
|
-
columnsToHideByDefault?: VisibilityState; // Columns that are hidden by default
|
|
30
|
-
filterConfig: FiltersConfig<OperationKeys>
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
type DataTableState = Pick<TableState, 'sorting' | 'rowSelection' | 'pagination'> & {};
|
|
35
|
-
/**
|
|
36
|
-
* Hook for handling all data-table state. Used in DataTableContext
|
|
37
|
-
*/
|
|
38
|
-
export function useDataTableState<TData extends Schemas>({ columns, fetchFn, columnsToHideByDefault = {}, allowSelection = false, filterConfig }: DataTableFactory<TData>) {
|
|
39
|
-
const id = useId();
|
|
40
|
-
|
|
41
|
-
// TODO: Would it be better for this state to be more granular?
|
|
42
|
-
const [tableState, _setTableState] = useState<DataTableState>({
|
|
43
|
-
/** TODO Reflect in URL */
|
|
44
|
-
pagination: { pageIndex: 0, pageSize: 20 }, // Pagination state
|
|
45
|
-
sorting: [], // Sorting state
|
|
46
|
-
|
|
47
|
-
/** Ephemeral */
|
|
48
|
-
rowSelection: {}, // Row selection state
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
/** TODO Reflect in URL */
|
|
52
|
-
const [searchQuery, setSearchQuery] = useState('');
|
|
53
|
-
|
|
54
|
-
/** TODO Store on browser as preference */
|
|
55
|
-
const [columnOrder, setColumnOrder] = useState<ColumnOrderState>([])
|
|
56
|
-
// TODO Store on browser
|
|
57
|
-
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({
|
|
58
|
-
id: false,
|
|
59
|
-
updatedAt: false,
|
|
60
|
-
...columnsToHideByDefault,
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const [filters, setFilters] = useState<ApiFilters<OperationKeys>[]>([]);
|
|
64
|
-
|
|
65
|
-
const mergedFilters = useMemo(() => {
|
|
66
|
-
return filters.reduce((acc, current) => {
|
|
67
|
-
return merge(acc, current);
|
|
68
|
-
}, {});
|
|
69
|
-
}, [filters])
|
|
70
|
-
|
|
71
|
-
// Update table state with new values
|
|
72
|
-
const updateTableState = (updates: Partial<DataTableState>) => {
|
|
73
|
-
_setTableState((prev) => ({ ...prev, ...updates }));
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const {
|
|
77
|
-
sorting,
|
|
78
|
-
rowSelection,
|
|
79
|
-
pagination,
|
|
80
|
-
} = tableState;
|
|
81
|
-
|
|
82
|
-
const query = useQuery({
|
|
83
|
-
queryKey: [id, pagination, sorting, searchQuery],
|
|
84
|
-
queryFn: () => fetchFn(pagination, sorting, searchQuery, mergedFilters),
|
|
85
|
-
placeholderData: keepPreviousData, // Keep previous data while loading new data
|
|
86
|
-
retry: 0,
|
|
87
|
-
refetchOnWindowFocus: false,
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
// TODO, store in localStorage
|
|
92
|
-
}, [columnVisibility])
|
|
93
|
-
|
|
94
|
-
const columnHelper = useMemo(() => createColumnHelper<TData>(), []);
|
|
95
|
-
|
|
96
|
-
const metadataColumns = useMemo<AccessorFnColumnDef<TData, string>[]>(() => {
|
|
97
|
-
const data = query.data?.data;
|
|
98
|
-
if (!data?.length) return [];
|
|
99
|
-
|
|
100
|
-
type WithMeta = TData & { metadata: ApiSchema<'MetadataDto'>[] };
|
|
101
|
-
const rowHasMetadata = (row: TData): row is WithMeta => row != null &&
|
|
102
|
-
typeof row === 'object' &&
|
|
103
|
-
'metadata' in row &&
|
|
104
|
-
Array.isArray((row).metadata);
|
|
105
|
-
|
|
106
|
-
const rowsWithMeta = data.filter(
|
|
107
|
-
rowHasMetadata
|
|
108
|
-
);
|
|
109
|
-
if (rowsWithMeta.length === 0) return [];
|
|
110
|
-
const keys = Array.from(new Set(rowsWithMeta.flatMap(r => r.metadata?.map(m => m.key) ?? [])));
|
|
111
|
-
|
|
112
|
-
return keys.map(key =>
|
|
113
|
-
columnHelper.accessor(
|
|
114
|
-
(row) => {
|
|
115
|
-
if (rowHasMetadata(row)) {
|
|
116
|
-
return row?.metadata?.find(m => m.key === key)?.value ?? '';
|
|
117
|
-
}
|
|
118
|
-
return '';
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
id: key,
|
|
122
|
-
header: key, // tooltip header
|
|
123
|
-
enableSorting: false,
|
|
124
|
-
cell: (info) => {
|
|
125
|
-
const value = info.getValue();
|
|
126
|
-
// Handle null/undefined values
|
|
127
|
-
if (value === null || value === undefined) return "";
|
|
128
|
-
// For primitive types, return the string representation
|
|
129
|
-
return String(value);
|
|
130
|
-
},
|
|
131
|
-
}
|
|
132
|
-
));
|
|
133
|
-
}, [query.data?.data]);
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* ID,createdAt and updatedAt will be added by default for all tables
|
|
137
|
-
* If selection is allowed, checkbox will be added
|
|
138
|
-
* If the dto has metadata, dynamics columns for all the metadata key-value will be added(particular for a view)
|
|
139
|
-
* If there are actions for the table, they will be placed fixed at the right side of table.
|
|
140
|
-
*/
|
|
141
|
-
const cols: ColumnDef<any, any>[] = [
|
|
142
|
-
...(allowSelection ? TABLE_CHECK_BOX_COLUMN : []),
|
|
143
|
-
...TABLE_ID_COLUMN,
|
|
144
|
-
...columns.filter((col) => col.id !== ACTIONS_COLUMN_ID),
|
|
145
|
-
...(metadataColumns.length ? metadataColumns : []),
|
|
146
|
-
...TABLE_DEFAULT_DATE_COLUMNS,
|
|
147
|
-
// Actions column
|
|
148
|
-
...columns.filter((col) => col.id === ACTIONS_COLUMN_ID),
|
|
149
|
-
];
|
|
150
|
-
|
|
151
|
-
// Type-guard for updater
|
|
152
|
-
function isUpdaterFunction<T>(updater: Updater<T>): updater is (old: T) => T {
|
|
153
|
-
return typeof updater === "function";
|
|
154
|
-
}
|
|
155
|
-
// Utility function to resolve updater
|
|
156
|
-
function resolveUpdater<T>(updater: Updater<T>, currentValue: T) {
|
|
157
|
-
if (isUpdaterFunction(updater)) {
|
|
158
|
-
return updater(currentValue);
|
|
159
|
-
}
|
|
160
|
-
return updater;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Use react-table's hook to create the table instance
|
|
164
|
-
const tanTable = useReactTable({
|
|
165
|
-
data: query.data?.data ?? [],
|
|
166
|
-
columns: cols,
|
|
167
|
-
getCoreRowModel: getCoreRowModel(),
|
|
168
|
-
rowCount: query.data?.total,
|
|
169
|
-
manualPagination: true, // Handle pagination manually since pagination is done server side for data tables
|
|
170
|
-
onPaginationChange: (updater) => {
|
|
171
|
-
updateTableState({ pagination: resolveUpdater(updater, pagination) });
|
|
172
|
-
},
|
|
173
|
-
manualSorting: true, // Handle sorting manually since sorting is done server side for data tables
|
|
174
|
-
onSortingChange: (updater) => {
|
|
175
|
-
updateTableState({ sorting: [...resolveUpdater(updater, sorting)], rowSelection: {} }); // Reset selection when sorting.
|
|
176
|
-
},
|
|
177
|
-
manualFiltering: true, // Handle filtering manually since filtering is done server side for data tables
|
|
178
|
-
onColumnVisibilityChange: (updater) => {
|
|
179
|
-
setColumnVisibility(resolveUpdater(updater, columnVisibility));
|
|
180
|
-
},
|
|
181
|
-
onRowSelectionChange: (updater) => {
|
|
182
|
-
updateTableState({ rowSelection: resolveUpdater(updater, rowSelection) });
|
|
183
|
-
},
|
|
184
|
-
onColumnOrderChange: (updater) => {
|
|
185
|
-
setColumnOrder(resolveUpdater(updater, columnOrder));
|
|
186
|
-
},
|
|
187
|
-
state: {
|
|
188
|
-
sorting: sorting,
|
|
189
|
-
columnVisibility: columnVisibility,
|
|
190
|
-
pagination: pagination,
|
|
191
|
-
rowSelection: rowSelection,
|
|
192
|
-
columnOrder: columnOrder
|
|
193
|
-
},
|
|
194
|
-
meta: {
|
|
195
|
-
refetch: query.refetch,
|
|
196
|
-
},
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
// By default, ColumnDef does not give guarantees of column.id existing. Once useReactTable is called, all columns are assigned IDs.
|
|
200
|
-
// This populates the columnIds in the columnOrder state
|
|
201
|
-
// TODO, add localStorage access layer for this.
|
|
202
|
-
useEffect(() => {
|
|
203
|
-
setColumnOrder([...tanTable.getAllLeafColumns().map(c => c.id)]);
|
|
204
|
-
}, [])
|
|
205
|
-
|
|
206
|
-
return { tableState, updateTableState, query, setSearchQuery, searchQuery, tanTable, mergedFilters, filters, setFilters, filterConfig }
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export const DataTableContext = createContext<ReturnType<typeof useDataTableState> | null>(null);
|
|
210
|
-
|
|
211
|
-
export const useDataTable = () => {
|
|
212
|
-
const ctx = useContext(DataTableContext);
|
|
213
|
-
if (!ctx) {
|
|
214
|
-
throw Error("DataTable should be used within DataTableProvider.")
|
|
215
|
-
}
|
|
216
|
-
return ctx;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
export function DataTableProvider({ children, ...props }: { children: React.ReactNode; } & ReturnType<typeof useDataTableState>) {
|
|
220
|
-
return (
|
|
221
|
-
<DataTableContext.Provider value={props}>
|
|
222
|
-
{children}
|
|
223
|
-
</DataTableContext.Provider>
|
|
224
|
-
)
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
export type DataTableProps = React.ComponentProps<'section'> & {
|
|
229
|
-
tableActions: TableActions;
|
|
230
|
-
|
|
231
|
-
// filterConfig: {
|
|
232
|
-
// filters: Record<keyof ApiFilters<TOperation>, 'date' | 'string' | 'known-string' | 'number'>,
|
|
233
|
-
// }
|
|
234
|
-
}
|
|
235
|
-
export function DataTable({
|
|
236
|
-
tableActions,
|
|
237
|
-
className,
|
|
238
|
-
...props
|
|
239
|
-
// filterableFields,
|
|
240
|
-
}: DataTableProps) {
|
|
241
|
-
// State for managing table data and filters
|
|
242
|
-
const { query, tanTable } = useDataTable();
|
|
243
|
-
|
|
244
|
-
return (
|
|
245
|
-
<>
|
|
246
|
-
{/* Table Actions Section */}
|
|
247
|
-
<section {...props} className={cn("flex flex-col bg-card", className)}>
|
|
248
|
-
<Actions tableActions={tableActions} />
|
|
249
|
-
{/* The div here is necessary because TableContent is internally a <table> tag and does not respect width, height CSS */}
|
|
250
|
-
<div className="w-full overflow-auto border-x grow min-h-table relative" tabIndex={0}>
|
|
251
|
-
{/* Table overlay with loader */}
|
|
252
|
-
{query.isLoading && (
|
|
253
|
-
<TableOverlay className="cursor-wait">
|
|
254
|
-
<Loader />
|
|
255
|
-
</TableOverlay>
|
|
256
|
-
)}
|
|
257
|
-
{/* Table overlay for empty table */}
|
|
258
|
-
{!query.isLoading && tanTable.getRowModel().rows.length === 0 && (
|
|
259
|
-
// Empty table
|
|
260
|
-
<TableOverlay className="cursor-not-allowed">
|
|
261
|
-
{!query.isFetching &&
|
|
262
|
-
(query.isError ? (
|
|
263
|
-
<span className="flex gap-3 justify-center items-center">
|
|
264
|
-
{/* TODO (mudasir-pandith) Check for 403 explicitly!! */}
|
|
265
|
-
{/* <span>{query.error}</span> */}
|
|
266
|
-
<Info />
|
|
267
|
-
<span>
|
|
268
|
-
You don't have the required permissions. Please contact your
|
|
269
|
-
admin.
|
|
270
|
-
</span>
|
|
271
|
-
</span>
|
|
272
|
-
) : !query.data?.data ? (
|
|
273
|
-
<>No results found.</>
|
|
274
|
-
) : (
|
|
275
|
-
<>Unknown error. Please contact customer support.</>
|
|
276
|
-
))}
|
|
277
|
-
</TableOverlay>
|
|
278
|
-
)}
|
|
279
|
-
{!query.isLoading && tanTable.getRowModel().rows.length !== 0 &&
|
|
280
|
-
<TableContent className="size-full" />}
|
|
281
|
-
</div>
|
|
282
|
-
|
|
283
|
-
{/* Table Footer Section with Pagination and Column Picker */}
|
|
284
|
-
<div className="flex w-full justify-between border gap-icon p-icon overflow-x-auto">
|
|
285
|
-
<div className="flex gap-icon">
|
|
286
|
-
<ColumnPicker />
|
|
287
|
-
<PageSize />
|
|
288
|
-
</div>
|
|
289
|
-
<Paginator />
|
|
290
|
-
</div>
|
|
291
|
-
</section>
|
|
292
|
-
</>
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
/** Table overlay to be shown for loaders or other messages */
|
|
296
|
-
function TableOverlay({
|
|
297
|
-
children,
|
|
298
|
-
className,
|
|
299
|
-
}: { children: React.ReactNode; className?: string }) {
|
|
300
|
-
return (
|
|
301
|
-
<>
|
|
302
|
-
<span
|
|
303
|
-
className={cn(
|
|
304
|
-
className,
|
|
305
|
-
"absolute top-0 bg-card z-20 size-full text-sm flex items-center justify-center",
|
|
306
|
-
)}
|
|
307
|
-
>
|
|
308
|
-
{children}
|
|
309
|
-
</span>
|
|
310
|
-
{/* Keep something in document flow with the correct height */}
|
|
311
|
-
<span className="relative h-full w-0 block" />
|
|
312
|
-
</>
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
import {
|
|
317
|
-
arrayMove,
|
|
318
|
-
SortableContext,
|
|
319
|
-
sortableKeyboardCoordinates,
|
|
320
|
-
useSortable,
|
|
321
|
-
verticalListSortingStrategy
|
|
322
|
-
} from '@dnd-kit/sortable';
|
|
323
|
-
|
|
324
|
-
function ColumnPicker() {
|
|
325
|
-
const { tanTable } = useDataTable();
|
|
326
|
-
const [activeId, setActiveId] = useState<string | null>(null);
|
|
327
|
-
|
|
328
|
-
const resourceFormatter = useResourceFormatter();
|
|
329
|
-
|
|
330
|
-
const sensors = useSensors(
|
|
331
|
-
useSensor(PointerSensor),
|
|
332
|
-
useSensor(KeyboardSensor, {
|
|
333
|
-
coordinateGetter: sortableKeyboardCoordinates,
|
|
334
|
-
})
|
|
335
|
-
);
|
|
336
|
-
return (
|
|
337
|
-
<DndContext
|
|
338
|
-
sensors={sensors}
|
|
339
|
-
collisionDetection={closestCenter}
|
|
340
|
-
onDragStart={(event) => {
|
|
341
|
-
const { active } = event;
|
|
342
|
-
setActiveId(active.id.toString());
|
|
343
|
-
}}
|
|
344
|
-
onDragEnd={(event) => {
|
|
345
|
-
const { active, over } = event;
|
|
346
|
-
|
|
347
|
-
if (over && active.id !== over.id) {
|
|
348
|
-
const columnOrder = tanTable.getState().columnOrder;
|
|
349
|
-
const oldIndex = columnOrder.indexOf(active.id.toString());
|
|
350
|
-
const newIndex = columnOrder.indexOf(over.id.toString());
|
|
351
|
-
tanTable.setColumnOrder([...arrayMove(columnOrder, oldIndex, newIndex)]);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
setActiveId(null);
|
|
355
|
-
}}
|
|
356
|
-
>
|
|
357
|
-
<SortableContext
|
|
358
|
-
items={tanTable.getState().columnOrder}
|
|
359
|
-
strategy={verticalListSortingStrategy}
|
|
360
|
-
>
|
|
361
|
-
<EasyMenu label={<><Columns3 />Columns</>} selectionMode="multiple" items={tanTable.getAllFlatColumns()} selectedKeys={tanTable.getIsAllColumnsVisible() ? 'all' : tanTable.getVisibleFlatColumns().map(c => c.id)}>
|
|
362
|
-
<MenuItem onAction={() => tanTable.toggleAllColumnsVisible()} className={'italic'}>(select all)</MenuItem>
|
|
363
|
-
{tanTable.getState().columnOrder.map(colId => {
|
|
364
|
-
const col = tanTable.getAllFlatColumns().find(c => c.id === colId);
|
|
365
|
-
if (!col) return null;
|
|
366
|
-
return <SortableItem key={col.id} column={col} />
|
|
367
|
-
})}
|
|
368
|
-
</EasyMenu>
|
|
369
|
-
<DragOverlay>
|
|
370
|
-
{activeId ? <div className="dropdown-item opacity-70 border-2 border-primary">{resourceFormatter(activeId)}</div> : null}
|
|
371
|
-
</DragOverlay>
|
|
372
|
-
</SortableContext>
|
|
373
|
-
</DndContext>
|
|
374
|
-
);
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
function SortableItem({ column }: { column: Column<any, unknown> }) {
|
|
378
|
-
const {
|
|
379
|
-
attributes,
|
|
380
|
-
listeners,
|
|
381
|
-
setNodeRef,
|
|
382
|
-
transform,
|
|
383
|
-
transition,
|
|
384
|
-
} = useSortable({ id: column.id });
|
|
385
|
-
const style = {
|
|
386
|
-
transform: CSS.Transform.toString(transform),
|
|
387
|
-
transition,
|
|
388
|
-
zIndex: '999'
|
|
389
|
-
};
|
|
390
|
-
return <MenuItem
|
|
391
|
-
ref={setNodeRef}
|
|
392
|
-
style={style}
|
|
393
|
-
{...attributes}
|
|
394
|
-
id={column.id}
|
|
395
|
-
onAction={() => column.toggleVisibility()}
|
|
396
|
-
isDisabled={!column.getCanHide()} className="flex items-center">
|
|
397
|
-
<GripVertical {...listeners} className="size-icon cursor-grab" />
|
|
398
|
-
{resourceFormatter(column.id)}
|
|
399
|
-
{column.getIsSorted() && <SortIcon className="size-icon" direction={column.getIsSorted()} />}
|
|
400
|
-
</MenuItem>;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
import {
|
|
407
|
-
ChevronFirst,
|
|
408
|
-
ChevronLast,
|
|
409
|
-
ChevronLeft,
|
|
410
|
-
ChevronRight,
|
|
411
|
-
} from "lucide-react";
|
|
412
|
-
|
|
413
|
-
function Paginator() {
|
|
414
|
-
const { tanTable, query } = useDataTable();
|
|
415
|
-
const rowCount = query.data?.total ?? 0
|
|
416
|
-
return (
|
|
417
|
-
<div className="flex items-center gap-2">
|
|
418
|
-
<span className="whitespace-nowrap caption text-muted">
|
|
419
|
-
{`${tanTable.getState().pagination.pageIndex * tanTable.getState().pagination.pageSize + 1} - ${Math.min(
|
|
420
|
-
(tanTable.getState().pagination.pageIndex + 1) *
|
|
421
|
-
tanTable.getState().pagination.pageSize,
|
|
422
|
-
rowCount,
|
|
423
|
-
)} of ${rowCount?.toLocaleString()}`}
|
|
424
|
-
</span>
|
|
425
|
-
|
|
426
|
-
<Button
|
|
427
|
-
onPress={() => tanTable.firstPage()}
|
|
428
|
-
isDisabled={!tanTable.getCanPreviousPage()}
|
|
429
|
-
variant="neutral"
|
|
430
|
-
size={"icon"}
|
|
431
|
-
><ChevronFirst /></Button>
|
|
432
|
-
<Button
|
|
433
|
-
onPress={() => tanTable.previousPage()}
|
|
434
|
-
isDisabled={!tanTable.getCanPreviousPage()}
|
|
435
|
-
variant="neutral"
|
|
436
|
-
size={"icon"}
|
|
437
|
-
><ChevronLeft /></Button>
|
|
438
|
-
<Button
|
|
439
|
-
onPress={() => tanTable.nextPage()}
|
|
440
|
-
isDisabled={!tanTable.getCanNextPage()}
|
|
441
|
-
variant="neutral"
|
|
442
|
-
size={"icon"}
|
|
443
|
-
><ChevronRight /></Button>
|
|
444
|
-
<Button
|
|
445
|
-
onClick={() => tanTable.lastPage()}
|
|
446
|
-
isDisabled={!tanTable.getCanNextPage()}
|
|
447
|
-
variant="neutral"
|
|
448
|
-
size={"icon"}
|
|
449
|
-
><ChevronLast /></Button>
|
|
450
|
-
</div>
|
|
451
|
-
);
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
function PageSize() {
|
|
456
|
-
const { tanTable } = useDataTable();
|
|
457
|
-
const PAGE_SIZES = [10, 20, 30, 40, 50];
|
|
458
|
-
|
|
459
|
-
return (
|
|
460
|
-
<EasyMenu label={tanTable.getState().pagination.pageSize.toString()} selectionMode="single" selectedKeys={[tanTable.getState().pagination.pageSize.toString()]} items={PAGE_SIZES.map(s => ({ id: s.toString(), value: s }))}>
|
|
461
|
-
{
|
|
462
|
-
(items) => <MenuItem onAction={() => tanTable.setPageSize(items.value)}>{items.value}</MenuItem>
|
|
463
|
-
}
|
|
464
|
-
</EasyMenu>
|
|
465
|
-
);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
import { RotateCw } from "lucide-react";
|
|
470
|
-
|
|
471
|
-
function Actions({ tableActions }: { tableActions: TableActions }) {
|
|
472
|
-
const { query, tanTable, searchQuery, setSearchQuery } = useDataTable();
|
|
473
|
-
|
|
474
|
-
return (
|
|
475
|
-
<section className="flex bg-card justify-between my-0 p-icon border gap-icon overflow-auto">
|
|
476
|
-
<div className="flex gap-icon">
|
|
477
|
-
<Button
|
|
478
|
-
isPending={query.isFetching}
|
|
479
|
-
onClick={() => query.refetch()}
|
|
480
|
-
variant={"neutral"}
|
|
481
|
-
size={"icon"}
|
|
482
|
-
><RotateCw /></Button>
|
|
483
|
-
|
|
484
|
-
{tableActions
|
|
485
|
-
.filter(ta => ta.bulk === tanTable.getSelectedRowModel().rows.length > 0)
|
|
486
|
-
.map((ta, i) => {
|
|
487
|
-
const Icon = ta.icon;
|
|
488
|
-
return (<Button key={`${i}-${ta.bulk}`} type="button" isDisabled={query.isFetching} className="animate-in fade-in slide-in-from-left-15 duration-300 transition-transform" onPress={(e) => { ta.onClick(e, tanTable) }} size={'icon'}><Icon /></Button>)
|
|
489
|
-
}
|
|
490
|
-
)}
|
|
491
|
-
</div>
|
|
492
|
-
<div className="flex gap-icon">
|
|
493
|
-
<DataTableFilter />
|
|
494
|
-
{(
|
|
495
|
-
<SearchField value={searchQuery} onChange={setSearchQuery} />
|
|
496
|
-
)}
|
|
497
|
-
</div>
|
|
498
|
-
</section>
|
|
499
|
-
);
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
import { closestCenter, DndContext, DragOverlay, KeyboardSensor, PointerSensor, useSensor, useSensors } from "@dnd-kit/core";
|
|
504
|
-
import { CSS } from "@dnd-kit/utilities";
|
|
505
|
-
import {
|
|
506
|
-
flexRender
|
|
507
|
-
} from "@tanstack/react-table";
|
|
508
|
-
import { DataTableFilter, type FiltersConfig } from "lib/components/data-table/data-table-filter";
|
|
509
|
-
import { type ApiFilters, type ApiSchema, TABLE_CHECK_BOX_COLUMN, TABLE_DEFAULT_DATE_COLUMNS, TABLE_ID_COLUMN, type TableActions, type TableFetchFn } from "lib/components/data-table/table-commons";
|
|
510
|
-
import { SearchField } from "lib/components/inputs/searchfield";
|
|
511
|
-
import { Button } from "lib/components/ui/button";
|
|
512
|
-
import { Loader } from "lib/components/ui/loader";
|
|
513
|
-
import { EasyMenu, MenuItem } from "lib/components/ui/menu";
|
|
514
|
-
import {
|
|
515
|
-
TableBody,
|
|
516
|
-
TableCell,
|
|
517
|
-
Table as TableComponent,
|
|
518
|
-
TableHead,
|
|
519
|
-
TableHeader,
|
|
520
|
-
TableRow,
|
|
521
|
-
} from "lib/components/ui/table";
|
|
522
|
-
import { cn } from "lib/utils/primitives";
|
|
523
|
-
import { useResourceFormatter } from "lib/utils/resource-names";
|
|
524
|
-
import { merge } from "lodash-es";
|
|
525
|
-
|
|
526
|
-
function SortIcon({ direction, ...props }: { direction: 'asc' | 'desc' | false } & Omit<React.ComponentProps<'svg'>, 'direction'>) {
|
|
527
|
-
if (direction === 'asc') return <ArrowDownNarrowWide {...props} />
|
|
528
|
-
else if (direction === 'desc') return <ArrowDownWideNarrow {...props} />
|
|
529
|
-
else return <ArrowUpDown {...props} />
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// TODO, automate checking valid HTML
|
|
533
|
-
function TableContent({ className }: React.ComponentProps<typeof TableComponent>) {
|
|
534
|
-
const { tanTable } = useDataTable();
|
|
535
|
-
const tableCellStyle = (isSticky: boolean, className?: string) => cn("animate-in fade-in slide-in-from-top-10", "px-icon py-2 text-left text-sm font-medium whitespace-nowrap", isSticky && "bg-card sticky right-0 z-50 text-center", className)
|
|
536
|
-
return (
|
|
537
|
-
<TableComponent className={cn(className)}>
|
|
538
|
-
<TableHeader className="sticky top-0 z-10">
|
|
539
|
-
{tanTable.getHeaderGroups().map((headerGroup) => (
|
|
540
|
-
<TableRow className={cn("h-input")} key={headerGroup.id}>
|
|
541
|
-
{headerGroup.headers.map((header) => (
|
|
542
|
-
<TableHead
|
|
543
|
-
key={header.id}
|
|
544
|
-
className={tableCellStyle(false, "bg-card")}
|
|
545
|
-
>
|
|
546
|
-
{!header.column.getCanSort() && !header.isPlaceholder
|
|
547
|
-
&& <>{flexRender(
|
|
548
|
-
header.column.columnDef.header,
|
|
549
|
-
header.getContext(),
|
|
550
|
-
)}</>}
|
|
551
|
-
{/* TODO Align header text with table text */}
|
|
552
|
-
{header.column.getCanSort() && (
|
|
553
|
-
<Button
|
|
554
|
-
variant="ghost"
|
|
555
|
-
className="w-full !justify-start !px-1.5"
|
|
556
|
-
onPress={header.column.getToggleSortingHandler()}
|
|
557
|
-
>
|
|
558
|
-
{flexRender(
|
|
559
|
-
header.column.columnDef.header,
|
|
560
|
-
header.getContext(),
|
|
561
|
-
)}
|
|
562
|
-
<SortIcon direction={header.column.getIsSorted()} />
|
|
563
|
-
</Button>
|
|
564
|
-
)}
|
|
565
|
-
</TableHead>
|
|
566
|
-
))}
|
|
567
|
-
</TableRow>
|
|
568
|
-
))}
|
|
569
|
-
</TableHeader>
|
|
570
|
-
<TableBody className="flex-1 overflow-y-auto relative">
|
|
571
|
-
{tanTable.getRowModel().rows.map((row) => (
|
|
572
|
-
<TableRow
|
|
573
|
-
className={cn("h-input transition-colors data-[selected=true]:bg-primary/10 hover:bg-muted-foreground/20")}
|
|
574
|
-
key={row.id}
|
|
575
|
-
data-selected={row.getIsSelected()}
|
|
576
|
-
>
|
|
577
|
-
{row.getVisibleCells().map((cell) => (
|
|
578
|
-
<TableCell
|
|
579
|
-
key={cell.id}
|
|
580
|
-
className={tableCellStyle(cell.column.id === ACTIONS_COLUMN_ID)}
|
|
581
|
-
>
|
|
582
|
-
{flexRender(
|
|
583
|
-
cell.column.columnDef.cell,
|
|
584
|
-
cell.getContext(),
|
|
585
|
-
)}
|
|
586
|
-
</TableCell>
|
|
587
|
-
))}
|
|
588
|
-
</TableRow>
|
|
589
|
-
))}
|
|
590
|
-
</TableBody>
|
|
591
|
-
</TableComponent>
|
|
592
|
-
);
|
|
593
|
-
}
|