@carefully-built/crud 0.1.2 → 0.1.3
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.mts +4 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +128 -140
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ActionHandlers, ActionType, Column, FilterConfig, ResponsiveSheetClassNames, SortState } from "@carefully-built/ui";
|
|
1
|
+
import { ActionHandlers, ActionType, Column, FilterConfig, ResponsiveSheetClassNames, SheetOutsideInteractionGuard, SortState } from "@carefully-built/ui";
|
|
2
2
|
import { ReactNode } from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/pagination.d.ts
|
|
@@ -18,6 +18,7 @@ interface CrudFilterDefinition<TItem> {
|
|
|
18
18
|
readonly key: Extract<keyof TItem, string>;
|
|
19
19
|
readonly config: FilterConfig;
|
|
20
20
|
readonly allowAll?: boolean;
|
|
21
|
+
readonly allOptionLabel?: string;
|
|
21
22
|
readonly clearable?: boolean;
|
|
22
23
|
}
|
|
23
24
|
interface UseCrudTableStateOptions<TItem> {
|
|
@@ -125,6 +126,7 @@ interface CrudResourceSheetProps {
|
|
|
125
126
|
readonly confirmLoading?: boolean;
|
|
126
127
|
readonly confirmCloseWhenDirty?: boolean;
|
|
127
128
|
readonly width?: number;
|
|
129
|
+
readonly outsideInteractionGuard?: SheetOutsideInteractionGuard;
|
|
128
130
|
readonly className?: string;
|
|
129
131
|
readonly contentClassName?: string;
|
|
130
132
|
readonly footerClassName?: string;
|
|
@@ -135,6 +137,7 @@ declare function CrudResourceSheet({
|
|
|
135
137
|
formId,
|
|
136
138
|
onConfirm,
|
|
137
139
|
confirmCloseWhenDirty: _confirmCloseWhenDirty,
|
|
140
|
+
outsideInteractionGuard,
|
|
138
141
|
...sheetProps
|
|
139
142
|
}: CrudResourceSheetProps): React.ReactElement;
|
|
140
143
|
//#endregion
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/pagination.ts","../src/types.ts","../src/crud-data-table.tsx","../src/crud-list-table.tsx","../src/crud-resource-sheet.tsx","../src/crud-table-view.tsx","../src/use-crud-table-state.ts","../src/use-url-string-filters.ts","../src/use-url-pagination.ts"],"sourcesContent":[],"mappings":";;;;UAAiB,mBAAA;;;;EAAA,SAAA,QAAA,EAAA,MAAmB;;;;ACKpC;;;KAAY,cAAA;ADLK,UCOA,oBDPmB,CAAA,KAAA,CAAA,CAAA;gBCQpB,cAAc;mBACX;;EAJP,SAAA,
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/pagination.ts","../src/types.ts","../src/crud-data-table.tsx","../src/crud-list-table.tsx","../src/crud-resource-sheet.tsx","../src/crud-table-view.tsx","../src/use-crud-table-state.ts","../src/use-url-string-filters.ts","../src/use-url-pagination.ts"],"sourcesContent":[],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ResponsiveSheet, SmartTable, TableToolbar, useTableSorting } from "@carefully-built/ui";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useCallback, useMemo
|
|
3
|
+
import { useCallback, useMemo } from "react";
|
|
4
4
|
import { parseAsInteger, parseAsString, useQueryState, useQueryStates } from "nuqs";
|
|
5
5
|
|
|
6
6
|
//#region src/crud-data-table.tsx
|
|
@@ -34,7 +34,7 @@ function CrudListTable(props) {
|
|
|
34
34
|
|
|
35
35
|
//#endregion
|
|
36
36
|
//#region src/crud-resource-sheet.tsx
|
|
37
|
-
function CrudResourceSheet({ children, formId, onConfirm, confirmCloseWhenDirty: _confirmCloseWhenDirty, ...sheetProps }) {
|
|
37
|
+
function CrudResourceSheet({ children, formId, onConfirm, confirmCloseWhenDirty: _confirmCloseWhenDirty, outsideInteractionGuard, ...sheetProps }) {
|
|
38
38
|
const submitForm = () => {
|
|
39
39
|
const form = formId ? document.getElementById(formId) : null;
|
|
40
40
|
if (form instanceof HTMLFormElement) {
|
|
@@ -45,6 +45,7 @@ function CrudResourceSheet({ children, formId, onConfirm, confirmCloseWhenDirty:
|
|
|
45
45
|
};
|
|
46
46
|
return /* @__PURE__ */ jsx(ResponsiveSheet, {
|
|
47
47
|
...sheetProps,
|
|
48
|
+
outsideInteractionGuard,
|
|
48
49
|
onConfirm: formId || onConfirm ? submitForm : void 0,
|
|
49
50
|
children
|
|
50
51
|
});
|
|
@@ -69,6 +70,7 @@ function CrudTableView({ state, columns, isLoading, searchPlaceholder = "Search.
|
|
|
69
70
|
state.setFilter(filter.key, value);
|
|
70
71
|
},
|
|
71
72
|
allowAll: filter.allowAll,
|
|
73
|
+
allOptionLabel: filter.allOptionLabel,
|
|
72
74
|
clearable: filter.clearable
|
|
73
75
|
})),
|
|
74
76
|
onClearAll: state.clearAll,
|
|
@@ -110,13 +112,104 @@ function matchesCrudSearch(searchText, query) {
|
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
//#endregion
|
|
113
|
-
//#region src/pagination.ts
|
|
114
|
-
function
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
//#region src/use-url-pagination.ts
|
|
116
|
+
function useUrlPagination({ totalItems, pageSize = 20, pageParam = "page" }) {
|
|
117
|
+
const [page, setPage] = useQueryState(pageParam, parseAsInteger.withDefault(1));
|
|
118
|
+
const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
|
|
119
|
+
const currentPage = Math.min(Math.max(1, page), totalPages);
|
|
118
120
|
const startIndex = (currentPage - 1) * pageSize;
|
|
119
|
-
|
|
121
|
+
const endIndex = Math.min(startIndex + pageSize, totalItems);
|
|
122
|
+
const hasPrevPage = currentPage > 1;
|
|
123
|
+
const hasNextPage = currentPage < totalPages;
|
|
124
|
+
const goToPage = useCallback((newPage) => {
|
|
125
|
+
const validPage = Math.min(Math.max(1, newPage), totalPages);
|
|
126
|
+
setPage(validPage === 1 ? null : validPage);
|
|
127
|
+
}, [setPage, totalPages]);
|
|
128
|
+
const nextPage = useCallback(() => {
|
|
129
|
+
if (hasNextPage) goToPage(currentPage + 1);
|
|
130
|
+
}, [
|
|
131
|
+
currentPage,
|
|
132
|
+
goToPage,
|
|
133
|
+
hasNextPage
|
|
134
|
+
]);
|
|
135
|
+
const prevPage = useCallback(() => {
|
|
136
|
+
if (hasPrevPage) goToPage(currentPage - 1);
|
|
137
|
+
}, [
|
|
138
|
+
currentPage,
|
|
139
|
+
goToPage,
|
|
140
|
+
hasPrevPage
|
|
141
|
+
]);
|
|
142
|
+
const firstPage = useCallback(() => {
|
|
143
|
+
goToPage(1);
|
|
144
|
+
}, [goToPage]);
|
|
145
|
+
const lastPage = useCallback(() => {
|
|
146
|
+
goToPage(totalPages);
|
|
147
|
+
}, [goToPage, totalPages]);
|
|
148
|
+
const paginate = useCallback((data) => data.slice(startIndex, endIndex), [endIndex, startIndex]);
|
|
149
|
+
return useMemo(() => ({
|
|
150
|
+
currentPage,
|
|
151
|
+
pageSize,
|
|
152
|
+
totalPages,
|
|
153
|
+
totalItems,
|
|
154
|
+
startIndex,
|
|
155
|
+
endIndex,
|
|
156
|
+
hasPrevPage,
|
|
157
|
+
hasNextPage,
|
|
158
|
+
goToPage,
|
|
159
|
+
nextPage,
|
|
160
|
+
prevPage,
|
|
161
|
+
firstPage,
|
|
162
|
+
lastPage,
|
|
163
|
+
paginate,
|
|
164
|
+
onPageChange: goToPage
|
|
165
|
+
}), [
|
|
166
|
+
currentPage,
|
|
167
|
+
endIndex,
|
|
168
|
+
firstPage,
|
|
169
|
+
goToPage,
|
|
170
|
+
hasNextPage,
|
|
171
|
+
hasPrevPage,
|
|
172
|
+
lastPage,
|
|
173
|
+
nextPage,
|
|
174
|
+
pageSize,
|
|
175
|
+
paginate,
|
|
176
|
+
prevPage,
|
|
177
|
+
startIndex,
|
|
178
|
+
totalItems,
|
|
179
|
+
totalPages
|
|
180
|
+
]);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
//#endregion
|
|
184
|
+
//#region src/use-url-string-filters.ts
|
|
185
|
+
function buildParserMap(definitions) {
|
|
186
|
+
return Object.fromEntries(definitions.map((definition) => [definition.param ?? definition.key, parseAsString.withDefault(definition.defaultValue ?? "all")]));
|
|
187
|
+
}
|
|
188
|
+
function normalizeFilterValue(value, definition) {
|
|
189
|
+
if (value === (definition.clearValue ?? definition.defaultValue ?? "all")) return null;
|
|
190
|
+
return value.length > 0 ? value : null;
|
|
191
|
+
}
|
|
192
|
+
function useUrlStringFilters(definitions) {
|
|
193
|
+
const [params, setParams] = useQueryStates(useMemo(() => buildParserMap(definitions), [definitions]));
|
|
194
|
+
const values = useMemo(() => Object.fromEntries(definitions.map((definition) => {
|
|
195
|
+
const param = definition.param ?? definition.key;
|
|
196
|
+
return [definition.key, params[param] ?? definition.defaultValue ?? "all"];
|
|
197
|
+
})), [definitions, params]);
|
|
198
|
+
return {
|
|
199
|
+
values,
|
|
200
|
+
setValue: useCallback((key, value) => {
|
|
201
|
+
const definition = definitions.find((item) => item.key === key);
|
|
202
|
+
if (!definition) return;
|
|
203
|
+
setParams({ [definition.param ?? definition.key]: normalizeFilterValue(value, definition) });
|
|
204
|
+
}, [definitions, setParams]),
|
|
205
|
+
clear: useCallback(() => {
|
|
206
|
+
setParams(Object.fromEntries(definitions.map((definition) => [definition.param ?? definition.key, null])));
|
|
207
|
+
}, [definitions, setParams]),
|
|
208
|
+
getDraftValues: useCallback((draftValues) => ({
|
|
209
|
+
...values,
|
|
210
|
+
...draftValues
|
|
211
|
+
}), [values])
|
|
212
|
+
};
|
|
120
213
|
}
|
|
121
214
|
|
|
122
215
|
//#endregion
|
|
@@ -139,45 +232,49 @@ function filterCrudData(data, search, filters, searchFields) {
|
|
|
139
232
|
});
|
|
140
233
|
}
|
|
141
234
|
function useCrudTableState({ data, columns, searchFields = [], filters: filterDefinitions = [], pageSize = 20, initialSortState = null }) {
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
235
|
+
const urlFilters = useUrlStringFilters(useMemo(() => [{
|
|
236
|
+
key: "search",
|
|
237
|
+
defaultValue: "",
|
|
238
|
+
clearValue: ""
|
|
239
|
+
}, ...filterDefinitions.map((filter) => ({
|
|
240
|
+
key: filter.key,
|
|
241
|
+
defaultValue: "all",
|
|
242
|
+
clearValue: "all"
|
|
243
|
+
}))], [filterDefinitions]));
|
|
244
|
+
const search = urlFilters.values.search ?? "";
|
|
245
|
+
const filters = useMemo(() => ({
|
|
246
|
+
...buildInitialFilters(filterDefinitions),
|
|
247
|
+
...Object.fromEntries(filterDefinitions.map((filter) => [filter.key, urlFilters.values[filter.key] ?? "all"]))
|
|
248
|
+
}), [filterDefinitions, urlFilters.values]);
|
|
145
249
|
const filteredData = useMemo(() => filterCrudData(data, search, filters, searchFields), [
|
|
146
250
|
data,
|
|
147
251
|
filters,
|
|
148
252
|
search,
|
|
149
253
|
searchFields
|
|
150
254
|
]);
|
|
255
|
+
const pagination = useUrlPagination({
|
|
256
|
+
totalItems: filteredData.length,
|
|
257
|
+
pageSize
|
|
258
|
+
});
|
|
259
|
+
const setCurrentPage = pagination.onPageChange;
|
|
151
260
|
const { sortedData, sortState, setSortState } = useTableSorting({
|
|
152
261
|
data: filteredData,
|
|
153
262
|
columns,
|
|
154
263
|
initialSortState
|
|
155
264
|
});
|
|
156
|
-
const
|
|
157
|
-
const validCurrentPage = getValidPage(currentPage, totalPages);
|
|
158
|
-
const startIndex = (validCurrentPage - 1) * pageSize;
|
|
159
|
-
const endIndex = Math.min(startIndex + pageSize, filteredData.length);
|
|
160
|
-
const paginatedData = useMemo(() => paginateCrudData(sortedData, validCurrentPage, pageSize), [
|
|
161
|
-
pageSize,
|
|
162
|
-
sortedData,
|
|
163
|
-
validCurrentPage
|
|
164
|
-
]);
|
|
265
|
+
const paginatedData = useMemo(() => pagination.paginate(sortedData), [pagination, sortedData]);
|
|
165
266
|
const setFilter = useCallback((key, value) => {
|
|
166
|
-
|
|
167
|
-
...currentFilters,
|
|
168
|
-
[key]: value
|
|
169
|
-
}));
|
|
267
|
+
urlFilters.setValue(key, value);
|
|
170
268
|
setCurrentPage(1);
|
|
171
|
-
}, []);
|
|
269
|
+
}, [setCurrentPage, urlFilters]);
|
|
172
270
|
const updateSearch = useCallback((value) => {
|
|
173
|
-
|
|
271
|
+
urlFilters.setValue("search", value);
|
|
174
272
|
setCurrentPage(1);
|
|
175
|
-
}, []);
|
|
273
|
+
}, [setCurrentPage, urlFilters]);
|
|
176
274
|
const clearAll = useCallback(() => {
|
|
177
|
-
|
|
178
|
-
setFilters(buildInitialFilters(filterDefinitions));
|
|
275
|
+
urlFilters.clear();
|
|
179
276
|
setCurrentPage(1);
|
|
180
|
-
}, [
|
|
277
|
+
}, [setCurrentPage, urlFilters]);
|
|
181
278
|
const getDraftFilterResultCount = useCallback((draftValues) => filterCrudData(data, search, {
|
|
182
279
|
...filters,
|
|
183
280
|
...draftValues
|
|
@@ -204,119 +301,10 @@ function useCrudTableState({ data, columns, searchFields = [], filters: filterDe
|
|
|
204
301
|
emptyState: hasSearch || hasFilters ? "no-results" : "initial",
|
|
205
302
|
sortState,
|
|
206
303
|
setSortState,
|
|
207
|
-
pagination
|
|
208
|
-
currentPage: validCurrentPage,
|
|
209
|
-
totalPages,
|
|
210
|
-
totalItems: filteredData.length,
|
|
211
|
-
pageSize,
|
|
212
|
-
startIndex,
|
|
213
|
-
endIndex,
|
|
214
|
-
onPageChange: setCurrentPage
|
|
215
|
-
}
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
//#endregion
|
|
220
|
-
//#region src/use-url-string-filters.ts
|
|
221
|
-
function buildParserMap(definitions) {
|
|
222
|
-
return Object.fromEntries(definitions.map((definition) => [definition.param ?? definition.key, parseAsString.withDefault(definition.defaultValue ?? "all")]));
|
|
223
|
-
}
|
|
224
|
-
function normalizeFilterValue(value, definition) {
|
|
225
|
-
if (value === (definition.clearValue ?? definition.defaultValue ?? "all")) return null;
|
|
226
|
-
return value.length > 0 ? value : null;
|
|
227
|
-
}
|
|
228
|
-
function useUrlStringFilters(definitions) {
|
|
229
|
-
const [params, setParams] = useQueryStates(useMemo(() => buildParserMap(definitions), [definitions]));
|
|
230
|
-
const values = useMemo(() => Object.fromEntries(definitions.map((definition) => {
|
|
231
|
-
const param = definition.param ?? definition.key;
|
|
232
|
-
return [definition.key, params[param] ?? definition.defaultValue ?? "all"];
|
|
233
|
-
})), [definitions, params]);
|
|
234
|
-
return {
|
|
235
|
-
values,
|
|
236
|
-
setValue: useCallback((key, value) => {
|
|
237
|
-
const definition = definitions.find((item) => item.key === key);
|
|
238
|
-
if (!definition) return;
|
|
239
|
-
setParams({ [definition.param ?? definition.key]: normalizeFilterValue(value, definition) });
|
|
240
|
-
}, [definitions, setParams]),
|
|
241
|
-
clear: useCallback(() => {
|
|
242
|
-
setParams(Object.fromEntries(definitions.map((definition) => [definition.param ?? definition.key, null])));
|
|
243
|
-
}, [definitions, setParams]),
|
|
244
|
-
getDraftValues: useCallback((draftValues) => ({
|
|
245
|
-
...values,
|
|
246
|
-
...draftValues
|
|
247
|
-
}), [values])
|
|
304
|
+
pagination
|
|
248
305
|
};
|
|
249
306
|
}
|
|
250
307
|
|
|
251
|
-
//#endregion
|
|
252
|
-
//#region src/use-url-pagination.ts
|
|
253
|
-
function useUrlPagination({ totalItems, pageSize = 20, pageParam = "page" }) {
|
|
254
|
-
const [page, setPage] = useQueryState(pageParam, parseAsInteger.withDefault(1));
|
|
255
|
-
const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
|
|
256
|
-
const currentPage = Math.min(Math.max(1, page), totalPages);
|
|
257
|
-
const startIndex = (currentPage - 1) * pageSize;
|
|
258
|
-
const endIndex = Math.min(startIndex + pageSize, totalItems);
|
|
259
|
-
const hasPrevPage = currentPage > 1;
|
|
260
|
-
const hasNextPage = currentPage < totalPages;
|
|
261
|
-
const goToPage = useCallback((newPage) => {
|
|
262
|
-
const validPage = Math.min(Math.max(1, newPage), totalPages);
|
|
263
|
-
setPage(validPage === 1 ? null : validPage);
|
|
264
|
-
}, [setPage, totalPages]);
|
|
265
|
-
const nextPage = useCallback(() => {
|
|
266
|
-
if (hasNextPage) goToPage(currentPage + 1);
|
|
267
|
-
}, [
|
|
268
|
-
currentPage,
|
|
269
|
-
goToPage,
|
|
270
|
-
hasNextPage
|
|
271
|
-
]);
|
|
272
|
-
const prevPage = useCallback(() => {
|
|
273
|
-
if (hasPrevPage) goToPage(currentPage - 1);
|
|
274
|
-
}, [
|
|
275
|
-
currentPage,
|
|
276
|
-
goToPage,
|
|
277
|
-
hasPrevPage
|
|
278
|
-
]);
|
|
279
|
-
const firstPage = useCallback(() => {
|
|
280
|
-
goToPage(1);
|
|
281
|
-
}, [goToPage]);
|
|
282
|
-
const lastPage = useCallback(() => {
|
|
283
|
-
goToPage(totalPages);
|
|
284
|
-
}, [goToPage, totalPages]);
|
|
285
|
-
const paginate = useCallback((data) => data.slice(startIndex, endIndex), [endIndex, startIndex]);
|
|
286
|
-
return useMemo(() => ({
|
|
287
|
-
currentPage,
|
|
288
|
-
pageSize,
|
|
289
|
-
totalPages,
|
|
290
|
-
totalItems,
|
|
291
|
-
startIndex,
|
|
292
|
-
endIndex,
|
|
293
|
-
hasPrevPage,
|
|
294
|
-
hasNextPage,
|
|
295
|
-
goToPage,
|
|
296
|
-
nextPage,
|
|
297
|
-
prevPage,
|
|
298
|
-
firstPage,
|
|
299
|
-
lastPage,
|
|
300
|
-
paginate,
|
|
301
|
-
onPageChange: goToPage
|
|
302
|
-
}), [
|
|
303
|
-
currentPage,
|
|
304
|
-
endIndex,
|
|
305
|
-
firstPage,
|
|
306
|
-
goToPage,
|
|
307
|
-
hasNextPage,
|
|
308
|
-
hasPrevPage,
|
|
309
|
-
lastPage,
|
|
310
|
-
nextPage,
|
|
311
|
-
pageSize,
|
|
312
|
-
paginate,
|
|
313
|
-
prevPage,
|
|
314
|
-
startIndex,
|
|
315
|
-
totalItems,
|
|
316
|
-
totalPages
|
|
317
|
-
]);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
308
|
//#endregion
|
|
321
309
|
export { CrudDataTable, CrudListTable, CrudResourceSheet, CrudTableView, useCrudTableState, useUrlPagination, useUrlStringFilters };
|
|
322
310
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/crud-data-table.tsx","../src/crud-list-table.tsx","../src/crud-resource-sheet.tsx","../src/crud-table-view.tsx","../src/search.ts","../src/pagination.ts","../src/use-crud-table-state.ts","../src/use-url-string-filters.ts","../src/use-url-pagination.ts"],"sourcesContent":["\"use client\";\n\nimport { SmartTable } from \"@carefully-built/ui\";\n\nimport type {\n CrudDataTableProps,\n} from \"./types\";\n\nexport function CrudDataTable<TItem extends object>({\n actions,\n actionHandlers,\n columns,\n data,\n fullHeight = true,\n getRowKey,\n isLoading,\n maxHeight,\n noDataContent,\n noDataMessage,\n onRowClick,\n pagination,\n renderActions,\n renderMobileCard,\n sortState,\n stickyHeader = true,\n onSortChange,\n}: CrudDataTableProps<TItem>): React.ReactElement {\n return (\n <SmartTable\n data={[...data]}\n columns={[...columns]}\n isLoading={isLoading}\n actions={actions ? [...actions] : undefined}\n actionHandlers={actionHandlers}\n renderActions={renderActions}\n noDataMessage={noDataMessage}\n noDataContent={noDataContent}\n getRowKey={getRowKey}\n onRowClick={onRowClick}\n renderMobileCard={renderMobileCard}\n stickyHeader={stickyHeader}\n fullHeight={fullHeight}\n maxHeight={maxHeight}\n sortState={sortState}\n onSortChange={onSortChange}\n pagination={pagination}\n />\n );\n}\n","\"use client\";\n\nimport { CrudDataTable } from \"./crud-data-table\";\nimport type { CrudDataTableProps } from \"./types\";\n\nexport type CrudListTableProps<TItem extends object> = CrudDataTableProps<TItem>;\n\nexport function CrudListTable<TItem extends object>(\n props: CrudListTableProps<TItem>,\n): React.ReactElement {\n return <CrudDataTable {...props} />;\n}\n","\"use client\";\n\nimport type { ReactNode } from \"react\";\n\nimport { ResponsiveSheet } from \"@carefully-built/ui\";\nimport type { ResponsiveSheetClassNames } from \"@carefully-built/ui\";\n\nexport interface CrudResourceSheetProps {\n readonly open: boolean;\n readonly onOpenChange: (open: boolean) => void;\n readonly title: ReactNode;\n readonly children: ReactNode;\n readonly formId?: string;\n readonly description?: ReactNode;\n readonly onCancel?: () => void;\n readonly cancelLabel?: ReactNode;\n readonly onConfirm?: () => void;\n readonly confirmLabel?: ReactNode;\n readonly confirmDisabled?: boolean;\n readonly confirmLoading?: boolean;\n readonly confirmCloseWhenDirty?: boolean;\n readonly width?: number;\n readonly className?: string;\n readonly contentClassName?: string;\n readonly footerClassName?: string;\n readonly classes?: ResponsiveSheetClassNames;\n}\n\nexport function CrudResourceSheet({\n children,\n formId,\n onConfirm,\n confirmCloseWhenDirty: _confirmCloseWhenDirty,\n ...sheetProps\n}: CrudResourceSheetProps): React.ReactElement {\n const submitForm = (): void => {\n const form = formId ? document.getElementById(formId) : null;\n\n if (form instanceof HTMLFormElement) {\n form.requestSubmit();\n return;\n }\n\n onConfirm?.();\n };\n\n return (\n <ResponsiveSheet\n {...sheetProps}\n onConfirm={formId || onConfirm ? submitForm : undefined}\n >\n {children}\n </ResponsiveSheet>\n );\n}\n","\"use client\";\n\nimport type { CrudTableViewProps } from \"./types\";\n\nimport { SmartTable, TableToolbar } from \"@carefully-built/ui\";\n\nexport function CrudTableView<TItem extends object>({\n state,\n columns,\n isLoading,\n searchPlaceholder = \"Search...\",\n filters = [],\n actions,\n actionHandlers,\n renderActions,\n noDataMessage,\n initialEmptyContent,\n noResultsContent,\n getRowKey,\n onRowClick,\n renderMobileCard,\n stickyHeader = true,\n fullHeight = true,\n maxHeight,\n}: CrudTableViewProps<TItem>): React.ReactElement {\n const emptyContent =\n state.emptyState === \"no-results\" ? noResultsContent : initialEmptyContent;\n\n return (\n <>\n <div className=\"shrink-0\">\n <TableToolbar\n search={{\n value: state.search,\n onChange: state.setSearch,\n placeholder: searchPlaceholder,\n }}\n filters={filters.map((filter) => ({\n config: filter.config,\n value: state.filters[filter.key] ?? \"all\",\n onChange: (value) => {\n state.setFilter(filter.key, value);\n },\n allowAll: filter.allowAll,\n clearable: filter.clearable,\n }))}\n onClearAll={state.clearAll}\n getDraftResultCount={state.getDraftFilterResultCount}\n />\n </div>\n\n <SmartTable\n data={state.paginatedData}\n columns={[...columns]}\n isLoading={isLoading}\n actions={actions ? [...actions] : undefined}\n actionHandlers={actionHandlers}\n renderActions={renderActions}\n noDataMessage={noDataMessage}\n noDataContent={emptyContent}\n getRowKey={getRowKey}\n onRowClick={onRowClick}\n renderMobileCard={renderMobileCard}\n stickyHeader={stickyHeader}\n fullHeight={fullHeight}\n maxHeight={maxHeight}\n sortState={state.sortState}\n onSortChange={state.setSortState}\n pagination={state.pagination}\n />\n </>\n );\n}\n","function normalizeSearchText(value: string): string {\n return value\n .toLocaleLowerCase()\n .normalize(\"NFD\")\n .replace(/\\p{Diacritic}/gu, \"\")\n .trim();\n}\n\nexport function buildCrudSearchText(values: readonly unknown[]): string {\n return normalizeSearchText(\n values\n .filter(\n (value): value is string | number =>\n typeof value === \"string\" || typeof value === \"number\",\n )\n .map(String)\n .join(\" \"),\n );\n}\n\nexport function matchesCrudSearch(searchText: string, query: string): boolean {\n const normalizedQuery = normalizeSearchText(query);\n\n if (!normalizedQuery) {\n return true;\n }\n\n return normalizedQuery\n .split(/\\s+/)\n .every((token) => searchText.includes(token));\n}\n","export interface CrudPaginationState {\n readonly currentPage: number;\n readonly totalPages: number;\n readonly totalItems: number;\n readonly pageSize: number;\n readonly startIndex: number;\n readonly endIndex: number;\n readonly onPageChange: (page: number) => void;\n}\n\nexport function getValidPage(page: number, totalPages: number): number {\n return Math.min(Math.max(1, page), totalPages);\n}\n\nexport function paginateCrudData<T>(\n data: readonly T[],\n currentPage: number,\n pageSize: number,\n): T[] {\n const startIndex = (currentPage - 1) * pageSize;\n return data.slice(startIndex, startIndex + pageSize);\n}\n","\"use client\";\n\nimport { useCallback, useMemo, useState } from \"react\";\n\nimport type { UseCrudTableStateOptions, CrudTableState } from \"./types\";\n\nimport { useTableSorting } from \"@carefully-built/ui\";\n\nimport { buildCrudSearchText, matchesCrudSearch } from \"./search\";\nimport { getValidPage, paginateCrudData } from \"./pagination\";\n\nfunction buildInitialFilters<TItem>(\n filters: readonly { readonly key: Extract<keyof TItem, string> }[],\n): Record<string, string> {\n return Object.fromEntries(filters.map((filter) => [filter.key, \"all\"]));\n}\n\nfunction itemMatchesFilters<TItem extends object>(\n item: TItem,\n filters: Record<string, string>,\n): boolean {\n const record = item as Record<string, unknown>;\n\n return Object.entries(filters).every(([key, filterValue]) => {\n if (!filterValue || filterValue === \"all\") {\n return true;\n }\n\n return record[key] === filterValue;\n });\n}\n\nfunction filterCrudData<TItem extends object>(\n data: readonly TItem[],\n search: string,\n filters: Record<string, string>,\n searchFields: readonly Extract<keyof TItem, string>[],\n): TItem[] {\n return data.filter((item) => {\n if (!itemMatchesFilters(item, filters)) {\n return false;\n }\n\n const record = item as Record<string, unknown>;\n const searchText = buildCrudSearchText(\n searchFields.map((field) => record[field]),\n );\n return matchesCrudSearch(searchText, search);\n });\n}\n\nexport function useCrudTableState<TItem extends object>({\n data,\n columns,\n searchFields = [],\n filters: filterDefinitions = [],\n pageSize = 20,\n initialSortState = null,\n}: UseCrudTableStateOptions<TItem>): CrudTableState<TItem> {\n const [search, setSearch] = useState(\"\");\n const [filters, setFilters] = useState<Record<string, string>>(() =>\n buildInitialFilters(filterDefinitions),\n );\n const [currentPage, setCurrentPage] = useState(1);\n\n const filteredData = useMemo(\n () => filterCrudData(data, search, filters, searchFields),\n [data, filters, search, searchFields],\n );\n const { sortedData, sortState, setSortState } = useTableSorting({\n data: filteredData,\n columns,\n initialSortState,\n });\n\n const totalPages = Math.max(1, Math.ceil(filteredData.length / pageSize));\n const validCurrentPage = getValidPage(currentPage, totalPages);\n const startIndex = (validCurrentPage - 1) * pageSize;\n const endIndex = Math.min(startIndex + pageSize, filteredData.length);\n const paginatedData = useMemo(\n () => paginateCrudData(sortedData, validCurrentPage, pageSize),\n [pageSize, sortedData, validCurrentPage],\n );\n\n const setFilter = useCallback((key: string, value: string) => {\n setFilters((currentFilters) => ({\n ...currentFilters,\n [key]: value,\n }));\n setCurrentPage(1);\n }, []);\n\n const updateSearch = useCallback((value: string) => {\n setSearch(value);\n setCurrentPage(1);\n }, []);\n\n const clearAll = useCallback(() => {\n setSearch(\"\");\n setFilters(buildInitialFilters(filterDefinitions));\n setCurrentPage(1);\n }, [filterDefinitions]);\n\n const getDraftFilterResultCount = useCallback(\n (draftValues: Record<string, string>) =>\n filterCrudData(\n data,\n search,\n {\n ...filters,\n ...draftValues,\n },\n searchFields,\n ).length,\n [data, filters, search, searchFields],\n );\n\n const hasSearch = search.trim().length > 0;\n const hasFilters = Object.values(filters).some(\n (value) => value && value !== \"all\",\n );\n\n return {\n filteredData,\n sortedData,\n paginatedData,\n search,\n setSearch: updateSearch,\n filters,\n setFilter,\n clearAll,\n getDraftFilterResultCount,\n hasSearch,\n hasFilters,\n emptyState: hasSearch || hasFilters ? \"no-results\" : \"initial\",\n sortState,\n setSortState,\n pagination: {\n currentPage: validCurrentPage,\n totalPages,\n totalItems: filteredData.length,\n pageSize,\n startIndex,\n endIndex,\n onPageChange: setCurrentPage,\n },\n };\n}\n","\"use client\";\n\nimport { parseAsString, useQueryStates } from \"nuqs\";\nimport { useCallback, useMemo } from \"react\";\n\nexport interface UrlStringFilterDefinition<TKey extends string = string> {\n readonly key: TKey;\n readonly param?: string;\n readonly defaultValue?: string;\n readonly clearValue?: string;\n}\n\ntype UrlStringFilterValues<TDefinitions extends readonly UrlStringFilterDefinition[]> = {\n readonly [TDefinition in TDefinitions[number] as TDefinition[\"key\"]]: string;\n};\n\nexport interface UrlStringFiltersState<\n TDefinitions extends readonly UrlStringFilterDefinition[],\n> {\n readonly values: UrlStringFilterValues<TDefinitions>;\n readonly setValue: (key: TDefinitions[number][\"key\"], value: string) => void;\n readonly clear: () => void;\n readonly getDraftValues: (\n draftValues: Record<string, string>,\n ) => UrlStringFilterValues<TDefinitions>;\n}\n\nfunction buildParserMap(definitions: readonly UrlStringFilterDefinition[]) {\n return Object.fromEntries(\n definitions.map((definition) => [\n definition.param ?? definition.key,\n parseAsString.withDefault(definition.defaultValue ?? \"all\"),\n ]),\n );\n}\n\nfunction normalizeFilterValue(\n value: string,\n definition: UrlStringFilterDefinition,\n): string | null {\n const clearValue = definition.clearValue ?? definition.defaultValue ?? \"all\";\n\n if (value === clearValue) {\n return null;\n }\n\n return value.length > 0 ? value : null;\n}\n\nexport function useUrlStringFilters<\n const TDefinitions extends readonly UrlStringFilterDefinition[],\n>(definitions: TDefinitions): UrlStringFiltersState<TDefinitions> {\n const parserMap = useMemo(() => buildParserMap(definitions), [definitions]);\n const [params, setParams] = useQueryStates(parserMap);\n\n const values = useMemo(\n () =>\n Object.fromEntries(\n definitions.map((definition) => {\n const param = definition.param ?? definition.key;\n return [definition.key, params[param] ?? definition.defaultValue ?? \"all\"];\n }),\n ) as UrlStringFilterValues<TDefinitions>,\n [definitions, params],\n );\n\n const setValue = useCallback(\n (key: TDefinitions[number][\"key\"], value: string) => {\n const definition = definitions.find((item) => item.key === key);\n\n if (!definition) {\n return;\n }\n\n void setParams({\n [definition.param ?? definition.key]: normalizeFilterValue(value, definition),\n });\n },\n [definitions, setParams],\n );\n\n const clear = useCallback(() => {\n void setParams(\n Object.fromEntries(\n definitions.map((definition) => [definition.param ?? definition.key, null]),\n ),\n );\n }, [definitions, setParams]);\n\n const getDraftValues = useCallback(\n (draftValues: Record<string, string>) =>\n ({\n ...values,\n ...draftValues,\n }) as UrlStringFilterValues<TDefinitions>,\n [values],\n );\n\n return {\n values,\n setValue,\n clear,\n getDraftValues,\n };\n}\n","\"use client\";\n\nimport { parseAsInteger, useQueryState } from \"nuqs\";\nimport { useCallback, useMemo } from \"react\";\n\nimport type { CrudPaginationState } from \"./pagination\";\n\nexport interface UseUrlPaginationOptions {\n readonly totalItems: number;\n readonly pageSize?: number;\n readonly pageParam?: string;\n}\n\nexport interface UrlPaginationState extends CrudPaginationState {\n readonly hasPrevPage: boolean;\n readonly hasNextPage: boolean;\n readonly goToPage: (page: number) => void;\n readonly nextPage: () => void;\n readonly prevPage: () => void;\n readonly firstPage: () => void;\n readonly lastPage: () => void;\n readonly paginate: <T>(data: readonly T[]) => T[];\n}\n\nexport function useUrlPagination({\n totalItems,\n pageSize = 20,\n pageParam = \"page\",\n}: UseUrlPaginationOptions): UrlPaginationState {\n const [page, setPage] = useQueryState(\n pageParam,\n parseAsInteger.withDefault(1),\n );\n\n const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));\n const currentPage = Math.min(Math.max(1, page), totalPages);\n const startIndex = (currentPage - 1) * pageSize;\n const endIndex = Math.min(startIndex + pageSize, totalItems);\n const hasPrevPage = currentPage > 1;\n const hasNextPage = currentPage < totalPages;\n\n const goToPage = useCallback(\n (newPage: number) => {\n const validPage = Math.min(Math.max(1, newPage), totalPages);\n void setPage(validPage === 1 ? null : validPage);\n },\n [setPage, totalPages],\n );\n\n const nextPage = useCallback(() => {\n if (hasNextPage) {\n goToPage(currentPage + 1);\n }\n }, [currentPage, goToPage, hasNextPage]);\n\n const prevPage = useCallback(() => {\n if (hasPrevPage) {\n goToPage(currentPage - 1);\n }\n }, [currentPage, goToPage, hasPrevPage]);\n\n const firstPage = useCallback(() => {\n goToPage(1);\n }, [goToPage]);\n\n const lastPage = useCallback(() => {\n goToPage(totalPages);\n }, [goToPage, totalPages]);\n\n const paginate = useCallback(\n <T,>(data: readonly T[]): T[] => data.slice(startIndex, endIndex),\n [endIndex, startIndex],\n );\n\n return useMemo(\n () => ({\n currentPage,\n pageSize,\n totalPages,\n totalItems,\n startIndex,\n endIndex,\n hasPrevPage,\n hasNextPage,\n goToPage,\n nextPage,\n prevPage,\n firstPage,\n lastPage,\n paginate,\n onPageChange: goToPage,\n }),\n [\n currentPage,\n endIndex,\n firstPage,\n goToPage,\n hasNextPage,\n hasPrevPage,\n lastPage,\n nextPage,\n pageSize,\n paginate,\n prevPage,\n startIndex,\n totalItems,\n totalPages,\n ],\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/crud-data-table.tsx","../src/crud-list-table.tsx","../src/crud-resource-sheet.tsx","../src/crud-table-view.tsx","../src/search.ts","../src/use-url-pagination.ts","../src/use-url-string-filters.ts","../src/use-crud-table-state.ts"],"sourcesContent":["\"use client\";\n\nimport { SmartTable } from \"@carefully-built/ui\";\n\nimport type {\n CrudDataTableProps,\n} from \"./types\";\n\nexport function CrudDataTable<TItem extends object>({\n actions,\n actionHandlers,\n columns,\n data,\n fullHeight = true,\n getRowKey,\n isLoading,\n maxHeight,\n noDataContent,\n noDataMessage,\n onRowClick,\n pagination,\n renderActions,\n renderMobileCard,\n sortState,\n stickyHeader = true,\n onSortChange,\n}: CrudDataTableProps<TItem>): React.ReactElement {\n return (\n <SmartTable\n data={[...data]}\n columns={[...columns]}\n isLoading={isLoading}\n actions={actions ? [...actions] : undefined}\n actionHandlers={actionHandlers}\n renderActions={renderActions}\n noDataMessage={noDataMessage}\n noDataContent={noDataContent}\n getRowKey={getRowKey}\n onRowClick={onRowClick}\n renderMobileCard={renderMobileCard}\n stickyHeader={stickyHeader}\n fullHeight={fullHeight}\n maxHeight={maxHeight}\n sortState={sortState}\n onSortChange={onSortChange}\n pagination={pagination}\n />\n );\n}\n","\"use client\";\n\nimport { CrudDataTable } from \"./crud-data-table\";\nimport type { CrudDataTableProps } from \"./types\";\n\nexport type CrudListTableProps<TItem extends object> = CrudDataTableProps<TItem>;\n\nexport function CrudListTable<TItem extends object>(\n props: CrudListTableProps<TItem>,\n): React.ReactElement {\n return <CrudDataTable {...props} />;\n}\n","\"use client\";\n\nimport type { ReactNode } from \"react\";\n\nimport { ResponsiveSheet } from \"@carefully-built/ui\";\nimport type { ResponsiveSheetClassNames, SheetOutsideInteractionGuard } from \"@carefully-built/ui\";\n\nexport interface CrudResourceSheetProps {\n readonly open: boolean;\n readonly onOpenChange: (open: boolean) => void;\n readonly title: ReactNode;\n readonly children: ReactNode;\n readonly formId?: string;\n readonly description?: ReactNode;\n readonly onCancel?: () => void;\n readonly cancelLabel?: ReactNode;\n readonly onConfirm?: () => void;\n readonly confirmLabel?: ReactNode;\n readonly confirmDisabled?: boolean;\n readonly confirmLoading?: boolean;\n readonly confirmCloseWhenDirty?: boolean;\n readonly width?: number;\n readonly outsideInteractionGuard?: SheetOutsideInteractionGuard;\n readonly className?: string;\n readonly contentClassName?: string;\n readonly footerClassName?: string;\n readonly classes?: ResponsiveSheetClassNames;\n}\n\nexport function CrudResourceSheet({\n children,\n formId,\n onConfirm,\n confirmCloseWhenDirty: _confirmCloseWhenDirty,\n outsideInteractionGuard,\n ...sheetProps\n}: CrudResourceSheetProps): React.ReactElement {\n const submitForm = (): void => {\n const form = formId ? document.getElementById(formId) : null;\n\n if (form instanceof HTMLFormElement) {\n form.requestSubmit();\n return;\n }\n\n onConfirm?.();\n };\n\n return (\n <ResponsiveSheet\n {...sheetProps}\n outsideInteractionGuard={outsideInteractionGuard}\n onConfirm={formId || onConfirm ? submitForm : undefined}\n >\n {children}\n </ResponsiveSheet>\n );\n}\n","\"use client\";\n\nimport type { CrudTableViewProps } from \"./types\";\n\nimport { SmartTable, TableToolbar } from \"@carefully-built/ui\";\n\nexport function CrudTableView<TItem extends object>({\n state,\n columns,\n isLoading,\n searchPlaceholder = \"Search...\",\n filters = [],\n actions,\n actionHandlers,\n renderActions,\n noDataMessage,\n initialEmptyContent,\n noResultsContent,\n getRowKey,\n onRowClick,\n renderMobileCard,\n stickyHeader = true,\n fullHeight = true,\n maxHeight,\n}: CrudTableViewProps<TItem>): React.ReactElement {\n const emptyContent =\n state.emptyState === \"no-results\" ? noResultsContent : initialEmptyContent;\n\n return (\n <>\n <div className=\"shrink-0\">\n <TableToolbar\n search={{\n value: state.search,\n onChange: state.setSearch,\n placeholder: searchPlaceholder,\n }}\n filters={filters.map((filter) => ({\n config: filter.config,\n value: state.filters[filter.key] ?? \"all\",\n onChange: (value) => {\n state.setFilter(filter.key, value);\n },\n allowAll: filter.allowAll,\n allOptionLabel: filter.allOptionLabel,\n clearable: filter.clearable,\n }))}\n onClearAll={state.clearAll}\n getDraftResultCount={state.getDraftFilterResultCount}\n />\n </div>\n\n <SmartTable\n data={state.paginatedData}\n columns={[...columns]}\n isLoading={isLoading}\n actions={actions ? [...actions] : undefined}\n actionHandlers={actionHandlers}\n renderActions={renderActions}\n noDataMessage={noDataMessage}\n noDataContent={emptyContent}\n getRowKey={getRowKey}\n onRowClick={onRowClick}\n renderMobileCard={renderMobileCard}\n stickyHeader={stickyHeader}\n fullHeight={fullHeight}\n maxHeight={maxHeight}\n sortState={state.sortState}\n onSortChange={state.setSortState}\n pagination={state.pagination}\n />\n </>\n );\n}\n","function normalizeSearchText(value: string): string {\n return value\n .toLocaleLowerCase()\n .normalize(\"NFD\")\n .replace(/\\p{Diacritic}/gu, \"\")\n .trim();\n}\n\nexport function buildCrudSearchText(values: readonly unknown[]): string {\n return normalizeSearchText(\n values\n .filter(\n (value): value is string | number =>\n typeof value === \"string\" || typeof value === \"number\",\n )\n .map(String)\n .join(\" \"),\n );\n}\n\nexport function matchesCrudSearch(searchText: string, query: string): boolean {\n const normalizedQuery = normalizeSearchText(query);\n\n if (!normalizedQuery) {\n return true;\n }\n\n return normalizedQuery\n .split(/\\s+/)\n .every((token) => searchText.includes(token));\n}\n","\"use client\";\n\nimport { parseAsInteger, useQueryState } from \"nuqs\";\nimport { useCallback, useMemo } from \"react\";\n\nimport type { CrudPaginationState } from \"./pagination\";\n\nexport interface UseUrlPaginationOptions {\n readonly totalItems: number;\n readonly pageSize?: number;\n readonly pageParam?: string;\n}\n\nexport interface UrlPaginationState extends CrudPaginationState {\n readonly hasPrevPage: boolean;\n readonly hasNextPage: boolean;\n readonly goToPage: (page: number) => void;\n readonly nextPage: () => void;\n readonly prevPage: () => void;\n readonly firstPage: () => void;\n readonly lastPage: () => void;\n readonly paginate: <T>(data: readonly T[]) => T[];\n}\n\nexport function useUrlPagination({\n totalItems,\n pageSize = 20,\n pageParam = \"page\",\n}: UseUrlPaginationOptions): UrlPaginationState {\n const [page, setPage] = useQueryState(\n pageParam,\n parseAsInteger.withDefault(1),\n );\n\n const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));\n const currentPage = Math.min(Math.max(1, page), totalPages);\n const startIndex = (currentPage - 1) * pageSize;\n const endIndex = Math.min(startIndex + pageSize, totalItems);\n const hasPrevPage = currentPage > 1;\n const hasNextPage = currentPage < totalPages;\n\n const goToPage = useCallback(\n (newPage: number) => {\n const validPage = Math.min(Math.max(1, newPage), totalPages);\n void setPage(validPage === 1 ? null : validPage);\n },\n [setPage, totalPages],\n );\n\n const nextPage = useCallback(() => {\n if (hasNextPage) {\n goToPage(currentPage + 1);\n }\n }, [currentPage, goToPage, hasNextPage]);\n\n const prevPage = useCallback(() => {\n if (hasPrevPage) {\n goToPage(currentPage - 1);\n }\n }, [currentPage, goToPage, hasPrevPage]);\n\n const firstPage = useCallback(() => {\n goToPage(1);\n }, [goToPage]);\n\n const lastPage = useCallback(() => {\n goToPage(totalPages);\n }, [goToPage, totalPages]);\n\n const paginate = useCallback(\n <T,>(data: readonly T[]): T[] => data.slice(startIndex, endIndex),\n [endIndex, startIndex],\n );\n\n return useMemo(\n () => ({\n currentPage,\n pageSize,\n totalPages,\n totalItems,\n startIndex,\n endIndex,\n hasPrevPage,\n hasNextPage,\n goToPage,\n nextPage,\n prevPage,\n firstPage,\n lastPage,\n paginate,\n onPageChange: goToPage,\n }),\n [\n currentPage,\n endIndex,\n firstPage,\n goToPage,\n hasNextPage,\n hasPrevPage,\n lastPage,\n nextPage,\n pageSize,\n paginate,\n prevPage,\n startIndex,\n totalItems,\n totalPages,\n ],\n );\n}\n","\"use client\";\n\nimport { parseAsString, useQueryStates } from \"nuqs\";\nimport { useCallback, useMemo } from \"react\";\n\nexport interface UrlStringFilterDefinition<TKey extends string = string> {\n readonly key: TKey;\n readonly param?: string;\n readonly defaultValue?: string;\n readonly clearValue?: string;\n}\n\ntype UrlStringFilterValues<TDefinitions extends readonly UrlStringFilterDefinition[]> = {\n readonly [TDefinition in TDefinitions[number] as TDefinition[\"key\"]]: string;\n};\n\nexport interface UrlStringFiltersState<\n TDefinitions extends readonly UrlStringFilterDefinition[],\n> {\n readonly values: UrlStringFilterValues<TDefinitions>;\n readonly setValue: (key: TDefinitions[number][\"key\"], value: string) => void;\n readonly clear: () => void;\n readonly getDraftValues: (\n draftValues: Record<string, string>,\n ) => UrlStringFilterValues<TDefinitions>;\n}\n\nfunction buildParserMap(definitions: readonly UrlStringFilterDefinition[]) {\n return Object.fromEntries(\n definitions.map((definition) => [\n definition.param ?? definition.key,\n parseAsString.withDefault(definition.defaultValue ?? \"all\"),\n ]),\n );\n}\n\nfunction normalizeFilterValue(\n value: string,\n definition: UrlStringFilterDefinition,\n): string | null {\n const clearValue = definition.clearValue ?? definition.defaultValue ?? \"all\";\n\n if (value === clearValue) {\n return null;\n }\n\n return value.length > 0 ? value : null;\n}\n\nexport function useUrlStringFilters<\n const TDefinitions extends readonly UrlStringFilterDefinition[],\n>(definitions: TDefinitions): UrlStringFiltersState<TDefinitions> {\n const parserMap = useMemo(() => buildParserMap(definitions), [definitions]);\n const [params, setParams] = useQueryStates(parserMap);\n\n const values = useMemo(\n () =>\n Object.fromEntries(\n definitions.map((definition) => {\n const param = definition.param ?? definition.key;\n return [definition.key, params[param] ?? definition.defaultValue ?? \"all\"];\n }),\n ) as UrlStringFilterValues<TDefinitions>,\n [definitions, params],\n );\n\n const setValue = useCallback(\n (key: TDefinitions[number][\"key\"], value: string) => {\n const definition = definitions.find((item) => item.key === key);\n\n if (!definition) {\n return;\n }\n\n void setParams({\n [definition.param ?? definition.key]: normalizeFilterValue(value, definition),\n });\n },\n [definitions, setParams],\n );\n\n const clear = useCallback(() => {\n void setParams(\n Object.fromEntries(\n definitions.map((definition) => [definition.param ?? definition.key, null]),\n ),\n );\n }, [definitions, setParams]);\n\n const getDraftValues = useCallback(\n (draftValues: Record<string, string>) =>\n ({\n ...values,\n ...draftValues,\n }) as UrlStringFilterValues<TDefinitions>,\n [values],\n );\n\n return {\n values,\n setValue,\n clear,\n getDraftValues,\n };\n}\n","\"use client\";\n\nimport { useCallback, useMemo } from \"react\";\n\nimport type { UseCrudTableStateOptions, CrudTableState } from \"./types\";\n\nimport { useTableSorting } from \"@carefully-built/ui\";\n\nimport { buildCrudSearchText, matchesCrudSearch } from \"./search\";\nimport { useUrlPagination } from \"./use-url-pagination\";\nimport { useUrlStringFilters, type UrlStringFilterDefinition } from \"./use-url-string-filters\";\n\nfunction buildInitialFilters<TItem>(\n filters: readonly { readonly key: Extract<keyof TItem, string> }[],\n): Record<string, string> {\n return Object.fromEntries(filters.map((filter) => [filter.key, \"all\"]));\n}\n\nfunction itemMatchesFilters<TItem extends object>(\n item: TItem,\n filters: Record<string, string>,\n): boolean {\n const record = item as Record<string, unknown>;\n\n return Object.entries(filters).every(([key, filterValue]) => {\n if (!filterValue || filterValue === \"all\") {\n return true;\n }\n\n return record[key] === filterValue;\n });\n}\n\nfunction filterCrudData<TItem extends object>(\n data: readonly TItem[],\n search: string,\n filters: Record<string, string>,\n searchFields: readonly Extract<keyof TItem, string>[],\n): TItem[] {\n return data.filter((item) => {\n if (!itemMatchesFilters(item, filters)) {\n return false;\n }\n\n const record = item as Record<string, unknown>;\n const searchText = buildCrudSearchText(\n searchFields.map((field) => record[field]),\n );\n return matchesCrudSearch(searchText, search);\n });\n}\n\nexport function useCrudTableState<TItem extends object>({\n data,\n columns,\n searchFields = [],\n filters: filterDefinitions = [],\n pageSize = 20,\n initialSortState = null,\n}: UseCrudTableStateOptions<TItem>): CrudTableState<TItem> {\n const urlFilterDefinitions = useMemo(\n () =>\n [\n { key: \"search\", defaultValue: \"\", clearValue: \"\" },\n ...filterDefinitions.map((filter) => ({\n key: filter.key,\n defaultValue: \"all\",\n clearValue: \"all\",\n })),\n ] satisfies readonly UrlStringFilterDefinition[],\n [filterDefinitions],\n );\n const urlFilters = useUrlStringFilters(urlFilterDefinitions);\n const search = urlFilters.values.search ?? \"\";\n const filters = useMemo(\n () => ({\n ...buildInitialFilters(filterDefinitions),\n ...Object.fromEntries(\n filterDefinitions.map((filter) => [filter.key, urlFilters.values[filter.key] ?? \"all\"]),\n ),\n }),\n [filterDefinitions, urlFilters.values],\n );\n\n const filteredData = useMemo(\n () => filterCrudData(data, search, filters, searchFields),\n [data, filters, search, searchFields],\n );\n const pagination = useUrlPagination({\n totalItems: filteredData.length,\n pageSize,\n });\n const setCurrentPage = pagination.onPageChange;\n const { sortedData, sortState, setSortState } = useTableSorting({\n data: filteredData,\n columns,\n initialSortState,\n });\n\n const paginatedData = useMemo(\n () => pagination.paginate(sortedData),\n [pagination, sortedData],\n );\n\n const setFilter = useCallback((key: string, value: string) => {\n urlFilters.setValue(key, value);\n void setCurrentPage(1);\n }, [setCurrentPage, urlFilters]);\n\n const updateSearch = useCallback((value: string) => {\n urlFilters.setValue(\"search\", value);\n void setCurrentPage(1);\n }, [setCurrentPage, urlFilters]);\n\n const clearAll = useCallback(() => {\n urlFilters.clear();\n void setCurrentPage(1);\n }, [setCurrentPage, urlFilters]);\n\n const getDraftFilterResultCount = useCallback(\n (draftValues: Record<string, string>) =>\n filterCrudData(\n data,\n search,\n {\n ...filters,\n ...draftValues,\n },\n searchFields,\n ).length,\n [data, filters, search, searchFields],\n );\n\n const hasSearch = search.trim().length > 0;\n const hasFilters = Object.values(filters).some(\n (value) => value && value !== \"all\",\n );\n\n return {\n filteredData,\n sortedData,\n paginatedData,\n search,\n setSearch: updateSearch,\n filters,\n setFilter,\n clearAll,\n getDraftFilterResultCount,\n hasSearch,\n hasFilters,\n emptyState: hasSearch || hasFilters ? \"no-results\" : \"initial\",\n sortState,\n setSortState,\n pagination,\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carefully-built/crud",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Config-driven CRUD table and form helpers for Carefully Built SaaS apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"access": "public"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"@carefully-built/ui": ">=0.1.
|
|
48
|
+
"@carefully-built/ui": ">=0.1.15",
|
|
49
49
|
"nuqs": ">=2.0.0",
|
|
50
50
|
"react": ">=18.2.0 || >=19.0.0",
|
|
51
51
|
"react-dom": ">=18.2.0 || >=19.0.0"
|