@m5kdev/frontend 0.1.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/.cursor/rules/frontend.mdc +49 -0
- package/.turbo/turbo-build.log +5 -0
- package/.turbo/turbo-check-types.log +5 -0
- package/.turbo/turbo-lint$colon$fix.log +101 -0
- package/.turbo/turbo-lint.log +162 -0
- package/LICENSE +621 -0
- package/dist/src/modules/auth/auth.lib.d.ts +2222 -0
- package/dist/src/modules/auth/auth.lib.d.ts.map +1 -0
- package/dist/src/modules/auth/auth.lib.js +41 -0
- package/dist/src/modules/auth/components/AuthProvider.d.ts +15 -0
- package/dist/src/modules/auth/components/AuthProvider.d.ts.map +1 -0
- package/dist/src/modules/auth/components/AuthProvider.js +73 -0
- package/dist/src/modules/auth/hooks/useAuth.d.ts +4 -0
- package/dist/src/modules/auth/hooks/useAuth.d.ts.map +1 -0
- package/dist/src/modules/auth/hooks/useAuth.js +14 -0
- package/dist/src/modules/auth/hooks/useAuthAdmin.d.ts +21 -0
- package/dist/src/modules/auth/hooks/useAuthAdmin.d.ts.map +1 -0
- package/dist/src/modules/auth/hooks/useAuthAdmin.js +160 -0
- package/dist/src/modules/auth/hooks/useSession.d.ts +38 -0
- package/dist/src/modules/auth/hooks/useSession.d.ts.map +1 -0
- package/dist/src/modules/auth/hooks/useSession.js +5 -0
- package/dist/src/modules/billing/components/BillingProvider.d.ts +14 -0
- package/dist/src/modules/billing/components/BillingProvider.d.ts.map +1 -0
- package/dist/src/modules/billing/components/BillingProvider.js +25 -0
- package/dist/src/modules/billing/hooks/useSubscription.d.ts +5 -0
- package/dist/src/modules/billing/hooks/useSubscription.d.ts.map +1 -0
- package/dist/src/modules/billing/hooks/useSubscription.js +5 -0
- package/dist/src/modules/file/hooks/useS3DownloadUrl.d.ts +3 -0
- package/dist/src/modules/file/hooks/useS3DownloadUrl.d.ts.map +1 -0
- package/dist/src/modules/file/hooks/useS3DownloadUrl.js +14 -0
- package/dist/src/modules/file/hooks/useS3Upload.d.ts +10 -0
- package/dist/src/modules/file/hooks/useS3Upload.d.ts.map +1 -0
- package/dist/src/modules/file/hooks/useS3Upload.js +76 -0
- package/dist/src/modules/file/hooks/useUpload.d.ts +30 -0
- package/dist/src/modules/file/hooks/useUpload.d.ts.map +1 -0
- package/dist/src/modules/file/hooks/useUpload.js +167 -0
- package/dist/src/modules/table/hooks/useDateRangeFilter.d.ts +28 -0
- package/dist/src/modules/table/hooks/useDateRangeFilter.d.ts.map +1 -0
- package/dist/src/modules/table/hooks/useDateRangeFilter.js +20 -0
- package/dist/src/modules/table/hooks/useNuqsQueryParams.d.ts +25 -0
- package/dist/src/modules/table/hooks/useNuqsQueryParams.d.ts.map +1 -0
- package/dist/src/modules/table/hooks/useNuqsQueryParams.js +82 -0
- package/dist/src/modules/table/hooks/useNuqsTable.d.ts +33 -0
- package/dist/src/modules/table/hooks/useNuqsTable.d.ts.map +1 -0
- package/dist/src/modules/table/hooks/useNuqsTable.js +32 -0
- package/dist/src/modules/table/hooks/useQueryWithParams.d.ts +26 -0
- package/dist/src/modules/table/hooks/useQueryWithParams.d.ts.map +1 -0
- package/dist/src/modules/table/hooks/useQueryWithParams.js +24 -0
- package/dist/src/types.d.ts +4 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +1 -0
- package/dist/src/utils/date.d.ts +24 -0
- package/dist/src/utils/date.d.ts.map +1 -0
- package/dist/src/utils/date.js +71 -0
- package/dist/src/utils/query.d.ts +8 -0
- package/dist/src/utils/query.d.ts.map +1 -0
- package/dist/src/utils/query.js +46 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/package.json +85 -0
- package/src/modules/auth/auth.lib.ts +49 -0
- package/src/modules/auth/components/AuthProvider.tsx +105 -0
- package/src/modules/auth/hooks/useAuth.ts +20 -0
- package/src/modules/auth/hooks/useAuthAdmin.ts +188 -0
- package/src/modules/auth/hooks/useSession.ts +6 -0
- package/src/modules/billing/components/BillingProvider.tsx +58 -0
- package/src/modules/billing/hooks/useSubscription.ts +6 -0
- package/src/modules/file/hooks/useS3DownloadUrl.ts +18 -0
- package/src/modules/file/hooks/useS3Upload.ts +89 -0
- package/src/modules/file/hooks/useUpload.ts +220 -0
- package/src/modules/table/hooks/useDateRangeFilter.ts +55 -0
- package/src/modules/table/hooks/useNuqsQueryParams.ts +134 -0
- package/src/modules/table/hooks/useNuqsTable.ts +83 -0
- package/src/modules/table/hooks/useQueryWithParams.ts +62 -0
- package/src/types.ts +4 -0
- package/src/utils/date.ts +88 -0
- package/src/utils/query.ts +72 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +29 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
|
+
// Shared utility function to create upload promise
|
|
3
|
+
const createUploadPromise = (type, file, callbacks) => {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
const xhr = new XMLHttpRequest();
|
|
6
|
+
xhr.upload.onprogress = (event) => {
|
|
7
|
+
if (event.lengthComputable) {
|
|
8
|
+
const progress = Math.round((event.loaded * 100) / event.total);
|
|
9
|
+
callbacks.onProgress(progress, event.loaded);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
xhr.onload = () => {
|
|
13
|
+
if (xhr.status === 200) {
|
|
14
|
+
callbacks.onComplete();
|
|
15
|
+
// if response has json header, parse it
|
|
16
|
+
if (xhr.getResponseHeader("Content-Type")?.includes("application/json")) {
|
|
17
|
+
resolve(JSON.parse(xhr.response));
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
resolve(xhr.response);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
const errorMessage = `Upload failed with status ${xhr.status}`;
|
|
25
|
+
callbacks.onError(errorMessage);
|
|
26
|
+
reject(new Error(errorMessage));
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
xhr.onerror = () => {
|
|
30
|
+
const errorMessage = "Network error during upload";
|
|
31
|
+
callbacks.onError(errorMessage);
|
|
32
|
+
reject(new Error(errorMessage));
|
|
33
|
+
};
|
|
34
|
+
xhr.open("POST", `${import.meta.env.VITE_SERVER_URL}/upload/file/${type}`);
|
|
35
|
+
const formData = new FormData();
|
|
36
|
+
formData.append("file", file);
|
|
37
|
+
xhr.send(formData);
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
// Shared utility to create a task
|
|
41
|
+
const createUploadTask = (file) => ({
|
|
42
|
+
id: typeof crypto !== "undefined" && crypto.randomUUID
|
|
43
|
+
? crypto.randomUUID()
|
|
44
|
+
: Math.random().toString(36).substr(2, 9),
|
|
45
|
+
file,
|
|
46
|
+
progress: 0,
|
|
47
|
+
status: "pending",
|
|
48
|
+
bytesUploaded: 0,
|
|
49
|
+
totalBytes: file.size,
|
|
50
|
+
});
|
|
51
|
+
// Hook for single file upload
|
|
52
|
+
export function useFileUpload() {
|
|
53
|
+
const [uploadTask, setUploadTask] = useState(null);
|
|
54
|
+
const upload = useCallback(async (type, file) => {
|
|
55
|
+
const task = createUploadTask(file);
|
|
56
|
+
setUploadTask(task);
|
|
57
|
+
const callbacks = {
|
|
58
|
+
onProgress: (progress, bytesUploaded) => {
|
|
59
|
+
setUploadTask((prev) => prev
|
|
60
|
+
? {
|
|
61
|
+
...prev,
|
|
62
|
+
progress,
|
|
63
|
+
bytesUploaded,
|
|
64
|
+
status: "uploading",
|
|
65
|
+
}
|
|
66
|
+
: null);
|
|
67
|
+
},
|
|
68
|
+
onComplete: () => {
|
|
69
|
+
setUploadTask((prev) => (prev ? { ...prev, status: "completed", progress: 100 } : null));
|
|
70
|
+
},
|
|
71
|
+
onError: (errorMessage) => {
|
|
72
|
+
setUploadTask((prev) => prev
|
|
73
|
+
? {
|
|
74
|
+
...prev,
|
|
75
|
+
status: "error",
|
|
76
|
+
errorMessage,
|
|
77
|
+
progress: 0,
|
|
78
|
+
}
|
|
79
|
+
: null);
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
return createUploadPromise(type, file, callbacks);
|
|
83
|
+
}, []);
|
|
84
|
+
const reset = useCallback(() => {
|
|
85
|
+
setUploadTask(null);
|
|
86
|
+
}, []);
|
|
87
|
+
return {
|
|
88
|
+
...uploadTask,
|
|
89
|
+
upload,
|
|
90
|
+
reset,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
// Hook for multiple file uploads
|
|
94
|
+
export function useMultipartUpload() {
|
|
95
|
+
const [uploadQueue, setUploadQueue] = useState([]);
|
|
96
|
+
const [overallProgress, setOverallProgress] = useState(0);
|
|
97
|
+
const updateTask = useCallback((id, changes) => {
|
|
98
|
+
setUploadQueue((prev) => prev.map((task) => (task.id === id ? { ...task, ...changes } : task)));
|
|
99
|
+
}, []);
|
|
100
|
+
const updateOverallProgress = useCallback((queue) => {
|
|
101
|
+
if (queue.length === 0) {
|
|
102
|
+
setOverallProgress(100);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const totalBytes = queue.reduce((sum, task) => sum + task.file.size, 0);
|
|
106
|
+
const uploadedBytes = queue.reduce((sum, task) => {
|
|
107
|
+
if (task.status === "completed") {
|
|
108
|
+
return sum + task.file.size;
|
|
109
|
+
}
|
|
110
|
+
if (task.status === "uploading") {
|
|
111
|
+
return sum + (task.progress / 100) * task.file.size;
|
|
112
|
+
}
|
|
113
|
+
return sum;
|
|
114
|
+
}, 0);
|
|
115
|
+
setOverallProgress(Math.floor((uploadedBytes / totalBytes) * 100));
|
|
116
|
+
}, []);
|
|
117
|
+
const uploadSingleFile = useCallback(async (type, task) => {
|
|
118
|
+
const callbacks = {
|
|
119
|
+
onProgress: (progress, bytesUploaded) => {
|
|
120
|
+
updateTask(task.id, {
|
|
121
|
+
progress,
|
|
122
|
+
bytesUploaded,
|
|
123
|
+
status: "uploading",
|
|
124
|
+
});
|
|
125
|
+
setUploadQueue((currentQueue) => {
|
|
126
|
+
const updatedQueue = currentQueue.map((q) => q.id === task.id ? { ...q, progress, bytesUploaded } : q);
|
|
127
|
+
updateOverallProgress(updatedQueue);
|
|
128
|
+
return updatedQueue;
|
|
129
|
+
});
|
|
130
|
+
},
|
|
131
|
+
onComplete: () => {
|
|
132
|
+
updateTask(task.id, { status: "completed", progress: 100 });
|
|
133
|
+
},
|
|
134
|
+
onError: (errorMessage) => {
|
|
135
|
+
updateTask(task.id, {
|
|
136
|
+
status: "error",
|
|
137
|
+
errorMessage,
|
|
138
|
+
progress: 0,
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
return createUploadPromise(type, task.file, callbacks);
|
|
143
|
+
}, [updateTask, updateOverallProgress]);
|
|
144
|
+
const uploadFiles = useCallback(async (type, files) => {
|
|
145
|
+
const initialQueue = files.map(createUploadTask);
|
|
146
|
+
setUploadQueue(initialQueue);
|
|
147
|
+
updateOverallProgress(initialQueue);
|
|
148
|
+
for (const task of initialQueue) {
|
|
149
|
+
try {
|
|
150
|
+
await uploadSingleFile(type, task);
|
|
151
|
+
}
|
|
152
|
+
catch (_error) { }
|
|
153
|
+
}
|
|
154
|
+
}, [uploadSingleFile, updateOverallProgress]);
|
|
155
|
+
const reset = useCallback(() => {
|
|
156
|
+
setUploadQueue([]);
|
|
157
|
+
setOverallProgress(0);
|
|
158
|
+
}, []);
|
|
159
|
+
return {
|
|
160
|
+
uploadQueue,
|
|
161
|
+
overallProgress,
|
|
162
|
+
uploadFiles,
|
|
163
|
+
reset,
|
|
164
|
+
getTotalBytes: () => uploadQueue.reduce((sum, task) => sum + task.totalBytes, 0),
|
|
165
|
+
getTotalBytesUploaded: () => uploadQueue.reduce((sum, task) => sum + task.bytesUploaded, 0),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { UseQueryOptions, UseQueryResult } from "@tanstack/react-query";
|
|
2
|
+
import { type NuqsQueryParams } from "./useNuqsQueryParams";
|
|
3
|
+
export interface DateRangeFilterReturn<TData> {
|
|
4
|
+
params: NuqsQueryParams;
|
|
5
|
+
query: UseQueryResult<TData>;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Flexible query options type that accepts both standard TanStack Query options
|
|
9
|
+
* and tRPC's queryOptions function return type.
|
|
10
|
+
* Uses permissive generics to handle type differences between TanStack Query and tRPC.
|
|
11
|
+
*/
|
|
12
|
+
type QueryOptionsLike<TData> = UseQueryOptions<TData, any, TData, any>;
|
|
13
|
+
/**
|
|
14
|
+
* Function type that accepts both standard query options functions and tRPC's queryOptions.
|
|
15
|
+
* tRPC's queryOptions accepts (input, opts?) while standard functions may only accept (input).
|
|
16
|
+
*/
|
|
17
|
+
type GetQueryOptionsFn<TInput, TData> = (input: TInput, ...args: any[]) => QueryOptionsLike<TData>;
|
|
18
|
+
export interface DateRangeFilterOptions<TInput, TData> {
|
|
19
|
+
getQueryOptions: GetQueryOptionsFn<TInput, TData>;
|
|
20
|
+
queryParams?: TInput;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Hook for charts with URL-synced query parameters and data fetching
|
|
24
|
+
* Similar to useNuqsTable but without rowSelection (not needed for charts)
|
|
25
|
+
*/
|
|
26
|
+
export declare const useDateRangeFilter: <TInput, TData>({ getQueryOptions, queryParams, }: DateRangeFilterOptions<TInput, TData>) => DateRangeFilterReturn<TData>;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=useDateRangeFilter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDateRangeFilter.d.ts","sourceRoot":"","sources":["../../../../../src/modules/table/hooks/useDateRangeFilter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGhF,MAAM,WAAW,qBAAqB,CAAC,KAAK;IAC1C,MAAM,EAAE,eAAe,CAAC;IACxB,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;CAC9B;AAED;;;;GAIG;AAEH,KAAK,gBAAgB,CAAC,KAAK,IAAI,eAAe,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;AAEvE;;;GAGG;AAEH,KAAK,iBAAiB,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,gBAAgB,CAAC,KAAK,CAAC,CAAC;AAEnG,MAAM,WAAW,sBAAsB,CAAC,MAAM,EAAE,KAAK;IACnD,eAAe,EAAE,iBAAiB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,EAAE,KAAK,EAAE,mCAG/C,sBAAsB,CAAC,MAAM,EAAE,KAAK,CAAC,KAAG,qBAAqB,CAAC,KAAK,CAgBrE,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useNuqsQueryParams } from "./useNuqsQueryParams";
|
|
2
|
+
import { useQueryWithParams } from "./useQueryWithParams";
|
|
3
|
+
/**
|
|
4
|
+
* Hook for charts with URL-synced query parameters and data fetching
|
|
5
|
+
* Similar to useNuqsTable but without rowSelection (not needed for charts)
|
|
6
|
+
*/
|
|
7
|
+
export const useDateRangeFilter = ({ getQueryOptions, queryParams = {}, }) => {
|
|
8
|
+
// Get only filters from URL query state
|
|
9
|
+
const { filters, setFilters, granularity, setGranularity } = useNuqsQueryParams();
|
|
10
|
+
// Get query result
|
|
11
|
+
const queryResult = useQueryWithParams({
|
|
12
|
+
getQueryOptions,
|
|
13
|
+
queryParams,
|
|
14
|
+
queryState: { filters },
|
|
15
|
+
});
|
|
16
|
+
return {
|
|
17
|
+
params: { filters, setFilters, granularity, setGranularity },
|
|
18
|
+
query: queryResult,
|
|
19
|
+
};
|
|
20
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { QueryFilters } from "@m5kdev/commons/modules/schemas/query.schema";
|
|
2
|
+
import type { PaginationState, SortingState, Updater } from "@tanstack/react-table";
|
|
3
|
+
export type Order = "asc" | "desc";
|
|
4
|
+
export type Granularity = "daily" | "weekly" | "monthly" | "yearly";
|
|
5
|
+
export interface NuqsQueryParams {
|
|
6
|
+
filters?: QueryFilters;
|
|
7
|
+
setFilters?: (filters: QueryFilters) => void;
|
|
8
|
+
granularity?: Granularity;
|
|
9
|
+
setGranularity?: (value: Granularity) => void;
|
|
10
|
+
sort?: string;
|
|
11
|
+
order?: Order | null;
|
|
12
|
+
setSorting?: (updater: Updater<SortingState>) => void;
|
|
13
|
+
sorting?: SortingState;
|
|
14
|
+
page?: number;
|
|
15
|
+
limit?: number;
|
|
16
|
+
setPagination?: (updater: Updater<PaginationState>) => void;
|
|
17
|
+
pagination?: PaginationState;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Hook to manage all query parameters via nuqs (URL query parameters)
|
|
21
|
+
* Manages: filters, sort, order, page, limit
|
|
22
|
+
* Reusable for any paginated/sorted/filtered lists
|
|
23
|
+
*/
|
|
24
|
+
export declare const useNuqsQueryParams: () => NuqsQueryParams;
|
|
25
|
+
//# sourceMappingURL=useNuqsQueryParams.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useNuqsQueryParams.d.ts","sourceRoot":"","sources":["../../../../../src/modules/table/hooks/useNuqsQueryParams.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8CAA8C,CAAC;AAEjF,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAcpF,MAAM,MAAM,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;AAEnC,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEpE,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAC;IAC7C,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,IAAI,CAAC;IACtD,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,KAAK,IAAI,CAAC;IAC5D,UAAU,CAAC,EAAE,eAAe,CAAC;CAC9B;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,QAAO,eA6FrC,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { filtersSchema } from "@m5kdev/commons/modules/schemas/query.schema";
|
|
2
|
+
import { parseAsInteger, parseAsJson, parseAsString, parseAsStringLiteral, useQueryState, } from "nuqs";
|
|
3
|
+
import { useCallback, useEffect, useMemo } from "react";
|
|
4
|
+
const parseAsFilters = parseAsJson((value) => filtersSchema.parse(value)).withDefault([]);
|
|
5
|
+
/**
|
|
6
|
+
* Hook to manage all query parameters via nuqs (URL query parameters)
|
|
7
|
+
* Manages: filters, sort, order, page, limit
|
|
8
|
+
* Reusable for any paginated/sorted/filtered lists
|
|
9
|
+
*/
|
|
10
|
+
export const useNuqsQueryParams = () => {
|
|
11
|
+
const [sort, setSort] = useQueryState("sort", parseAsString.withDefault(""));
|
|
12
|
+
const [order, setOrder] = useQueryState("order", parseAsStringLiteral(["asc", "desc"]));
|
|
13
|
+
const [page, setPage] = useQueryState("page", parseAsInteger.withDefault(1));
|
|
14
|
+
const [limit, setLimit] = useQueryState("limit", parseAsInteger.withDefault(10));
|
|
15
|
+
const [filters, setFilters] = useQueryState("filters", parseAsFilters);
|
|
16
|
+
const [granularity, setGranularity] = useQueryState("granularity", parseAsStringLiteral(["daily", "weekly", "monthly", "yearly"]).withDefault("daily"));
|
|
17
|
+
const sorting = useMemo(() => {
|
|
18
|
+
if (!sort) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
const effectiveOrder = order ?? "asc";
|
|
22
|
+
return [{ id: sort, desc: effectiveOrder === "desc" }];
|
|
23
|
+
}, [sort, order]);
|
|
24
|
+
const pagination = useMemo(() => ({ pageIndex: page - 1, pageSize: limit }), [page, limit]);
|
|
25
|
+
const setSorting = useCallback((updater) => {
|
|
26
|
+
// Build current sorting state from current values
|
|
27
|
+
const currentSorting = sort
|
|
28
|
+
? [{ id: sort, desc: (order ?? "asc") === "desc" }]
|
|
29
|
+
: [];
|
|
30
|
+
const next = typeof updater === "function" ? updater(currentSorting) : updater;
|
|
31
|
+
const first = next[0];
|
|
32
|
+
if (!first || !first.id) {
|
|
33
|
+
setSort("");
|
|
34
|
+
setOrder(null);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
setSort(first.id);
|
|
38
|
+
setOrder(first.desc ? "desc" : "asc");
|
|
39
|
+
}, [sort, order, setSort, setOrder]);
|
|
40
|
+
// Sync order to "asc" if sort is set but order is null (handles URL state inconsistencies)
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (sort && !order) {
|
|
43
|
+
setOrder("asc");
|
|
44
|
+
}
|
|
45
|
+
}, [sort, order, setOrder]);
|
|
46
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: dependent on page and limit only
|
|
47
|
+
const setPagination = useCallback((updater) => {
|
|
48
|
+
// TanStack Table uses 0-based indexing, but URL uses 1-based
|
|
49
|
+
const currentState = { pageIndex: page - 1, pageSize: limit };
|
|
50
|
+
const next = typeof updater === "function" ? updater(currentState) : updater;
|
|
51
|
+
// Convert back to 1-based for URL
|
|
52
|
+
setPage((next.pageIndex ?? 0) + 1);
|
|
53
|
+
setLimit(next.pageSize ?? 10);
|
|
54
|
+
}, [page, limit]);
|
|
55
|
+
return useMemo(() => ({
|
|
56
|
+
filters,
|
|
57
|
+
setFilters,
|
|
58
|
+
granularity,
|
|
59
|
+
setGranularity,
|
|
60
|
+
sort,
|
|
61
|
+
order,
|
|
62
|
+
setSorting,
|
|
63
|
+
sorting,
|
|
64
|
+
page,
|
|
65
|
+
limit,
|
|
66
|
+
setPagination,
|
|
67
|
+
pagination,
|
|
68
|
+
}), [
|
|
69
|
+
filters,
|
|
70
|
+
setFilters,
|
|
71
|
+
granularity,
|
|
72
|
+
setGranularity,
|
|
73
|
+
sort,
|
|
74
|
+
order,
|
|
75
|
+
setSorting,
|
|
76
|
+
sorting,
|
|
77
|
+
page,
|
|
78
|
+
limit,
|
|
79
|
+
setPagination,
|
|
80
|
+
pagination,
|
|
81
|
+
]);
|
|
82
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { UseQueryOptions, UseQueryResult } from "@tanstack/react-query";
|
|
2
|
+
import type { RowSelectionState, Updater } from "@tanstack/react-table";
|
|
3
|
+
import { type NuqsQueryParams } from "./useNuqsQueryParams";
|
|
4
|
+
export interface TableParams extends NuqsQueryParams {
|
|
5
|
+
rowSelection: RowSelectionState;
|
|
6
|
+
setRowSelection: (updater: Updater<RowSelectionState>) => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Alias for TableParams - used by TablePagination and other table components
|
|
10
|
+
*/
|
|
11
|
+
export type TableProps = TableParams;
|
|
12
|
+
export interface NuqsTableReturn<TData> {
|
|
13
|
+
params: TableParams;
|
|
14
|
+
query: UseQueryResult<TData>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Flexible query options type that accepts both standard TanStack Query options
|
|
18
|
+
* and tRPC's queryOptions function return type.
|
|
19
|
+
* Uses permissive generics to handle type differences between TanStack Query and tRPC.
|
|
20
|
+
*/
|
|
21
|
+
type QueryOptionsLike<TData> = UseQueryOptions<TData, any, TData, any>;
|
|
22
|
+
/**
|
|
23
|
+
* Function type that accepts both standard query options functions and tRPC's queryOptions.
|
|
24
|
+
* tRPC's queryOptions accepts (input, opts?) while standard functions may only accept (input).
|
|
25
|
+
*/
|
|
26
|
+
type GetQueryOptionsFn<TInput, TData> = (input: TInput, ...args: any[]) => QueryOptionsLike<TData>;
|
|
27
|
+
export interface NuqsTableOptions<TInput, TData> {
|
|
28
|
+
getQueryOptions: GetQueryOptionsFn<TInput, TData>;
|
|
29
|
+
queryParams?: TInput;
|
|
30
|
+
}
|
|
31
|
+
declare const useNuqsTable: <TInput, TData>({ getQueryOptions, queryParams, }: NuqsTableOptions<TInput, TData>) => NuqsTableReturn<TData>;
|
|
32
|
+
export default useNuqsTable;
|
|
33
|
+
//# sourceMappingURL=useNuqsTable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useNuqsTable.d.ts","sourceRoot":"","sources":["../../../../../src/modules/table/hooks/useNuqsTable.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,KAAK,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAExE,OAAO,EAAE,KAAK,eAAe,EAAsB,MAAM,sBAAsB,CAAC;AAGhF,MAAM,WAAW,WAAY,SAAQ,eAAe;IAClD,YAAY,EAAE,iBAAiB,CAAC;IAChC,eAAe,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;CAChE;AAED;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,WAAW,CAAC;AAErC,MAAM,WAAW,eAAe,CAAC,KAAK;IACpC,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;CAC9B;AAED;;;;GAIG;AAEH,KAAK,gBAAgB,CAAC,KAAK,IAAI,eAAe,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;AAEvE;;;GAGG;AAEH,KAAK,iBAAiB,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,gBAAgB,CAAC,KAAK,CAAC,CAAC;AAEnG,MAAM,WAAW,gBAAgB,CAAC,MAAM,EAAE,KAAK;IAC7C,eAAe,EAAE,iBAAiB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,QAAA,MAAM,YAAY,GAAI,MAAM,EAAE,KAAK,EAAE,mCAGlC,gBAAgB,CAAC,MAAM,EAAE,KAAK,CAAC,KAAG,eAAe,CAAC,KAAK,CAoCzD,CAAC;AAEF,eAAe,YAAY,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from "react";
|
|
2
|
+
import { useNuqsQueryParams } from "./useNuqsQueryParams";
|
|
3
|
+
import { useQueryWithParams } from "./useQueryWithParams";
|
|
4
|
+
const useNuqsTable = ({ getQueryOptions, queryParams = {}, }) => {
|
|
5
|
+
// Get all URL query state
|
|
6
|
+
const queryState = useNuqsQueryParams();
|
|
7
|
+
// Get query result
|
|
8
|
+
const queryResult = useQueryWithParams({
|
|
9
|
+
getQueryOptions,
|
|
10
|
+
queryParams,
|
|
11
|
+
queryState,
|
|
12
|
+
});
|
|
13
|
+
// Table-specific row selection state
|
|
14
|
+
const [rowSelection, setRowSelectionRaw] = useState({});
|
|
15
|
+
console.log("rowSelection", rowSelection);
|
|
16
|
+
const setRowSelection = useCallback((updater) => {
|
|
17
|
+
setRowSelectionRaw((prev) => {
|
|
18
|
+
const next = typeof updater === "function" ? updater(prev) : updater;
|
|
19
|
+
return next;
|
|
20
|
+
});
|
|
21
|
+
}, []);
|
|
22
|
+
const params = useMemo(() => ({
|
|
23
|
+
...queryState,
|
|
24
|
+
rowSelection,
|
|
25
|
+
setRowSelection,
|
|
26
|
+
}), [queryState, rowSelection, setRowSelection]);
|
|
27
|
+
return {
|
|
28
|
+
params,
|
|
29
|
+
query: queryResult,
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
export default useNuqsTable;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type UseQueryOptions, type UseQueryResult } from "@tanstack/react-query";
|
|
2
|
+
import type { NuqsQueryParams } from "./useNuqsQueryParams";
|
|
3
|
+
/**
|
|
4
|
+
* Flexible query options type that accepts both standard TanStack Query options
|
|
5
|
+
* and tRPC's queryOptions function return type.
|
|
6
|
+
* Uses permissive generics to handle type differences between TanStack Query and tRPC.
|
|
7
|
+
*/
|
|
8
|
+
type QueryOptionsLike<TData> = UseQueryOptions<TData, any, TData, any>;
|
|
9
|
+
/**
|
|
10
|
+
* Function type that accepts both standard query options functions and tRPC's queryOptions.
|
|
11
|
+
* tRPC's queryOptions accepts (input, opts?) while standard functions may only accept (input).
|
|
12
|
+
*/
|
|
13
|
+
type GetQueryOptionsFn<TInput, TData> = (input: TInput, ...args: any[]) => QueryOptionsLike<TData>;
|
|
14
|
+
export interface QueryWithParamsOptions<TInput, TData> {
|
|
15
|
+
getQueryOptions: GetQueryOptionsFn<TInput, TData>;
|
|
16
|
+
queryParams?: TInput;
|
|
17
|
+
queryState: Pick<NuqsQueryParams, "filters" | "sort" | "order" | "page" | "limit">;
|
|
18
|
+
enabled?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Hook to integrate query state with React Query
|
|
22
|
+
* Combines queryParams with queryState and manages data fetching
|
|
23
|
+
*/
|
|
24
|
+
export declare const useQueryWithParams: <TInput, TData>({ getQueryOptions, queryParams, queryState, enabled, }: QueryWithParamsOptions<TInput, TData>) => UseQueryResult<TData>;
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=useQueryWithParams.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useQueryWithParams.d.ts","sourceRoot":"","sources":["../../../../../src/modules/table/hooks/useQueryWithParams.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAY,MAAM,uBAAuB,CAAC;AAE5F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5D;;;;GAIG;AAEH,KAAK,gBAAgB,CAAC,KAAK,IAAI,eAAe,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;AAEvE;;;GAGG;AAEH,KAAK,iBAAiB,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,gBAAgB,CAAC,KAAK,CAAC,CAAC;AAEnG,MAAM,WAAW,sBAAsB,CAAC,MAAM,EAAE,KAAK;IACnD,eAAe,EAAE,iBAAiB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IACnF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,EAAE,KAAK,EAAE,wDAK/C,sBAAsB,CAAC,MAAM,EAAE,KAAK,CAAC,KAAG,cAAc,CAAC,KAAK,CA0B9D,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useQuery } from "@tanstack/react-query";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
/**
|
|
4
|
+
* Hook to integrate query state with React Query
|
|
5
|
+
* Combines queryParams with queryState and manages data fetching
|
|
6
|
+
*/
|
|
7
|
+
export const useQueryWithParams = ({ getQueryOptions, queryParams = {}, queryState, enabled = true, }) => {
|
|
8
|
+
const { filters, sort, order, page, limit } = queryState;
|
|
9
|
+
const input = useMemo(() => ({
|
|
10
|
+
...queryParams,
|
|
11
|
+
page,
|
|
12
|
+
limit,
|
|
13
|
+
sort,
|
|
14
|
+
order: order ?? undefined,
|
|
15
|
+
filters,
|
|
16
|
+
}), [queryParams, page, limit, sort, order, filters]);
|
|
17
|
+
const queryOptions = useMemo(() => ({
|
|
18
|
+
...getQueryOptions(input),
|
|
19
|
+
placeholderData: (previousData) => previousData,
|
|
20
|
+
enabled,
|
|
21
|
+
}), [getQueryOptions, input, enabled]);
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
23
|
+
return useQuery(queryOptions);
|
|
24
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAEpE,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { DateValue, RangeValue } from "@heroui/react";
|
|
2
|
+
import { CalendarDate } from "@internationalized/date";
|
|
3
|
+
import type { QueryFilter } from "@m5kdev/commons/modules/schemas/query.schema";
|
|
4
|
+
/**
|
|
5
|
+
* Convert CalendarDate to UTC ISO string at midnight UTC
|
|
6
|
+
*/
|
|
7
|
+
export declare const calendarDateToUTC: (date: CalendarDate) => string;
|
|
8
|
+
/**
|
|
9
|
+
* Convert CalendarDate to UTC ISO string at end of day UTC
|
|
10
|
+
*/
|
|
11
|
+
export declare const calendarDateToUTCEndOfDay: (date: CalendarDate) => string;
|
|
12
|
+
/**
|
|
13
|
+
* Convert RangeValue<DateValue> to URL string format "startISO,endISO"
|
|
14
|
+
*/
|
|
15
|
+
export declare const rangeToUrlString: (range: RangeValue<DateValue> | null) => string | null;
|
|
16
|
+
/**
|
|
17
|
+
* Convert URL string "startISO,endISO" to RangeValue<DateValue>
|
|
18
|
+
*/
|
|
19
|
+
export declare const urlStringToRange: (value: string | null) => RangeValue<DateValue> | null;
|
|
20
|
+
/**
|
|
21
|
+
* Convert date range to QueryFilter format
|
|
22
|
+
*/
|
|
23
|
+
export declare const dateRangeToFilter: (dateRange: RangeValue<DateValue> | null, columnId?: string) => QueryFilter | null;
|
|
24
|
+
//# sourceMappingURL=date.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date.d.ts","sourceRoot":"","sources":["../../../src/utils/date.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAmC,MAAM,yBAAyB,CAAC;AACxF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8CAA8C,CAAC;AAGhF;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAAI,MAAM,YAAY,KAAG,MAEtD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,yBAAyB,GAAI,MAAM,YAAY,KAAG,MAE9D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,UAAU,CAAC,SAAS,CAAC,GAAG,IAAI,KAAG,MAAM,GAAG,IAa/E,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,MAAM,GAAG,IAAI,KAAG,UAAU,CAAC,SAAS,CAAC,GAAG,IAqB/E,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAC5B,WAAW,UAAU,CAAC,SAAS,CAAC,GAAG,IAAI,EACvC,iBAAsB,KACrB,WAAW,GAAG,IAkBhB,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { CalendarDate, getLocalTimeZone, parseAbsolute } from "@internationalized/date";
|
|
2
|
+
import { DateTime } from "luxon";
|
|
3
|
+
/**
|
|
4
|
+
* Convert CalendarDate to UTC ISO string at midnight UTC
|
|
5
|
+
*/
|
|
6
|
+
export const calendarDateToUTC = (date) => {
|
|
7
|
+
return DateTime.utc(date.year, date.month, date.day).toISO() ?? "";
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Convert CalendarDate to UTC ISO string at end of day UTC
|
|
11
|
+
*/
|
|
12
|
+
export const calendarDateToUTCEndOfDay = (date) => {
|
|
13
|
+
return DateTime.utc(date.year, date.month, date.day).endOf("day").toISO() ?? "";
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Convert RangeValue<DateValue> to URL string format "startISO,endISO"
|
|
17
|
+
*/
|
|
18
|
+
export const rangeToUrlString = (range) => {
|
|
19
|
+
if (!range || !range.start || !range.end)
|
|
20
|
+
return null;
|
|
21
|
+
const start = range.start;
|
|
22
|
+
const end = range.end;
|
|
23
|
+
if (!start?.year || !end?.year)
|
|
24
|
+
return null;
|
|
25
|
+
// Format as ISO date strings (YYYY-MM-DD)
|
|
26
|
+
const startISO = `${start.year}-${String(start.month).padStart(2, "0")}-${String(start.day).padStart(2, "0")}`;
|
|
27
|
+
const endISO = `${end.year}-${String(end.month).padStart(2, "0")}-${String(end.day).padStart(2, "0")}`;
|
|
28
|
+
return `${startISO},${endISO}`;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Convert URL string "startISO,endISO" to RangeValue<DateValue>
|
|
32
|
+
*/
|
|
33
|
+
export const urlStringToRange = (value) => {
|
|
34
|
+
if (!value)
|
|
35
|
+
return null;
|
|
36
|
+
const [startISO, endISO] = value.split(",");
|
|
37
|
+
if (!startISO || !endISO)
|
|
38
|
+
return null;
|
|
39
|
+
const tz = getLocalTimeZone();
|
|
40
|
+
// Parse ISO date strings using parseAbsolute for proper timezone handling
|
|
41
|
+
const startZoned = parseAbsolute(`${startISO}T00:00:00Z`, tz);
|
|
42
|
+
const endZoned = parseAbsolute(`${endISO}T00:00:00Z`, tz);
|
|
43
|
+
if (!startZoned || !endZoned)
|
|
44
|
+
return null;
|
|
45
|
+
const startDate = new CalendarDate(startZoned.year, startZoned.month, startZoned.day);
|
|
46
|
+
const endDate = new CalendarDate(endZoned.year, endZoned.month, endZoned.day);
|
|
47
|
+
return {
|
|
48
|
+
start: startDate,
|
|
49
|
+
end: endDate,
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Convert date range to QueryFilter format
|
|
54
|
+
*/
|
|
55
|
+
export const dateRangeToFilter = (dateRange, columnId = "startedAt") => {
|
|
56
|
+
if (!dateRange || !dateRange.start || !dateRange.end)
|
|
57
|
+
return null;
|
|
58
|
+
const start = dateRange.start;
|
|
59
|
+
const end = dateRange.end;
|
|
60
|
+
if (!start?.year || !end?.year)
|
|
61
|
+
return null;
|
|
62
|
+
const startISO = calendarDateToUTC(start);
|
|
63
|
+
const endISO = calendarDateToUTCEndOfDay(end);
|
|
64
|
+
return {
|
|
65
|
+
columnId,
|
|
66
|
+
type: "date",
|
|
67
|
+
method: "between",
|
|
68
|
+
value: startISO,
|
|
69
|
+
valueTo: endISO,
|
|
70
|
+
};
|
|
71
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
declare class QueryCache {
|
|
2
|
+
update: <T extends Record<K, any>, A extends string | null = null, K extends string = "id">(element: T | T[], accessor?: A, key?: K) => <D>(data: D) => D;
|
|
3
|
+
delete: <T extends Record<K, any>, D extends (A extends string ? { [key in A]: T[]; } : T[]) | undefined, A extends string | null = null, K extends string = "id">(element: T | T[], accessor?: A, key?: K) => <OriginalD extends D>(data: OriginalD) => OriginalD;
|
|
4
|
+
create: <T extends Record<string, any>, D extends (A extends string ? { [key in A]: T[]; } : T[]) | undefined, A extends string | null = null>(element: T | T[], accessor?: A) => <OriginalD extends D>(data: OriginalD) => OriginalD;
|
|
5
|
+
}
|
|
6
|
+
export declare const queryCache: QueryCache;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../../src/utils/query.ts"],"names":[],"mappings":"AAAA,cAAM,UAAU;IACd,MAAM,GACH,CAAC,SAAS,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,SAAS,MAAM,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC,SAAS,MAAM,GAAG,IAAI,EAChF,SAAS,CAAC,GAAG,CAAC,EAAE,EAChB,WAAU,CAAa,EACvB,MAAK,CAAa,MAEnB,CAAC,EAAE,MAAM,CAAC,KAAG,CAAC,CAiBb;IAEJ,MAAM,GAEF,CAAC,SAAS,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,EACxB,CAAC,SAAS,CAAC,CAAC,SAAS,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,GAAE,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EACpE,CAAC,SAAS,MAAM,GAAG,IAAI,GAAG,IAAI,EAC9B,CAAC,SAAS,MAAM,GAAG,IAAI,EAEvB,SAAS,CAAC,GAAG,CAAC,EAAE,EAChB,WAAU,CAAa,EACvB,MAAK,CAAa,MAEnB,SAAS,SAAS,CAAC,EAAE,MAAM,SAAS,KAAG,SAAS,CAY/C;IAEJ,MAAM,GAEF,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC7B,CAAC,SAAS,CAAC,CAAC,SAAS,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,GAAE,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EACpE,CAAC,SAAS,MAAM,GAAG,IAAI,GAAG,IAAI,EAE9B,SAAS,CAAC,GAAG,CAAC,EAAE,EAChB,WAAU,CAAa,MAExB,SAAS,SAAS,CAAC,EAAE,MAAM,SAAS,KAAG,SAAS,CAQ/C;CACL;AAED,eAAO,MAAM,UAAU,YAAmB,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
class QueryCache {
|
|
2
|
+
update = (element, accessor = null, key = "id") => (data) => {
|
|
3
|
+
if (!data)
|
|
4
|
+
return data;
|
|
5
|
+
const target = accessor ? data[accessor] : data;
|
|
6
|
+
if (!Array.isArray(target))
|
|
7
|
+
return data;
|
|
8
|
+
const modified = target.map((item) => {
|
|
9
|
+
const isMatch = Array.isArray(element)
|
|
10
|
+
? element.findIndex((el) => el[key] === item[key]) > -1
|
|
11
|
+
: element[key] === item[key];
|
|
12
|
+
return isMatch
|
|
13
|
+
? {
|
|
14
|
+
...item,
|
|
15
|
+
...(Array.isArray(element) ? element.find((el) => el[key] === item[key]) : element),
|
|
16
|
+
}
|
|
17
|
+
: item;
|
|
18
|
+
});
|
|
19
|
+
return accessor ? { ...data, [accessor]: modified } : modified;
|
|
20
|
+
};
|
|
21
|
+
delete = (element, accessor = null, key = "id") => (data) => {
|
|
22
|
+
if (!data)
|
|
23
|
+
return data;
|
|
24
|
+
const target = (accessor ? data[accessor] : data);
|
|
25
|
+
if (!Array.isArray(target))
|
|
26
|
+
return data;
|
|
27
|
+
const modified = target.filter((item) => Array.isArray(element)
|
|
28
|
+
? element.findIndex((el) => el[key] === item[key]) === -1
|
|
29
|
+
: item[key] !== element[key]);
|
|
30
|
+
return accessor
|
|
31
|
+
? { ...data, [accessor]: modified }
|
|
32
|
+
: modified;
|
|
33
|
+
};
|
|
34
|
+
create = (element, accessor = null) => (data) => {
|
|
35
|
+
if (!data)
|
|
36
|
+
return data;
|
|
37
|
+
const target = (accessor ? data[accessor] : data);
|
|
38
|
+
if (!target)
|
|
39
|
+
return data;
|
|
40
|
+
const modified = [...target, ...(Array.isArray(element) ? element : [element])];
|
|
41
|
+
return accessor
|
|
42
|
+
? { ...data, [accessor]: modified }
|
|
43
|
+
: modified;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export const queryCache = new QueryCache();
|