antd-crud-table 0.4.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +293 -783
- package/dist/CrudTable-Btf58zof.js +610 -0
- package/dist/CrudTable-DRN70sET.cjs +1 -0
- package/dist/CrudTable.cjs +1 -1
- package/dist/CrudTable.d.ts +85 -18
- package/dist/CrudTable.js +3 -306
- package/dist/CrudTableLazy.cjs +1 -1
- package/dist/CrudTableLazy.d.ts +4 -3
- package/dist/CrudTableLazy.js +8 -7
- package/dist/core/CustomDataSource.d.ts +35 -0
- package/dist/core/InMemoryDataSource.d.ts +30 -0
- package/dist/core/LocalStorageDataSource.d.ts +30 -0
- package/dist/core/RestDataSource.d.ts +120 -0
- package/dist/core/StaticDataSource.d.ts +20 -0
- package/dist/core/identity.d.ts +28 -0
- package/dist/core/inMemoryQuery.d.ts +26 -0
- package/dist/core/index.d.ts +11 -0
- package/dist/core/types.d.ts +86 -0
- package/dist/en_US-D3Kl6g3n.cjs +1 -0
- package/dist/en_US-DGCMDOdX.js +41 -0
- package/dist/exportData.cjs +13 -13
- package/dist/exportData.js +48 -39
- package/dist/fields/registry.d.ts +38 -0
- package/dist/fields/types.d.ts +29 -0
- package/dist/hooks/useCrudTable.d.ts +114 -57
- package/dist/hooks/useLocalStorageCrud.d.ts +14 -9
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +14 -6
- package/dist/index.js +7 -0
- package/dist/locale/en_US.d.ts +10 -0
- package/dist/locale/index.d.ts +4 -0
- package/dist/locale/types.d.ts +89 -0
- package/dist/locale/useResolvedLocale.d.ts +35 -0
- package/dist/useCrudTable-40jpmCkr.cjs +1 -0
- package/dist/useCrudTable-DxjnXVlQ.js +460 -0
- package/dist/useCrudTable.cjs +1 -1
- package/dist/useCrudTable.js +2 -235
- package/dist/useLocalStorageCrud.cjs +1 -1
- package/dist/useLocalStorageCrud.js +12 -198
- package/dist/utils/exportData.d.ts +37 -12
- package/package.json +52 -16
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { CrudDataSource, CrudDraft, CrudPage, CrudQuery, CrudSort } from './types';
|
|
2
|
+
/** Paths appended to `baseUrl` for each operation. */
|
|
3
|
+
export interface RestEndpoints {
|
|
4
|
+
/** Path listing records. */
|
|
5
|
+
list: string;
|
|
6
|
+
/** Path accepting a new record. */
|
|
7
|
+
create: string;
|
|
8
|
+
/** Path for updating one record; the id is appended by `buildRecordPath`. */
|
|
9
|
+
update: string;
|
|
10
|
+
/** Path for deleting one record; the id is appended by `buildRecordPath`. */
|
|
11
|
+
remove: string;
|
|
12
|
+
}
|
|
13
|
+
/** Query-string parameter names, for APIs that do not speak ProTable's vocabulary. */
|
|
14
|
+
export interface RestParamNames {
|
|
15
|
+
/** Parameter carrying the 1-based page number. Defaults to `current`. */
|
|
16
|
+
page: string;
|
|
17
|
+
/** Parameter carrying the page size. Defaults to `pageSize`. */
|
|
18
|
+
pageSize: string;
|
|
19
|
+
/** Parameter carrying the field to sort by. Defaults to `sortBy`. */
|
|
20
|
+
sortBy: string;
|
|
21
|
+
/** Parameter carrying the sort direction. Defaults to `sortOrder`. */
|
|
22
|
+
sortOrder: string;
|
|
23
|
+
}
|
|
24
|
+
/** HTTP verbs used per mutating operation. */
|
|
25
|
+
export interface RestMethods {
|
|
26
|
+
/** Verb for creating a record. Defaults to `POST`. */
|
|
27
|
+
create: 'POST' | 'PUT';
|
|
28
|
+
/** Verb for updating a record. Defaults to `PUT`. */
|
|
29
|
+
update: 'PUT' | 'PATCH' | 'POST';
|
|
30
|
+
/** Verb for deleting a record. Defaults to `DELETE`. */
|
|
31
|
+
remove: 'DELETE' | 'POST';
|
|
32
|
+
}
|
|
33
|
+
/** Everything needed to point a {@link RestDataSource} at an API. */
|
|
34
|
+
export interface RestDataSourceOptions<T> {
|
|
35
|
+
/** Prefixed to every request path, exactly once. */
|
|
36
|
+
baseUrl?: string;
|
|
37
|
+
/** Overrides for the per-operation paths. */
|
|
38
|
+
endpoints?: Partial<RestEndpoints>;
|
|
39
|
+
/** Overrides for query-parameter names, for APIs with another vocabulary. */
|
|
40
|
+
paramNames?: Partial<RestParamNames>;
|
|
41
|
+
/** Overrides for the HTTP verb used by each mutating operation. */
|
|
42
|
+
methods?: Partial<RestMethods>;
|
|
43
|
+
/** Sent with every request, merged over the default content type. */
|
|
44
|
+
headers?: Readonly<Record<string, string>>;
|
|
45
|
+
/** Maps a draft onto the request body. Defaults to sending the draft as-is. */
|
|
46
|
+
serializeRequest?: (draft: CrudDraft<T>) => unknown;
|
|
47
|
+
/** Maps a list response onto a page. Required when the API wraps its payload. */
|
|
48
|
+
parseResponse?: (payload: unknown) => CrudPage<T>;
|
|
49
|
+
/** Injected for testing; defaults to the global `fetch`. */
|
|
50
|
+
fetchImpl?: typeof fetch;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Raised when the server answers with a non-2xx status.
|
|
54
|
+
*
|
|
55
|
+
* Carries the status and the raw body so consumers can branch on a 409 or
|
|
56
|
+
* surface a validation payload, rather than receiving a flattened string.
|
|
57
|
+
*/
|
|
58
|
+
export declare class RestError extends Error {
|
|
59
|
+
/** The HTTP status the server responded with. */
|
|
60
|
+
readonly status: number;
|
|
61
|
+
/** The raw response body, for reading a validation payload. */
|
|
62
|
+
readonly body: string;
|
|
63
|
+
constructor(status: number, body: string, url: string);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A source over a REST API.
|
|
67
|
+
*
|
|
68
|
+
* URL and payload construction live in `protected` methods rather than inline,
|
|
69
|
+
* so an API that does not match the default dialect is a small subclass
|
|
70
|
+
* instead of a full reimplementation of the strategy.
|
|
71
|
+
*/
|
|
72
|
+
export declare class RestDataSource<T extends object, K extends keyof T> implements CrudDataSource<T, K> {
|
|
73
|
+
/** Prefixed to every request path, exactly once. */
|
|
74
|
+
protected readonly baseUrl: string;
|
|
75
|
+
/** Resolved per-operation paths. */
|
|
76
|
+
protected readonly endpoints: RestEndpoints;
|
|
77
|
+
/** Resolved query-parameter names. */
|
|
78
|
+
protected readonly paramNames: RestParamNames;
|
|
79
|
+
/** Resolved HTTP verbs. */
|
|
80
|
+
protected readonly methods: RestMethods;
|
|
81
|
+
/** Headers sent with every request. */
|
|
82
|
+
protected readonly headers: Readonly<Record<string, string>>;
|
|
83
|
+
/** Maps a draft onto the request body. */
|
|
84
|
+
protected readonly serializeRequest: (draft: CrudDraft<T>) => unknown;
|
|
85
|
+
/** Maps a list payload onto a page, when the API wraps its results. */
|
|
86
|
+
protected readonly parseResponse?: (payload: unknown) => CrudPage<T>;
|
|
87
|
+
private readonly fetchImpl;
|
|
88
|
+
constructor(options?: RestDataSourceOptions<T>);
|
|
89
|
+
/**
|
|
90
|
+
* Encode sort instructions as query parameters.
|
|
91
|
+
*
|
|
92
|
+
* Only the first instruction is sent: `sortBy`/`sortOrder` is a single-sort
|
|
93
|
+
* shape, and inventing a multi-sort encoding would guess at a convention the
|
|
94
|
+
* server may not share. Override to support one.
|
|
95
|
+
*/
|
|
96
|
+
protected serializeSort(sort: readonly CrudSort<T>[] | undefined, params: URLSearchParams): void;
|
|
97
|
+
/** Build the list URL, including pagination, sort and filters. */
|
|
98
|
+
protected buildListUrl(query: CrudQuery<T>): string;
|
|
99
|
+
/** Path addressing a single record. Override for `?id=` style APIs. */
|
|
100
|
+
protected buildRecordPath(endpoint: string, id: T[K]): string;
|
|
101
|
+
/**
|
|
102
|
+
* Interpret a list payload.
|
|
103
|
+
*
|
|
104
|
+
* Without a `parseResponse`, accepts either a bare array or the
|
|
105
|
+
* `{ data, total }` shape the previous implementation assumed.
|
|
106
|
+
*/
|
|
107
|
+
protected toPage(payload: unknown): CrudPage<T>;
|
|
108
|
+
/**
|
|
109
|
+
* Single point where `baseUrl`, headers and error handling are applied.
|
|
110
|
+
*
|
|
111
|
+
* Every request goes through here so `baseUrl` is prefixed exactly once -
|
|
112
|
+
* building a full URL earlier and prefixing again is how it ended up
|
|
113
|
+
* duplicated for relative bases.
|
|
114
|
+
*/
|
|
115
|
+
protected request(path: string, init?: RequestInit): Promise<unknown>;
|
|
116
|
+
list(query: CrudQuery<T>): Promise<CrudPage<T>>;
|
|
117
|
+
create(draft: CrudDraft<T>): Promise<T>;
|
|
118
|
+
update(id: T[K], draft: CrudDraft<T>): Promise<T>;
|
|
119
|
+
remove(id: T[K]): Promise<void>;
|
|
120
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { InMemoryDataSource } from './InMemoryDataSource';
|
|
2
|
+
import { IdGenerator } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* A source over an in-process array, for fixtures, demos and tests.
|
|
5
|
+
*
|
|
6
|
+
* The collection is instance state, seeded once from the array passed to the
|
|
7
|
+
* constructor and never re-read from it. That is the fix for the old
|
|
8
|
+
* closure-factory behaviour: identity of the caller's array no longer
|
|
9
|
+
* controls the dataset's lifetime, so passing an inline literal from a
|
|
10
|
+
* component no longer discards every created and edited row when the parent
|
|
11
|
+
* happens to re-render.
|
|
12
|
+
*
|
|
13
|
+
* The seed array is copied, so mutations never write back to the caller's data.
|
|
14
|
+
*/
|
|
15
|
+
export declare class StaticDataSource<T extends object, K extends keyof T> extends InMemoryDataSource<T, K> {
|
|
16
|
+
private records;
|
|
17
|
+
constructor(seed: readonly T[], key: K, generateId?: IdGenerator<T, K>);
|
|
18
|
+
protected read(): readonly T[];
|
|
19
|
+
protected write(records: readonly T[]): void;
|
|
20
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { IdGenerator } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Fresh RFC-4122 v4 identifier.
|
|
4
|
+
*
|
|
5
|
+
* `crypto.randomUUID` needs a secure context and is absent in some test
|
|
6
|
+
* environments, so this falls back to `getRandomValues` and, failing that,
|
|
7
|
+
* to `Math.random`. The fallback is for uniqueness within a client-side
|
|
8
|
+
* dataset, not for anything security-bearing.
|
|
9
|
+
*/
|
|
10
|
+
declare const randomUuid: () => string;
|
|
11
|
+
/**
|
|
12
|
+
* Default identity policy for in-memory sources.
|
|
13
|
+
*
|
|
14
|
+
* Chooses from the *runtime type of the existing keys* rather than coercing
|
|
15
|
+
* them to numbers. Coercion was the old bug: `Number('a3f9-...')` is `NaN`,
|
|
16
|
+
* `|| 0` collapsed it to `0`, and every created row came back as `1` -
|
|
17
|
+
* duplicate keys, and `update`/`remove` then resolving to the wrong record.
|
|
18
|
+
*
|
|
19
|
+
* - numeric keys -> highest + 1
|
|
20
|
+
* - digit-like strings -> highest + 1, kept as a string
|
|
21
|
+
* - any other string -> a fresh UUID
|
|
22
|
+
* - empty dataset -> `1`, matching the previous behaviour for the common case
|
|
23
|
+
*
|
|
24
|
+
* Mixed or non-primitive key types cannot be continued safely, so those throw
|
|
25
|
+
* rather than silently colliding. Pass an explicit generator for those.
|
|
26
|
+
*/
|
|
27
|
+
export declare const defaultIdGenerator: <T, K extends keyof T>(key: K) => IdGenerator<T, K>;
|
|
28
|
+
export { randomUuid };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { CrudFilters, CrudPage, CrudQuery, CrudSort } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Apply filters to an in-memory collection.
|
|
4
|
+
*
|
|
5
|
+
* Filters whose value is undefined, null or the empty string are ignored, so
|
|
6
|
+
* a cleared search box does not exclude everything. A record missing the
|
|
7
|
+
* filtered field never matches.
|
|
8
|
+
*/
|
|
9
|
+
export declare const filterRecords: <T>(records: readonly T[], filters?: CrudFilters<T>) => readonly T[];
|
|
10
|
+
/**
|
|
11
|
+
* Apply an ordered list of sort instructions.
|
|
12
|
+
*
|
|
13
|
+
* Earlier entries take precedence; later ones break ties. Missing values sort
|
|
14
|
+
* last in both directions - they carry no ordering information, so surfacing
|
|
15
|
+
* them ahead of real data in one direction would be arbitrary.
|
|
16
|
+
*/
|
|
17
|
+
export declare const sortRecords: <T>(records: readonly T[], sort?: readonly CrudSort<T>[]) => readonly T[];
|
|
18
|
+
/** Take the requested page. Pages are 1-based; out-of-range pages yield an empty slice. */
|
|
19
|
+
export declare const paginateRecords: <T>(records: readonly T[], page: number, pageSize: number) => readonly T[];
|
|
20
|
+
/**
|
|
21
|
+
* The shared filter-then-sort-then-paginate pipeline.
|
|
22
|
+
*
|
|
23
|
+
* Every strategy backed by a local collection routes through this, so their
|
|
24
|
+
* querying behaviour cannot drift apart.
|
|
25
|
+
*/
|
|
26
|
+
export declare const queryRecords: <T>(records: readonly T[], query: CrudQuery<T>) => CrudPage<T>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type { CrudDataSource, CrudDraft, CrudFilters, CrudFilterValue, CrudPage, CrudQuery, CrudSort, IdGenerator, SortDirection, } from './types';
|
|
2
|
+
export { defaultIdGenerator, randomUuid } from './identity';
|
|
3
|
+
export { filterRecords, paginateRecords, queryRecords, sortRecords } from './inMemoryQuery';
|
|
4
|
+
export { InMemoryDataSource } from './InMemoryDataSource';
|
|
5
|
+
export { StaticDataSource } from './StaticDataSource';
|
|
6
|
+
export { LocalStorageDataSource } from './LocalStorageDataSource';
|
|
7
|
+
export type { Timestamped } from './LocalStorageDataSource';
|
|
8
|
+
export { RestDataSource, RestError } from './RestDataSource';
|
|
9
|
+
export type { RestDataSourceOptions, RestEndpoints, RestMethods, RestParamNames, } from './RestDataSource';
|
|
10
|
+
export { CustomDataSource, UnsupportedOperationError } from './CustomDataSource';
|
|
11
|
+
export type { CrudOperations } from './CustomDataSource';
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The core data-access contract every CRUD strategy implements.
|
|
3
|
+
*
|
|
4
|
+
* `T` is the record shape and `K` the property holding its identity, so
|
|
5
|
+
* `T[K]` is the precise type of an id throughout - never `any`, never a
|
|
6
|
+
* widened `string | number`.
|
|
7
|
+
*/
|
|
8
|
+
/** Sort direction, matching the vocabulary ProTable reports. */
|
|
9
|
+
export type SortDirection = 'ascend' | 'descend';
|
|
10
|
+
/** One sort instruction. Queries carry an ordered list to support multi-sort. */
|
|
11
|
+
export interface CrudSort<T> {
|
|
12
|
+
/** The property to order by. */
|
|
13
|
+
readonly field: keyof T;
|
|
14
|
+
/** Ascending or descending. */
|
|
15
|
+
readonly direction: SortDirection;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A value a column can be filtered by.
|
|
19
|
+
*
|
|
20
|
+
* Deliberately narrow: filters arrive from form inputs and query strings, so
|
|
21
|
+
* this is the complete set of things a user can actually express. Anything
|
|
22
|
+
* richer belongs in a custom data source.
|
|
23
|
+
*/
|
|
24
|
+
export type CrudFilterValue = string | number | boolean;
|
|
25
|
+
/** Field-keyed filters. Absent keys are unfiltered. */
|
|
26
|
+
export type CrudFilters<T> = {
|
|
27
|
+
readonly [P in keyof T]?: CrudFilterValue;
|
|
28
|
+
};
|
|
29
|
+
/** A complete read request: which slice, in what order, matching what. */
|
|
30
|
+
export interface CrudQuery<T> {
|
|
31
|
+
/** 1-based, matching ProTable's `current`. */
|
|
32
|
+
readonly page: number;
|
|
33
|
+
/** Rows per page. */
|
|
34
|
+
readonly pageSize: number;
|
|
35
|
+
/** Ordered sort instructions; earlier entries take precedence. */
|
|
36
|
+
readonly sort?: readonly CrudSort<T>[];
|
|
37
|
+
/** Field-keyed filters. Absent keys are unfiltered. */
|
|
38
|
+
readonly filters?: CrudFilters<T>;
|
|
39
|
+
}
|
|
40
|
+
/** One page of results plus the total matching the filters (not the page size). */
|
|
41
|
+
export interface CrudPage<T> {
|
|
42
|
+
/** The records on this page. */
|
|
43
|
+
readonly items: readonly T[];
|
|
44
|
+
/** Records matching the filters, across all pages. */
|
|
45
|
+
readonly total: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A record being written.
|
|
49
|
+
*
|
|
50
|
+
* Partial because the identity is assigned by the source on create, and an
|
|
51
|
+
* update legitimately touches a subset of fields.
|
|
52
|
+
*/
|
|
53
|
+
export type CrudDraft<T> = Partial<T>;
|
|
54
|
+
/**
|
|
55
|
+
* Produces the identity for a newly created record.
|
|
56
|
+
*
|
|
57
|
+
* Receives the existing records so a strategy can derive the next value
|
|
58
|
+
* (max + 1, a fresh UUID, a sequence). Supplying one replaces the built-in
|
|
59
|
+
* policy entirely.
|
|
60
|
+
*/
|
|
61
|
+
export type IdGenerator<T, K extends keyof T> = (existing: readonly T[]) => T[K];
|
|
62
|
+
/**
|
|
63
|
+
* The interface every strategy implements: static fixtures, REST,
|
|
64
|
+
* localStorage, or a consumer's own.
|
|
65
|
+
*
|
|
66
|
+
* Implementations own their storage and lifetime. They are plain objects -
|
|
67
|
+
* no React involvement - so each is unit-testable without a renderer.
|
|
68
|
+
*/
|
|
69
|
+
export interface CrudDataSource<T, K extends keyof T> {
|
|
70
|
+
/** Read one page. Implementations must apply filters before paginating. */
|
|
71
|
+
list(query: CrudQuery<T>): Promise<CrudPage<T>>;
|
|
72
|
+
/** Persist a new record and return it as stored, identity included. */
|
|
73
|
+
create(draft: CrudDraft<T>): Promise<T>;
|
|
74
|
+
/** Merge `draft` into the record at `id` and return the stored result. */
|
|
75
|
+
update(id: T[K], draft: CrudDraft<T>): Promise<T>;
|
|
76
|
+
/** Delete the record at `id`. */
|
|
77
|
+
remove(id: T[K]): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Every record matching the query, ignoring pagination.
|
|
80
|
+
*
|
|
81
|
+
* Exists so export can cover the whole result set rather than the current
|
|
82
|
+
* page. Optional: a source over an unbounded remote collection may have no
|
|
83
|
+
* safe way to honour it, and callers must degrade to `list` when absent.
|
|
84
|
+
*/
|
|
85
|
+
listAll?(query: Omit<CrudQuery<T>, 'page' | 'pageSize'>): Promise<readonly T[]>;
|
|
86
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e={actions:`Actions`,edit:`Edit`,delete:`Delete`,create:`New`,refresh:`Refresh`,createTitle:`Create Item`,editTitle:`Edit Item`,ok:`OK`,cancel:`Cancel`,requiredField:e=>`${e} is required`,confirmDeleteTitle:`Are you sure?`,confirmDeleteContent:`This action cannot be undone.`,confirmDeleteOk:`Yes, Delete`,confirmBulkDeleteTitle:e=>`Delete ${e} items?`,confirmBulkDeleteOk:`Yes, Delete All`,deleteSelected:e=>`Delete Selected (${e})`,selectItemsToDelete:`Please select items to delete`,bulkDeleteSuccess:e=>`Deleted ${e} items`,bulkDeletePartial:(e,t,n)=>`Deleted ${e} of ${t}. ${n} failed.`,exportAll:e=>`Export all as ${e}`,exportPage:e=>`Export page as ${e}`,nothingToExport:`Nothing to export`,exportFailed:e=>`Export failed: ${e}`,createSuccess:`Created successfully`,updateSuccess:`Updated successfully`,deleteSuccess:`Deleted successfully`,loading:`Loading CRUD table…`,yes:`Yes`,no:`No`,empty:`-`,selectPlaceholder:e=>`Select ${e.toLowerCase()}`,tagsPlaceholder:`Type and press enter`,invalidEmail:`Please enter a valid email`,invalidUrl:`Please enter a valid URL`,invalidImageUrl:`Please enter a valid image URL`,invalidJson:`Please enter valid JSON`};Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return e}});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//#region lib/locale/en_US.ts
|
|
2
|
+
var e = {
|
|
3
|
+
actions: "Actions",
|
|
4
|
+
edit: "Edit",
|
|
5
|
+
delete: "Delete",
|
|
6
|
+
create: "New",
|
|
7
|
+
refresh: "Refresh",
|
|
8
|
+
createTitle: "Create Item",
|
|
9
|
+
editTitle: "Edit Item",
|
|
10
|
+
ok: "OK",
|
|
11
|
+
cancel: "Cancel",
|
|
12
|
+
requiredField: (e) => `${e} is required`,
|
|
13
|
+
confirmDeleteTitle: "Are you sure?",
|
|
14
|
+
confirmDeleteContent: "This action cannot be undone.",
|
|
15
|
+
confirmDeleteOk: "Yes, Delete",
|
|
16
|
+
confirmBulkDeleteTitle: (e) => `Delete ${e} items?`,
|
|
17
|
+
confirmBulkDeleteOk: "Yes, Delete All",
|
|
18
|
+
deleteSelected: (e) => `Delete Selected (${e})`,
|
|
19
|
+
selectItemsToDelete: "Please select items to delete",
|
|
20
|
+
bulkDeleteSuccess: (e) => `Deleted ${e} items`,
|
|
21
|
+
bulkDeletePartial: (e, t, n) => `Deleted ${e} of ${t}. ${n} failed.`,
|
|
22
|
+
exportAll: (e) => `Export all as ${e}`,
|
|
23
|
+
exportPage: (e) => `Export page as ${e}`,
|
|
24
|
+
nothingToExport: "Nothing to export",
|
|
25
|
+
exportFailed: (e) => `Export failed: ${e}`,
|
|
26
|
+
createSuccess: "Created successfully",
|
|
27
|
+
updateSuccess: "Updated successfully",
|
|
28
|
+
deleteSuccess: "Deleted successfully",
|
|
29
|
+
loading: "Loading CRUD table…",
|
|
30
|
+
yes: "Yes",
|
|
31
|
+
no: "No",
|
|
32
|
+
empty: "-",
|
|
33
|
+
selectPlaceholder: (e) => `Select ${e.toLowerCase()}`,
|
|
34
|
+
tagsPlaceholder: "Type and press enter",
|
|
35
|
+
invalidEmail: "Please enter a valid email",
|
|
36
|
+
invalidUrl: "Please enter a valid URL",
|
|
37
|
+
invalidImageUrl: "Please enter a valid image URL",
|
|
38
|
+
invalidJson: "Please enter valid JSON"
|
|
39
|
+
};
|
|
40
|
+
//#endregion
|
|
41
|
+
export { e as t };
|
package/dist/exportData.cjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
Object.
|
|
2
|
-
`)},
|
|
3
|
-
<?
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
</
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=[`=`,`+`,`-`,`@`,` `,`\r`],t=/^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/,n=n=>{if(n===``)return n;let r=n.trimStart();if(t.test(r))return n;let i=t=>t!==``&&e.includes(t[0]);return i(n)||i(r)?`'${n}`:n},r=(e,t)=>{let r=t.filter(e=>e.fieldType!==`option`&&e.dataIndex),i=r.map(e=>String(e.title||e.dataIndex)),a=e.map(e=>r.map(t=>{let n=e[t.dataIndex];return n==null?``:t.fieldType===`boolean`?n?`Yes`:`No`:t.fieldType===`date`?String(n):t.fieldType===`enum`&&t.enumOptions&&t.enumOptions[n]?.text||String(n)})),o=e=>`"${n(e).replace(/"/g,`""`)}"`;return[i.map(o).join(`,`),...a.map(e=>e.map(e=>o(String(e))).join(`,`))].join(`
|
|
2
|
+
`)},i=(e,t,n)=>{let r=new Blob([e],{type:n}),i=URL.createObjectURL(r),a=document.createElement(`a`);a.href=i,a.download=t,document.body.appendChild(a),a.click(),document.body.removeChild(a),URL.revokeObjectURL(i)},a=e=>{let{data:t,columns:n,filename:a=`export`}=e;i(r(t,n),`${a}.csv`,`text/csv;charset=utf-8;`)},o=e=>{let{data:t,filename:n=`export`}=e;i(JSON.stringify(t,null,2),`${n}.json`,`application/json`)},s=e=>{let{data:t,columns:n,filename:r=`export`}=e,a=n.filter(e=>e.fieldType!==`option`&&e.dataIndex),o=a.map(e=>String(e.title||e.dataIndex)),s=t.map(e=>a.map(t=>{let n=e[t.dataIndex];return n==null?``:t.fieldType===`boolean`?n?`Yes`:`No`:t.fieldType===`enum`&&t.enumOptions?t.enumOptions[n]?.text||String(n):typeof n==`number`?n:String(n)})),c=e=>e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`).replace(/'/g,`'`),l=e=>`<Cell><Data ss:Type="${typeof e==`number`?`Number`:`String`}">${typeof e==`number`?String(e):c(e)}</Data></Cell>`;i(`<?xml version="1.0"?>
|
|
3
|
+
<?mso-application progid="Excel.Sheet"?>
|
|
4
|
+
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
|
|
5
|
+
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">
|
|
6
|
+
<Worksheet ss:Name="Export">
|
|
7
|
+
<Table>
|
|
8
|
+
${`<Row>${o.map(e=>l(e)).join(``)}</Row>`}
|
|
9
|
+
${s.map(e=>`<Row>${e.map(e=>l(e)).join(``)}</Row>`).join(`
|
|
10
|
+
`)}
|
|
11
|
+
</Table>
|
|
12
|
+
</Worksheet>
|
|
13
|
+
</Workbook>`,`${r}.xls`,`application/vnd.ms-excel`)},c=e=>{let{format:t=`csv`}=e;switch(t){case`csv`:a(e);break;case`json`:o(e);break;case`excel`:s(e);break;default:a(e)}},l=async(e,t,n=`export`,r=`csv`)=>{try{c({data:await e(),columns:t,filename:n,format:r})}catch(e){throw console.error(`Export failed:`,e),e}};exports.exportAllData=l,exports.exportData=c,exports.exportToCSV=a,exports.exportToExcel=s,exports.exportToJSON=o;
|
package/dist/exportData.js
CHANGED
|
@@ -1,56 +1,65 @@
|
|
|
1
1
|
//#region lib/utils/exportData.ts
|
|
2
|
-
var e =
|
|
3
|
-
|
|
2
|
+
var e = [
|
|
3
|
+
"=",
|
|
4
|
+
"+",
|
|
5
|
+
"-",
|
|
6
|
+
"@",
|
|
7
|
+
" ",
|
|
8
|
+
"\r"
|
|
9
|
+
], t = /^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/, n = (n) => {
|
|
10
|
+
if (n === "") return n;
|
|
11
|
+
let r = n.trimStart();
|
|
12
|
+
if (t.test(r)) return n;
|
|
13
|
+
let i = (t) => t !== "" && e.includes(t[0]);
|
|
14
|
+
return i(n) || i(r) ? `'${n}` : n;
|
|
15
|
+
}, r = (e, t) => {
|
|
16
|
+
let r = t.filter((e) => e.fieldType !== "option" && e.dataIndex), i = r.map((e) => String(e.title || e.dataIndex)), a = e.map((e) => r.map((t) => {
|
|
4
17
|
let n = e[t.dataIndex];
|
|
5
18
|
return n == null ? "" : t.fieldType === "boolean" ? n ? "Yes" : "No" : t.fieldType === "date" ? String(n) : t.fieldType === "enum" && t.enumOptions && t.enumOptions[n]?.text || String(n);
|
|
6
|
-
}))
|
|
7
|
-
return [
|
|
8
|
-
},
|
|
19
|
+
})), o = (e) => `"${n(e).replace(/"/g, "\"\"")}"`;
|
|
20
|
+
return [i.map(o).join(","), ...a.map((e) => e.map((e) => o(String(e))).join(","))].join("\n");
|
|
21
|
+
}, i = (e, t, n) => {
|
|
9
22
|
let r = new Blob([e], { type: n }), i = URL.createObjectURL(r), a = document.createElement("a");
|
|
10
23
|
a.href = i, a.download = t, document.body.appendChild(a), a.click(), document.body.removeChild(a), URL.revokeObjectURL(i);
|
|
11
|
-
}, n = (n) => {
|
|
12
|
-
let { data: r, columns: i, filename: a = "export" } = n;
|
|
13
|
-
t(e(r, i), `${a}.csv`, "text/csv;charset=utf-8;");
|
|
14
|
-
}, r = (e) => {
|
|
15
|
-
let { data: n, filename: r = "export" } = e;
|
|
16
|
-
t(JSON.stringify(n, null, 2), `${r}.json`, "application/json");
|
|
17
|
-
}, i = (e) => {
|
|
18
|
-
let { data: n, columns: r, filename: i = "export" } = e, a = r.filter((e) => e.fieldType !== "option" && e.dataIndex), o = a.map((e) => String(e.title || e.dataIndex)), s = n.map((e) => a.map((t) => {
|
|
19
|
-
let n = e[t.dataIndex];
|
|
20
|
-
return n == null ? "" : t.fieldType === "boolean" ? n ? "Yes" : "No" : t.fieldType === "enum" && t.enumOptions && t.enumOptions[n]?.text || String(n);
|
|
21
|
-
}));
|
|
22
|
-
t((() => {
|
|
23
|
-
let e = (e) => e.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
24
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
25
|
-
<?xml-stylesheet type="text/xsl" href="stylesheet.xsl"?>
|
|
26
|
-
<workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet">
|
|
27
|
-
<sheets>
|
|
28
|
-
<sheet name="Export">
|
|
29
|
-
<table>
|
|
30
|
-
<row>${o.map((t) => `<c t="inlineStr"><is><t>${e(t)}</t></is></c>`).join("")}</row>
|
|
31
|
-
${s.map((t) => `<row>${t.map((t) => `<c t="inlineStr"><is><t>${e(String(t))}</t></is></c>`).join("")}</row>`).join("")}
|
|
32
|
-
</table>
|
|
33
|
-
</sheet>
|
|
34
|
-
</sheets>
|
|
35
|
-
</workbook>`;
|
|
36
|
-
})(), `${i}.xls`, "application/vnd.ms-excel");
|
|
37
24
|
}, a = (e) => {
|
|
25
|
+
let { data: t, columns: n, filename: a = "export" } = e;
|
|
26
|
+
i(r(t, n), `${a}.csv`, "text/csv;charset=utf-8;");
|
|
27
|
+
}, o = (e) => {
|
|
28
|
+
let { data: t, filename: n = "export" } = e;
|
|
29
|
+
i(JSON.stringify(t, null, 2), `${n}.json`, "application/json");
|
|
30
|
+
}, s = (e) => {
|
|
31
|
+
let { data: t, columns: n, filename: r = "export" } = e, a = n.filter((e) => e.fieldType !== "option" && e.dataIndex), o = a.map((e) => String(e.title || e.dataIndex)), s = t.map((e) => a.map((t) => {
|
|
32
|
+
let n = e[t.dataIndex];
|
|
33
|
+
return n == null ? "" : t.fieldType === "boolean" ? n ? "Yes" : "No" : t.fieldType === "enum" && t.enumOptions ? t.enumOptions[n]?.text || String(n) : typeof n == "number" ? n : String(n);
|
|
34
|
+
})), c = (e) => e.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'"), l = (e) => `<Cell><Data ss:Type="${typeof e == "number" ? "Number" : "String"}">${typeof e == "number" ? String(e) : c(e)}</Data></Cell>`;
|
|
35
|
+
i(`<?xml version="1.0"?>
|
|
36
|
+
<?mso-application progid="Excel.Sheet"?>
|
|
37
|
+
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
|
|
38
|
+
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">
|
|
39
|
+
<Worksheet ss:Name="Export">
|
|
40
|
+
<Table>
|
|
41
|
+
${`<Row>${o.map((e) => l(e)).join("")}</Row>`}
|
|
42
|
+
${s.map((e) => `<Row>${e.map((e) => l(e)).join("")}</Row>`).join("\n")}
|
|
43
|
+
</Table>
|
|
44
|
+
</Worksheet>
|
|
45
|
+
</Workbook>`, `${r}.xls`, "application/vnd.ms-excel");
|
|
46
|
+
}, c = (e) => {
|
|
38
47
|
let { format: t = "csv" } = e;
|
|
39
48
|
switch (t) {
|
|
40
49
|
case "csv":
|
|
41
|
-
|
|
50
|
+
a(e);
|
|
42
51
|
break;
|
|
43
52
|
case "json":
|
|
44
|
-
|
|
53
|
+
o(e);
|
|
45
54
|
break;
|
|
46
|
-
case "
|
|
47
|
-
|
|
55
|
+
case "excel":
|
|
56
|
+
s(e);
|
|
48
57
|
break;
|
|
49
|
-
default:
|
|
58
|
+
default: a(e);
|
|
50
59
|
}
|
|
51
|
-
},
|
|
60
|
+
}, l = async (e, t, n = "export", r = "csv") => {
|
|
52
61
|
try {
|
|
53
|
-
|
|
62
|
+
c({
|
|
54
63
|
data: await e(),
|
|
55
64
|
columns: t,
|
|
56
65
|
filename: n,
|
|
@@ -61,4 +70,4 @@ var e = (e, t) => {
|
|
|
61
70
|
}
|
|
62
71
|
};
|
|
63
72
|
//#endregion
|
|
64
|
-
export {
|
|
73
|
+
export { l as exportAllData, c as exportData, a as exportToCSV, s as exportToExcel, o as exportToJSON };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ProColumns } from '@ant-design/pro-components';
|
|
2
|
+
import { FormRule } from 'antd';
|
|
3
|
+
import { EnumOption, FieldColumn } from './types';
|
|
4
|
+
import { CrudTableLocale } from '../locale/types';
|
|
5
|
+
/** Every column type CrudTable understands. */
|
|
6
|
+
export type FieldType = 'string' | 'number' | 'date' | 'boolean' | 'enum' | 'custom' | 'textarea' | 'email' | 'url' | 'password' | 'money' | 'percent' | 'rating' | 'progress' | 'time' | 'dateRange' | 'tags' | 'image' | 'color' | 'json';
|
|
7
|
+
/**
|
|
8
|
+
* A self-contained description of one field type: how it renders in the
|
|
9
|
+
* table, which control edits it in the modal form, and how values are
|
|
10
|
+
* converted between record shape and form shape. Adding a new column
|
|
11
|
+
* type to CrudTable means adding exactly one entry here.
|
|
12
|
+
*/
|
|
13
|
+
export interface FieldTypeDefinition {
|
|
14
|
+
/** Extra ProColumns props merged over the base column (valueType, render, ...). */
|
|
15
|
+
column?: (col: FieldColumn, locale: CrudTableLocale) => Partial<ProColumns<Record<PropertyKey, unknown>>>;
|
|
16
|
+
/** The antd control used in the create/edit modal. Return null for "no form field". */
|
|
17
|
+
formControl: (col: FieldColumn, disabled: boolean, locale: CrudTableLocale) => React.ReactNode | null;
|
|
18
|
+
/** Convert a record value into what the form control expects. */
|
|
19
|
+
toFormValue?: (value: unknown) => unknown;
|
|
20
|
+
/** Convert the submitted form value back into the record shape. */
|
|
21
|
+
fromFormValue?: (value: unknown) => unknown;
|
|
22
|
+
/** Validation rules implied by the type itself (merged before user rules). */
|
|
23
|
+
rules?: (col: FieldColumn, locale: CrudTableLocale) => FormRule[];
|
|
24
|
+
/** Form.Item valuePropName override (e.g. 'checked' for Switch). */
|
|
25
|
+
valuePropName?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Every field type, keyed by name.
|
|
29
|
+
*
|
|
30
|
+
* Adding a column type to CrudTable means adding one entry here; nothing else
|
|
31
|
+
* dispatches on `fieldType`.
|
|
32
|
+
*/
|
|
33
|
+
export declare const fieldRegistry: Record<FieldType, FieldTypeDefinition>;
|
|
34
|
+
/** Look up a field type, falling back to `string` so every column gets a form control. */
|
|
35
|
+
export declare const getFieldDefinition: (fieldType?: FieldType) => FieldTypeDefinition;
|
|
36
|
+
/** The strings a field definition falls back to when a caller passes none. */
|
|
37
|
+
export declare const defaultFieldLocale: CrudTableLocale;
|
|
38
|
+
export type { EnumOption, FieldColumn };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { FieldType } from './registry';
|
|
2
|
+
/** One selectable value for an `enum` column. */
|
|
3
|
+
export interface EnumOption {
|
|
4
|
+
/** Label shown in the cell and in the select control. */
|
|
5
|
+
text: string;
|
|
6
|
+
/** antd Tag colour used when rendering the cell. */
|
|
7
|
+
color?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The slice of a column definition the field registry reads.
|
|
11
|
+
*
|
|
12
|
+
* Structural rather than generic on purpose: the registry dispatches across
|
|
13
|
+
* twenty field types with unrelated value shapes, so a single definition
|
|
14
|
+
* cannot be parameterised over any one record type. `unknown` here is the
|
|
15
|
+
* strict choice, not a lax one - it forces every renderer to narrow before
|
|
16
|
+
* using a value, which `any` would not.
|
|
17
|
+
*/
|
|
18
|
+
export interface FieldColumn {
|
|
19
|
+
/** The record property this column reads. */
|
|
20
|
+
dataIndex: PropertyKey;
|
|
21
|
+
/** Column header, also used as the form label. */
|
|
22
|
+
title: string;
|
|
23
|
+
/** Which registry entry renders and edits this column. */
|
|
24
|
+
fieldType?: FieldType;
|
|
25
|
+
/** Selectable values, for `enum` columns. */
|
|
26
|
+
enumOptions?: Record<string, EnumOption>;
|
|
27
|
+
/** Cell renderer for `custom` columns. */
|
|
28
|
+
customRender?: (value: unknown, record: Record<PropertyKey, unknown>) => React.ReactNode;
|
|
29
|
+
}
|