@carefully-built/crud 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -0
- package/dist/index.d.mts +211 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +322 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Carefully Built CRUD
|
|
2
|
+
|
|
3
|
+
Config-driven CRUD table helpers for Carefully Built SaaS apps.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @carefully-built/crud @carefully-built/ui
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## What It Includes
|
|
12
|
+
|
|
13
|
+
- `useCrudTableState`: search, select filters, sorting, pagination, and empty-state derivation for table CRUD screens.
|
|
14
|
+
- `CrudTableView`: shared toolbar plus `SmartTable` rendering with consistent empty-state and pagination wiring.
|
|
15
|
+
- `CrudListTable`: direct CRUD table wrapper for pages that already own their toolbar/filter layout.
|
|
16
|
+
- `CrudResourceSheet`: responsive create/edit sheet for resource forms, with confirm/cancel footer wiring.
|
|
17
|
+
- CRUD types for resource configs, filters, and empty-state values.
|
|
18
|
+
|
|
19
|
+
## Basic Usage
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
const table = useCrudTableState({
|
|
23
|
+
data: items,
|
|
24
|
+
columns,
|
|
25
|
+
searchFields: ["name", "description"],
|
|
26
|
+
filters: [
|
|
27
|
+
{ key: "status", config: STATUS_FILTER },
|
|
28
|
+
{ key: "priority", config: PRIORITY_FILTER },
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
<CrudTableView
|
|
33
|
+
state={table}
|
|
34
|
+
columns={columns}
|
|
35
|
+
isLoading={isLoading}
|
|
36
|
+
searchPlaceholder="Search..."
|
|
37
|
+
actions={["edit", "delete"]}
|
|
38
|
+
actionHandlers={actionHandlers}
|
|
39
|
+
/>;
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Keep domain actions, mutations, and labels inside the consuming app. This package owns the repeated table mechanics.
|
|
43
|
+
|
|
44
|
+
## Sheet Usage
|
|
45
|
+
|
|
46
|
+
Use `CrudResourceSheet` for create/edit forms instead of wiring `ResponsiveSheet` repeatedly in each app.
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
<CrudResourceSheet
|
|
50
|
+
open={isOpen}
|
|
51
|
+
onOpenChange={setIsOpen}
|
|
52
|
+
title="Edit contact"
|
|
53
|
+
description="Update the contact details."
|
|
54
|
+
formId="contact-form"
|
|
55
|
+
confirmLabel="Save"
|
|
56
|
+
confirmLoading={isSaving}
|
|
57
|
+
classes={{
|
|
58
|
+
body: 'gap-4',
|
|
59
|
+
footer: 'border-t',
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
<ContactForm id="contact-form" />
|
|
63
|
+
</CrudResourceSheet>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`className`, `contentClassName`, `footerClassName`, and `classes` are optional. Omit them to keep the default kit style.
|
|
67
|
+
|
|
68
|
+
## Component Docs
|
|
69
|
+
|
|
70
|
+
- [CRUD Table](./docs/crud-table.md)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { ActionHandlers, ActionType, Column, FilterConfig, ResponsiveSheetClassNames, SortState } from "@carefully-built/ui";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
|
|
4
|
+
//#region src/pagination.d.ts
|
|
5
|
+
interface CrudPaginationState {
|
|
6
|
+
readonly currentPage: number;
|
|
7
|
+
readonly totalPages: number;
|
|
8
|
+
readonly totalItems: number;
|
|
9
|
+
readonly pageSize: number;
|
|
10
|
+
readonly startIndex: number;
|
|
11
|
+
readonly endIndex: number;
|
|
12
|
+
readonly onPageChange: (page: number) => void;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/types.d.ts
|
|
16
|
+
type CrudEmptyState = "initial" | "no-results";
|
|
17
|
+
interface CrudFilterDefinition<TItem> {
|
|
18
|
+
readonly key: Extract<keyof TItem, string>;
|
|
19
|
+
readonly config: FilterConfig;
|
|
20
|
+
readonly allowAll?: boolean;
|
|
21
|
+
readonly clearable?: boolean;
|
|
22
|
+
}
|
|
23
|
+
interface UseCrudTableStateOptions<TItem> {
|
|
24
|
+
readonly data: readonly TItem[];
|
|
25
|
+
readonly columns: readonly Column<TItem>[];
|
|
26
|
+
readonly searchFields?: readonly Extract<keyof TItem, string>[];
|
|
27
|
+
readonly filters?: readonly CrudFilterDefinition<TItem>[];
|
|
28
|
+
readonly pageSize?: number;
|
|
29
|
+
readonly initialSortState?: SortState;
|
|
30
|
+
}
|
|
31
|
+
interface CrudTableState<TItem> {
|
|
32
|
+
readonly filteredData: TItem[];
|
|
33
|
+
readonly sortedData: TItem[];
|
|
34
|
+
readonly paginatedData: TItem[];
|
|
35
|
+
readonly search: string;
|
|
36
|
+
readonly setSearch: (value: string) => void;
|
|
37
|
+
readonly filters: Record<string, string>;
|
|
38
|
+
readonly setFilter: (key: string, value: string) => void;
|
|
39
|
+
readonly clearAll: () => void;
|
|
40
|
+
readonly getDraftFilterResultCount: (draftValues: Record<string, string>) => number;
|
|
41
|
+
readonly hasSearch: boolean;
|
|
42
|
+
readonly hasFilters: boolean;
|
|
43
|
+
readonly emptyState: CrudEmptyState;
|
|
44
|
+
readonly sortState: SortState;
|
|
45
|
+
readonly setSortState: (state: SortState) => void;
|
|
46
|
+
readonly pagination: CrudPaginationState;
|
|
47
|
+
}
|
|
48
|
+
interface CrudTableViewProps<TItem> {
|
|
49
|
+
readonly state: CrudTableState<TItem>;
|
|
50
|
+
readonly columns: readonly Column<TItem>[];
|
|
51
|
+
readonly isLoading: boolean;
|
|
52
|
+
readonly searchPlaceholder?: string;
|
|
53
|
+
readonly filters?: readonly CrudFilterDefinition<TItem>[];
|
|
54
|
+
readonly actions?: readonly ActionType[];
|
|
55
|
+
readonly actionHandlers?: ActionHandlers<TItem>;
|
|
56
|
+
readonly renderActions?: (item: TItem) => ReactNode;
|
|
57
|
+
readonly noDataMessage?: string;
|
|
58
|
+
readonly initialEmptyContent?: ReactNode;
|
|
59
|
+
readonly noResultsContent?: ReactNode;
|
|
60
|
+
readonly getRowKey?: (item: TItem) => string | number;
|
|
61
|
+
readonly onRowClick?: (item: TItem) => void;
|
|
62
|
+
readonly renderMobileCard?: (item: TItem) => ReactNode;
|
|
63
|
+
readonly stickyHeader?: boolean;
|
|
64
|
+
readonly fullHeight?: boolean;
|
|
65
|
+
readonly maxHeight?: string;
|
|
66
|
+
}
|
|
67
|
+
interface CrudDataTableProps<TItem> {
|
|
68
|
+
readonly data: readonly TItem[];
|
|
69
|
+
readonly columns: readonly Column<TItem>[];
|
|
70
|
+
readonly isLoading: boolean;
|
|
71
|
+
readonly actions?: readonly ActionType[];
|
|
72
|
+
readonly actionHandlers?: ActionHandlers<TItem>;
|
|
73
|
+
readonly renderActions?: (item: TItem) => ReactNode;
|
|
74
|
+
readonly noDataMessage?: string;
|
|
75
|
+
readonly noDataContent?: ReactNode;
|
|
76
|
+
readonly getRowKey?: (item: TItem) => string | number;
|
|
77
|
+
readonly onRowClick?: (item: TItem) => void;
|
|
78
|
+
readonly renderMobileCard?: (item: TItem) => ReactNode;
|
|
79
|
+
readonly stickyHeader?: boolean;
|
|
80
|
+
readonly fullHeight?: boolean;
|
|
81
|
+
readonly maxHeight?: string;
|
|
82
|
+
readonly sortState?: SortState;
|
|
83
|
+
readonly onSortChange?: (state: SortState) => void;
|
|
84
|
+
readonly pagination?: CrudPaginationState;
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/crud-data-table.d.ts
|
|
88
|
+
declare function CrudDataTable<TItem extends object>({
|
|
89
|
+
actions,
|
|
90
|
+
actionHandlers,
|
|
91
|
+
columns,
|
|
92
|
+
data,
|
|
93
|
+
fullHeight,
|
|
94
|
+
getRowKey,
|
|
95
|
+
isLoading,
|
|
96
|
+
maxHeight,
|
|
97
|
+
noDataContent,
|
|
98
|
+
noDataMessage,
|
|
99
|
+
onRowClick,
|
|
100
|
+
pagination,
|
|
101
|
+
renderActions,
|
|
102
|
+
renderMobileCard,
|
|
103
|
+
sortState,
|
|
104
|
+
stickyHeader,
|
|
105
|
+
onSortChange
|
|
106
|
+
}: CrudDataTableProps<TItem>): React.ReactElement;
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/crud-list-table.d.ts
|
|
109
|
+
type CrudListTableProps<TItem extends object> = CrudDataTableProps<TItem>;
|
|
110
|
+
declare function CrudListTable<TItem extends object>(props: CrudListTableProps<TItem>): React.ReactElement;
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region src/crud-resource-sheet.d.ts
|
|
113
|
+
interface CrudResourceSheetProps {
|
|
114
|
+
readonly open: boolean;
|
|
115
|
+
readonly onOpenChange: (open: boolean) => void;
|
|
116
|
+
readonly title: ReactNode;
|
|
117
|
+
readonly children: ReactNode;
|
|
118
|
+
readonly formId?: string;
|
|
119
|
+
readonly description?: ReactNode;
|
|
120
|
+
readonly onCancel?: () => void;
|
|
121
|
+
readonly cancelLabel?: ReactNode;
|
|
122
|
+
readonly onConfirm?: () => void;
|
|
123
|
+
readonly confirmLabel?: ReactNode;
|
|
124
|
+
readonly confirmDisabled?: boolean;
|
|
125
|
+
readonly confirmLoading?: boolean;
|
|
126
|
+
readonly confirmCloseWhenDirty?: boolean;
|
|
127
|
+
readonly width?: number;
|
|
128
|
+
readonly className?: string;
|
|
129
|
+
readonly contentClassName?: string;
|
|
130
|
+
readonly footerClassName?: string;
|
|
131
|
+
readonly classes?: ResponsiveSheetClassNames;
|
|
132
|
+
}
|
|
133
|
+
declare function CrudResourceSheet({
|
|
134
|
+
children,
|
|
135
|
+
formId,
|
|
136
|
+
onConfirm,
|
|
137
|
+
confirmCloseWhenDirty: _confirmCloseWhenDirty,
|
|
138
|
+
...sheetProps
|
|
139
|
+
}: CrudResourceSheetProps): React.ReactElement;
|
|
140
|
+
//#endregion
|
|
141
|
+
//#region src/crud-table-view.d.ts
|
|
142
|
+
declare function CrudTableView<TItem extends object>({
|
|
143
|
+
state,
|
|
144
|
+
columns,
|
|
145
|
+
isLoading,
|
|
146
|
+
searchPlaceholder,
|
|
147
|
+
filters,
|
|
148
|
+
actions,
|
|
149
|
+
actionHandlers,
|
|
150
|
+
renderActions,
|
|
151
|
+
noDataMessage,
|
|
152
|
+
initialEmptyContent,
|
|
153
|
+
noResultsContent,
|
|
154
|
+
getRowKey,
|
|
155
|
+
onRowClick,
|
|
156
|
+
renderMobileCard,
|
|
157
|
+
stickyHeader,
|
|
158
|
+
fullHeight,
|
|
159
|
+
maxHeight
|
|
160
|
+
}: CrudTableViewProps<TItem>): React.ReactElement;
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/use-crud-table-state.d.ts
|
|
163
|
+
declare function useCrudTableState<TItem extends object>({
|
|
164
|
+
data,
|
|
165
|
+
columns,
|
|
166
|
+
searchFields,
|
|
167
|
+
filters: filterDefinitions,
|
|
168
|
+
pageSize,
|
|
169
|
+
initialSortState
|
|
170
|
+
}: UseCrudTableStateOptions<TItem>): CrudTableState<TItem>;
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/use-url-string-filters.d.ts
|
|
173
|
+
interface UrlStringFilterDefinition<TKey extends string = string> {
|
|
174
|
+
readonly key: TKey;
|
|
175
|
+
readonly param?: string;
|
|
176
|
+
readonly defaultValue?: string;
|
|
177
|
+
readonly clearValue?: string;
|
|
178
|
+
}
|
|
179
|
+
type UrlStringFilterValues<TDefinitions extends readonly UrlStringFilterDefinition[]> = { readonly [TDefinition in TDefinitions[number] as TDefinition["key"]]: string };
|
|
180
|
+
interface UrlStringFiltersState<TDefinitions extends readonly UrlStringFilterDefinition[]> {
|
|
181
|
+
readonly values: UrlStringFilterValues<TDefinitions>;
|
|
182
|
+
readonly setValue: (key: TDefinitions[number]["key"], value: string) => void;
|
|
183
|
+
readonly clear: () => void;
|
|
184
|
+
readonly getDraftValues: (draftValues: Record<string, string>) => UrlStringFilterValues<TDefinitions>;
|
|
185
|
+
}
|
|
186
|
+
declare function useUrlStringFilters<const TDefinitions extends readonly UrlStringFilterDefinition[]>(definitions: TDefinitions): UrlStringFiltersState<TDefinitions>;
|
|
187
|
+
//#endregion
|
|
188
|
+
//#region src/use-url-pagination.d.ts
|
|
189
|
+
interface UseUrlPaginationOptions {
|
|
190
|
+
readonly totalItems: number;
|
|
191
|
+
readonly pageSize?: number;
|
|
192
|
+
readonly pageParam?: string;
|
|
193
|
+
}
|
|
194
|
+
interface UrlPaginationState extends CrudPaginationState {
|
|
195
|
+
readonly hasPrevPage: boolean;
|
|
196
|
+
readonly hasNextPage: boolean;
|
|
197
|
+
readonly goToPage: (page: number) => void;
|
|
198
|
+
readonly nextPage: () => void;
|
|
199
|
+
readonly prevPage: () => void;
|
|
200
|
+
readonly firstPage: () => void;
|
|
201
|
+
readonly lastPage: () => void;
|
|
202
|
+
readonly paginate: <T>(data: readonly T[]) => T[];
|
|
203
|
+
}
|
|
204
|
+
declare function useUrlPagination({
|
|
205
|
+
totalItems,
|
|
206
|
+
pageSize,
|
|
207
|
+
pageParam
|
|
208
|
+
}: UseUrlPaginationOptions): UrlPaginationState;
|
|
209
|
+
//#endregion
|
|
210
|
+
export { CrudDataTable, type CrudDataTableProps, type CrudEmptyState, type CrudFilterDefinition, CrudListTable, type CrudListTableProps, type CrudPaginationState, CrudResourceSheet, type CrudResourceSheetProps, type CrudTableState, CrudTableView, type CrudTableViewProps, type UrlPaginationState, type UrlStringFilterDefinition, type UrlStringFiltersState, type UseCrudTableStateOptions, type UseUrlPaginationOptions, useCrudTableState, useUrlPagination, useUrlStringFilters };
|
|
211
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +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":"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"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { ResponsiveSheet, SmartTable, TableToolbar, useTableSorting } from "@carefully-built/ui";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useMemo, useState } from "react";
|
|
4
|
+
import { parseAsInteger, parseAsString, useQueryState, useQueryStates } from "nuqs";
|
|
5
|
+
|
|
6
|
+
//#region src/crud-data-table.tsx
|
|
7
|
+
function CrudDataTable({ actions, actionHandlers, columns, data, fullHeight = true, getRowKey, isLoading, maxHeight, noDataContent, noDataMessage, onRowClick, pagination, renderActions, renderMobileCard, sortState, stickyHeader = true, onSortChange }) {
|
|
8
|
+
return /* @__PURE__ */ jsx(SmartTable, {
|
|
9
|
+
data: [...data],
|
|
10
|
+
columns: [...columns],
|
|
11
|
+
isLoading,
|
|
12
|
+
actions: actions ? [...actions] : void 0,
|
|
13
|
+
actionHandlers,
|
|
14
|
+
renderActions,
|
|
15
|
+
noDataMessage,
|
|
16
|
+
noDataContent,
|
|
17
|
+
getRowKey,
|
|
18
|
+
onRowClick,
|
|
19
|
+
renderMobileCard,
|
|
20
|
+
stickyHeader,
|
|
21
|
+
fullHeight,
|
|
22
|
+
maxHeight,
|
|
23
|
+
sortState,
|
|
24
|
+
onSortChange,
|
|
25
|
+
pagination
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/crud-list-table.tsx
|
|
31
|
+
function CrudListTable(props) {
|
|
32
|
+
return /* @__PURE__ */ jsx(CrudDataTable, { ...props });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/crud-resource-sheet.tsx
|
|
37
|
+
function CrudResourceSheet({ children, formId, onConfirm, confirmCloseWhenDirty: _confirmCloseWhenDirty, ...sheetProps }) {
|
|
38
|
+
const submitForm = () => {
|
|
39
|
+
const form = formId ? document.getElementById(formId) : null;
|
|
40
|
+
if (form instanceof HTMLFormElement) {
|
|
41
|
+
form.requestSubmit();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
onConfirm?.();
|
|
45
|
+
};
|
|
46
|
+
return /* @__PURE__ */ jsx(ResponsiveSheet, {
|
|
47
|
+
...sheetProps,
|
|
48
|
+
onConfirm: formId || onConfirm ? submitForm : void 0,
|
|
49
|
+
children
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/crud-table-view.tsx
|
|
55
|
+
function CrudTableView({ state, columns, isLoading, searchPlaceholder = "Search...", filters = [], actions, actionHandlers, renderActions, noDataMessage, initialEmptyContent, noResultsContent, getRowKey, onRowClick, renderMobileCard, stickyHeader = true, fullHeight = true, maxHeight }) {
|
|
56
|
+
const emptyContent = state.emptyState === "no-results" ? noResultsContent : initialEmptyContent;
|
|
57
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
58
|
+
className: "shrink-0",
|
|
59
|
+
children: /* @__PURE__ */ jsx(TableToolbar, {
|
|
60
|
+
search: {
|
|
61
|
+
value: state.search,
|
|
62
|
+
onChange: state.setSearch,
|
|
63
|
+
placeholder: searchPlaceholder
|
|
64
|
+
},
|
|
65
|
+
filters: filters.map((filter) => ({
|
|
66
|
+
config: filter.config,
|
|
67
|
+
value: state.filters[filter.key] ?? "all",
|
|
68
|
+
onChange: (value) => {
|
|
69
|
+
state.setFilter(filter.key, value);
|
|
70
|
+
},
|
|
71
|
+
allowAll: filter.allowAll,
|
|
72
|
+
clearable: filter.clearable
|
|
73
|
+
})),
|
|
74
|
+
onClearAll: state.clearAll,
|
|
75
|
+
getDraftResultCount: state.getDraftFilterResultCount
|
|
76
|
+
})
|
|
77
|
+
}), /* @__PURE__ */ jsx(SmartTable, {
|
|
78
|
+
data: state.paginatedData,
|
|
79
|
+
columns: [...columns],
|
|
80
|
+
isLoading,
|
|
81
|
+
actions: actions ? [...actions] : void 0,
|
|
82
|
+
actionHandlers,
|
|
83
|
+
renderActions,
|
|
84
|
+
noDataMessage,
|
|
85
|
+
noDataContent: emptyContent,
|
|
86
|
+
getRowKey,
|
|
87
|
+
onRowClick,
|
|
88
|
+
renderMobileCard,
|
|
89
|
+
stickyHeader,
|
|
90
|
+
fullHeight,
|
|
91
|
+
maxHeight,
|
|
92
|
+
sortState: state.sortState,
|
|
93
|
+
onSortChange: state.setSortState,
|
|
94
|
+
pagination: state.pagination
|
|
95
|
+
})] });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/search.ts
|
|
100
|
+
function normalizeSearchText(value) {
|
|
101
|
+
return value.toLocaleLowerCase().normalize("NFD").replace(/\p{Diacritic}/gu, "").trim();
|
|
102
|
+
}
|
|
103
|
+
function buildCrudSearchText(values) {
|
|
104
|
+
return normalizeSearchText(values.filter((value) => typeof value === "string" || typeof value === "number").map(String).join(" "));
|
|
105
|
+
}
|
|
106
|
+
function matchesCrudSearch(searchText, query) {
|
|
107
|
+
const normalizedQuery = normalizeSearchText(query);
|
|
108
|
+
if (!normalizedQuery) return true;
|
|
109
|
+
return normalizedQuery.split(/\s+/).every((token) => searchText.includes(token));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/pagination.ts
|
|
114
|
+
function getValidPage(page, totalPages) {
|
|
115
|
+
return Math.min(Math.max(1, page), totalPages);
|
|
116
|
+
}
|
|
117
|
+
function paginateCrudData(data, currentPage, pageSize) {
|
|
118
|
+
const startIndex = (currentPage - 1) * pageSize;
|
|
119
|
+
return data.slice(startIndex, startIndex + pageSize);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/use-crud-table-state.ts
|
|
124
|
+
function buildInitialFilters(filters) {
|
|
125
|
+
return Object.fromEntries(filters.map((filter) => [filter.key, "all"]));
|
|
126
|
+
}
|
|
127
|
+
function itemMatchesFilters(item, filters) {
|
|
128
|
+
const record = item;
|
|
129
|
+
return Object.entries(filters).every(([key, filterValue]) => {
|
|
130
|
+
if (!filterValue || filterValue === "all") return true;
|
|
131
|
+
return record[key] === filterValue;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function filterCrudData(data, search, filters, searchFields) {
|
|
135
|
+
return data.filter((item) => {
|
|
136
|
+
if (!itemMatchesFilters(item, filters)) return false;
|
|
137
|
+
const record = item;
|
|
138
|
+
return matchesCrudSearch(buildCrudSearchText(searchFields.map((field) => record[field])), search);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function useCrudTableState({ data, columns, searchFields = [], filters: filterDefinitions = [], pageSize = 20, initialSortState = null }) {
|
|
142
|
+
const [search, setSearch] = useState("");
|
|
143
|
+
const [filters, setFilters] = useState(() => buildInitialFilters(filterDefinitions));
|
|
144
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
145
|
+
const filteredData = useMemo(() => filterCrudData(data, search, filters, searchFields), [
|
|
146
|
+
data,
|
|
147
|
+
filters,
|
|
148
|
+
search,
|
|
149
|
+
searchFields
|
|
150
|
+
]);
|
|
151
|
+
const { sortedData, sortState, setSortState } = useTableSorting({
|
|
152
|
+
data: filteredData,
|
|
153
|
+
columns,
|
|
154
|
+
initialSortState
|
|
155
|
+
});
|
|
156
|
+
const totalPages = Math.max(1, Math.ceil(filteredData.length / pageSize));
|
|
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
|
+
]);
|
|
165
|
+
const setFilter = useCallback((key, value) => {
|
|
166
|
+
setFilters((currentFilters) => ({
|
|
167
|
+
...currentFilters,
|
|
168
|
+
[key]: value
|
|
169
|
+
}));
|
|
170
|
+
setCurrentPage(1);
|
|
171
|
+
}, []);
|
|
172
|
+
const updateSearch = useCallback((value) => {
|
|
173
|
+
setSearch(value);
|
|
174
|
+
setCurrentPage(1);
|
|
175
|
+
}, []);
|
|
176
|
+
const clearAll = useCallback(() => {
|
|
177
|
+
setSearch("");
|
|
178
|
+
setFilters(buildInitialFilters(filterDefinitions));
|
|
179
|
+
setCurrentPage(1);
|
|
180
|
+
}, [filterDefinitions]);
|
|
181
|
+
const getDraftFilterResultCount = useCallback((draftValues) => filterCrudData(data, search, {
|
|
182
|
+
...filters,
|
|
183
|
+
...draftValues
|
|
184
|
+
}, searchFields).length, [
|
|
185
|
+
data,
|
|
186
|
+
filters,
|
|
187
|
+
search,
|
|
188
|
+
searchFields
|
|
189
|
+
]);
|
|
190
|
+
const hasSearch = search.trim().length > 0;
|
|
191
|
+
const hasFilters = Object.values(filters).some((value) => value && value !== "all");
|
|
192
|
+
return {
|
|
193
|
+
filteredData,
|
|
194
|
+
sortedData,
|
|
195
|
+
paginatedData,
|
|
196
|
+
search,
|
|
197
|
+
setSearch: updateSearch,
|
|
198
|
+
filters,
|
|
199
|
+
setFilter,
|
|
200
|
+
clearAll,
|
|
201
|
+
getDraftFilterResultCount,
|
|
202
|
+
hasSearch,
|
|
203
|
+
hasFilters,
|
|
204
|
+
emptyState: hasSearch || hasFilters ? "no-results" : "initial",
|
|
205
|
+
sortState,
|
|
206
|
+
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])
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
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
|
+
//#endregion
|
|
321
|
+
export { CrudDataTable, CrudListTable, CrudResourceSheet, CrudTableView, useCrudTableState, useUrlPagination, useUrlStringFilters };
|
|
322
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +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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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@carefully-built/crud",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Config-driven CRUD table and form helpers for Carefully Built SaaS apps.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Alessandro Dodi",
|
|
8
|
+
"homepage": "https://github.com/AlessandroDodi/carefully-built-saas-kit#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/AlessandroDodi/carefully-built-saas-kit.git",
|
|
12
|
+
"directory": "packages/crud"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/AlessandroDodi/carefully-built-saas-kit/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"react",
|
|
19
|
+
"crud",
|
|
20
|
+
"table",
|
|
21
|
+
"saas",
|
|
22
|
+
"convex"
|
|
23
|
+
],
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"main": "./dist/index.mjs",
|
|
26
|
+
"module": "./dist/index.mjs",
|
|
27
|
+
"types": "./dist/index.d.mts",
|
|
28
|
+
"files": [
|
|
29
|
+
"dist"
|
|
30
|
+
],
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.mts",
|
|
34
|
+
"import": "./dist/index.mjs",
|
|
35
|
+
"default": "./dist/index.mjs"
|
|
36
|
+
},
|
|
37
|
+
"./package.json": "./package.json"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsdown src/index.ts --format esm --dts",
|
|
41
|
+
"prepublishOnly": "bun run typecheck && bun run build",
|
|
42
|
+
"typecheck": "tsc --noEmit"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"@carefully-built/ui": ">=0.1.4",
|
|
49
|
+
"nuqs": ">=2.0.0",
|
|
50
|
+
"react": ">=18.2.0 || >=19.0.0",
|
|
51
|
+
"react-dom": ">=18.2.0 || >=19.0.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependenciesMeta": {
|
|
54
|
+
"@carefully-built/ui": {
|
|
55
|
+
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"nuqs": {
|
|
58
|
+
"optional": true
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@carefully-built/ui": "workspace:*",
|
|
63
|
+
"nuqs": "^2.8.8",
|
|
64
|
+
"react": "^19.2.3",
|
|
65
|
+
"react-dom": "^19.2.3"
|
|
66
|
+
}
|
|
67
|
+
}
|