@m5kdev/frontend 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/modules/auth/auth.lib.d.ts +0 -1
- package/dist/src/modules/auth/components/AuthProvider.d.ts +0 -1
- package/dist/src/modules/auth/hooks/useAuth.d.ts +0 -1
- package/dist/src/modules/auth/hooks/useAuthAdmin.d.ts +0 -1
- package/dist/src/modules/auth/hooks/useSession.d.ts +0 -1
- package/dist/src/modules/billing/components/BillingProvider.d.ts +0 -1
- package/dist/src/modules/billing/hooks/useSubscription.d.ts +0 -1
- package/dist/src/modules/file/hooks/useS3DownloadUrl.d.ts +0 -1
- package/dist/src/modules/file/hooks/useS3Upload.d.ts +0 -1
- package/dist/src/modules/file/hooks/useUpload.d.ts +0 -1
- package/dist/src/modules/table/hooks/useDateRangeFilter.d.ts +0 -1
- package/dist/src/modules/table/hooks/useNuqsQueryParams.d.ts +0 -1
- package/dist/src/modules/table/hooks/useNuqsTable.d.ts +0 -1
- package/dist/src/modules/table/hooks/useQueryWithParams.d.ts +0 -1
- package/dist/src/types.d.ts +0 -1
- package/dist/src/utils/date.d.ts +0 -1
- package/dist/src/utils/query.d.ts +0 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +7 -4
- package/.cursor/rules/frontend.mdc +0 -49
- package/.turbo/turbo-build.log +0 -5
- package/.turbo/turbo-check-types.log +0 -5
- package/.turbo/turbo-lint$colon$fix.log +0 -101
- package/.turbo/turbo-lint.log +0 -162
- package/CHANGELOG.md +0 -33
- package/dist/src/modules/auth/auth.lib.d.ts.map +0 -1
- package/dist/src/modules/auth/components/AuthProvider.d.ts.map +0 -1
- package/dist/src/modules/auth/hooks/useAuth.d.ts.map +0 -1
- package/dist/src/modules/auth/hooks/useAuthAdmin.d.ts.map +0 -1
- package/dist/src/modules/auth/hooks/useSession.d.ts.map +0 -1
- package/dist/src/modules/billing/components/BillingProvider.d.ts.map +0 -1
- package/dist/src/modules/billing/hooks/useSubscription.d.ts.map +0 -1
- package/dist/src/modules/file/hooks/useS3DownloadUrl.d.ts.map +0 -1
- package/dist/src/modules/file/hooks/useS3Upload.d.ts.map +0 -1
- package/dist/src/modules/file/hooks/useUpload.d.ts.map +0 -1
- package/dist/src/modules/table/hooks/useDateRangeFilter.d.ts.map +0 -1
- package/dist/src/modules/table/hooks/useNuqsQueryParams.d.ts.map +0 -1
- package/dist/src/modules/table/hooks/useNuqsTable.d.ts.map +0 -1
- package/dist/src/modules/table/hooks/useQueryWithParams.d.ts.map +0 -1
- package/dist/src/types.d.ts.map +0 -1
- package/dist/src/utils/date.d.ts.map +0 -1
- package/dist/src/utils/query.d.ts.map +0 -1
- package/src/modules/auth/auth.lib.ts +0 -49
- package/src/modules/auth/components/AuthProvider.tsx +0 -105
- package/src/modules/auth/hooks/useAuth.ts +0 -20
- package/src/modules/auth/hooks/useAuthAdmin.ts +0 -188
- package/src/modules/auth/hooks/useSession.ts +0 -6
- package/src/modules/billing/components/BillingProvider.tsx +0 -58
- package/src/modules/billing/hooks/useSubscription.ts +0 -6
- package/src/modules/file/hooks/useS3DownloadUrl.ts +0 -18
- package/src/modules/file/hooks/useS3Upload.ts +0 -89
- package/src/modules/file/hooks/useUpload.ts +0 -220
- package/src/modules/table/hooks/useDateRangeFilter.ts +0 -55
- package/src/modules/table/hooks/useNuqsQueryParams.ts +0 -134
- package/src/modules/table/hooks/useNuqsTable.ts +0 -83
- package/src/modules/table/hooks/useQueryWithParams.ts +0 -62
- package/src/types.ts +0 -4
- package/src/utils/date.ts +0 -88
- package/src/utils/query.ts +0 -72
- package/src/vite-env.d.ts +0 -1
- package/tsconfig.json +0 -29
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
import { useCallback, useState } from "react";
|
|
2
|
-
|
|
3
|
-
export type UploadStatus = "pending" | "uploading" | "completed" | "error";
|
|
4
|
-
|
|
5
|
-
export interface UploadFileTask {
|
|
6
|
-
id: string;
|
|
7
|
-
file: File;
|
|
8
|
-
progress: number;
|
|
9
|
-
status: UploadStatus;
|
|
10
|
-
errorMessage?: string;
|
|
11
|
-
bytesUploaded: number;
|
|
12
|
-
totalBytes: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface UploadCallbacks {
|
|
16
|
-
onProgress: (progress: number, bytesUploaded: number) => void;
|
|
17
|
-
onComplete: () => void;
|
|
18
|
-
onError: (error: string) => void;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Shared utility function to create upload promise
|
|
22
|
-
const createUploadPromise = <T>(
|
|
23
|
-
type: string,
|
|
24
|
-
file: File,
|
|
25
|
-
callbacks: UploadCallbacks
|
|
26
|
-
): Promise<T> => {
|
|
27
|
-
return new Promise((resolve, reject) => {
|
|
28
|
-
const xhr = new XMLHttpRequest();
|
|
29
|
-
|
|
30
|
-
xhr.upload.onprogress = (event: ProgressEvent<EventTarget>) => {
|
|
31
|
-
if (event.lengthComputable) {
|
|
32
|
-
const progress = Math.round((event.loaded * 100) / event.total);
|
|
33
|
-
callbacks.onProgress(progress, event.loaded);
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
xhr.onload = () => {
|
|
38
|
-
if (xhr.status === 200) {
|
|
39
|
-
callbacks.onComplete();
|
|
40
|
-
// if response has json header, parse it
|
|
41
|
-
if (xhr.getResponseHeader("Content-Type")?.includes("application/json")) {
|
|
42
|
-
resolve(JSON.parse(xhr.response) as T);
|
|
43
|
-
} else {
|
|
44
|
-
resolve(xhr.response as T);
|
|
45
|
-
}
|
|
46
|
-
} else {
|
|
47
|
-
const errorMessage = `Upload failed with status ${xhr.status}`;
|
|
48
|
-
callbacks.onError(errorMessage);
|
|
49
|
-
reject(new Error(errorMessage));
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
xhr.onerror = () => {
|
|
54
|
-
const errorMessage = "Network error during upload";
|
|
55
|
-
callbacks.onError(errorMessage);
|
|
56
|
-
reject(new Error(errorMessage));
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
xhr.open("POST", `${import.meta.env.VITE_SERVER_URL}/upload/file/${type}`);
|
|
60
|
-
const formData = new FormData();
|
|
61
|
-
formData.append("file", file);
|
|
62
|
-
xhr.send(formData);
|
|
63
|
-
});
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
// Shared utility to create a task
|
|
67
|
-
const createUploadTask = (file: File): UploadFileTask => ({
|
|
68
|
-
id:
|
|
69
|
-
typeof crypto !== "undefined" && crypto.randomUUID
|
|
70
|
-
? crypto.randomUUID()
|
|
71
|
-
: Math.random().toString(36).substr(2, 9),
|
|
72
|
-
file,
|
|
73
|
-
progress: 0,
|
|
74
|
-
status: "pending",
|
|
75
|
-
bytesUploaded: 0,
|
|
76
|
-
totalBytes: file.size,
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// Hook for single file upload
|
|
80
|
-
export function useFileUpload() {
|
|
81
|
-
const [uploadTask, setUploadTask] = useState<UploadFileTask | null>(null);
|
|
82
|
-
|
|
83
|
-
const upload = useCallback(async <T>(type: string, file: File): Promise<T> => {
|
|
84
|
-
const task = createUploadTask(file);
|
|
85
|
-
setUploadTask(task);
|
|
86
|
-
|
|
87
|
-
const callbacks: UploadCallbacks = {
|
|
88
|
-
onProgress: (progress, bytesUploaded) => {
|
|
89
|
-
setUploadTask((prev) =>
|
|
90
|
-
prev
|
|
91
|
-
? {
|
|
92
|
-
...prev,
|
|
93
|
-
progress,
|
|
94
|
-
bytesUploaded,
|
|
95
|
-
status: "uploading",
|
|
96
|
-
}
|
|
97
|
-
: null
|
|
98
|
-
);
|
|
99
|
-
},
|
|
100
|
-
onComplete: () => {
|
|
101
|
-
setUploadTask((prev) => (prev ? { ...prev, status: "completed", progress: 100 } : null));
|
|
102
|
-
},
|
|
103
|
-
onError: (errorMessage) => {
|
|
104
|
-
setUploadTask((prev) =>
|
|
105
|
-
prev
|
|
106
|
-
? {
|
|
107
|
-
...prev,
|
|
108
|
-
status: "error",
|
|
109
|
-
errorMessage,
|
|
110
|
-
progress: 0,
|
|
111
|
-
}
|
|
112
|
-
: null
|
|
113
|
-
);
|
|
114
|
-
},
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
return createUploadPromise<T>(type, file, callbacks);
|
|
118
|
-
}, []);
|
|
119
|
-
|
|
120
|
-
const reset = useCallback(() => {
|
|
121
|
-
setUploadTask(null);
|
|
122
|
-
}, []);
|
|
123
|
-
|
|
124
|
-
return {
|
|
125
|
-
...uploadTask,
|
|
126
|
-
upload,
|
|
127
|
-
reset,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Hook for multiple file uploads
|
|
132
|
-
export function useMultipartUpload() {
|
|
133
|
-
const [uploadQueue, setUploadQueue] = useState<UploadFileTask[]>([]);
|
|
134
|
-
const [overallProgress, setOverallProgress] = useState<number>(0);
|
|
135
|
-
|
|
136
|
-
const updateTask = useCallback((id: string, changes: Partial<UploadFileTask>) => {
|
|
137
|
-
setUploadQueue((prev) => prev.map((task) => (task.id === id ? { ...task, ...changes } : task)));
|
|
138
|
-
}, []);
|
|
139
|
-
|
|
140
|
-
const updateOverallProgress = useCallback((queue: UploadFileTask[]) => {
|
|
141
|
-
if (queue.length === 0) {
|
|
142
|
-
setOverallProgress(100);
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
const totalBytes = queue.reduce((sum, task) => sum + task.file.size, 0);
|
|
146
|
-
const uploadedBytes = queue.reduce((sum, task) => {
|
|
147
|
-
if (task.status === "completed") {
|
|
148
|
-
return sum + task.file.size;
|
|
149
|
-
}
|
|
150
|
-
if (task.status === "uploading") {
|
|
151
|
-
return sum + (task.progress / 100) * task.file.size;
|
|
152
|
-
}
|
|
153
|
-
return sum;
|
|
154
|
-
}, 0);
|
|
155
|
-
setOverallProgress(Math.floor((uploadedBytes / totalBytes) * 100));
|
|
156
|
-
}, []);
|
|
157
|
-
|
|
158
|
-
const uploadSingleFile = useCallback(
|
|
159
|
-
async <T>(type: string, task: UploadFileTask): Promise<T> => {
|
|
160
|
-
const callbacks: UploadCallbacks = {
|
|
161
|
-
onProgress: (progress, bytesUploaded) => {
|
|
162
|
-
updateTask(task.id, {
|
|
163
|
-
progress,
|
|
164
|
-
bytesUploaded,
|
|
165
|
-
status: "uploading",
|
|
166
|
-
});
|
|
167
|
-
setUploadQueue((currentQueue) => {
|
|
168
|
-
const updatedQueue = currentQueue.map((q) =>
|
|
169
|
-
q.id === task.id ? { ...q, progress, bytesUploaded } : q
|
|
170
|
-
);
|
|
171
|
-
updateOverallProgress(updatedQueue);
|
|
172
|
-
return updatedQueue;
|
|
173
|
-
});
|
|
174
|
-
},
|
|
175
|
-
onComplete: () => {
|
|
176
|
-
updateTask(task.id, { status: "completed", progress: 100 });
|
|
177
|
-
},
|
|
178
|
-
onError: (errorMessage) => {
|
|
179
|
-
updateTask(task.id, {
|
|
180
|
-
status: "error",
|
|
181
|
-
errorMessage,
|
|
182
|
-
progress: 0,
|
|
183
|
-
});
|
|
184
|
-
},
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
return createUploadPromise<T>(type, task.file, callbacks);
|
|
188
|
-
},
|
|
189
|
-
[updateTask, updateOverallProgress]
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
const uploadFiles = useCallback(
|
|
193
|
-
async (type: string, files: File[]) => {
|
|
194
|
-
const initialQueue = files.map(createUploadTask);
|
|
195
|
-
setUploadQueue(initialQueue);
|
|
196
|
-
updateOverallProgress(initialQueue);
|
|
197
|
-
|
|
198
|
-
for (const task of initialQueue) {
|
|
199
|
-
try {
|
|
200
|
-
await uploadSingleFile(type, task);
|
|
201
|
-
} catch (_error) {}
|
|
202
|
-
}
|
|
203
|
-
},
|
|
204
|
-
[uploadSingleFile, updateOverallProgress]
|
|
205
|
-
);
|
|
206
|
-
|
|
207
|
-
const reset = useCallback(() => {
|
|
208
|
-
setUploadQueue([]);
|
|
209
|
-
setOverallProgress(0);
|
|
210
|
-
}, []);
|
|
211
|
-
|
|
212
|
-
return {
|
|
213
|
-
uploadQueue,
|
|
214
|
-
overallProgress,
|
|
215
|
-
uploadFiles,
|
|
216
|
-
reset,
|
|
217
|
-
getTotalBytes: () => uploadQueue.reduce((sum, task) => sum + task.totalBytes, 0),
|
|
218
|
-
getTotalBytesUploaded: () => uploadQueue.reduce((sum, task) => sum + task.bytesUploaded, 0),
|
|
219
|
-
};
|
|
220
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import type { UseQueryOptions, UseQueryResult } from "@tanstack/react-query";
|
|
2
|
-
import { useNuqsQueryParams, type NuqsQueryParams } from "./useNuqsQueryParams";
|
|
3
|
-
import { useQueryWithParams } from "./useQueryWithParams";
|
|
4
|
-
|
|
5
|
-
export interface DateRangeFilterReturn<TData> {
|
|
6
|
-
params: NuqsQueryParams;
|
|
7
|
-
query: UseQueryResult<TData>;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Flexible query options type that accepts both standard TanStack Query options
|
|
12
|
-
* and tRPC's queryOptions function return type.
|
|
13
|
-
* Uses permissive generics to handle type differences between TanStack Query and tRPC.
|
|
14
|
-
*/
|
|
15
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
-
type QueryOptionsLike<TData> = UseQueryOptions<TData, any, TData, any>;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Function type that accepts both standard query options functions and tRPC's queryOptions.
|
|
20
|
-
* tRPC's queryOptions accepts (input, opts?) while standard functions may only accept (input).
|
|
21
|
-
*/
|
|
22
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
23
|
-
type GetQueryOptionsFn<TInput, TData> = (input: TInput, ...args: any[]) => QueryOptionsLike<TData>;
|
|
24
|
-
|
|
25
|
-
export interface DateRangeFilterOptions<TInput, TData> {
|
|
26
|
-
getQueryOptions: GetQueryOptionsFn<TInput, TData>;
|
|
27
|
-
queryParams?: TInput;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Hook for charts with URL-synced query parameters and data fetching
|
|
32
|
-
* Similar to useNuqsTable but without rowSelection (not needed for charts)
|
|
33
|
-
*/
|
|
34
|
-
export const useDateRangeFilter = <TInput, TData>({
|
|
35
|
-
getQueryOptions,
|
|
36
|
-
queryParams = {} as TInput,
|
|
37
|
-
}: DateRangeFilterOptions<TInput, TData>): DateRangeFilterReturn<TData> => {
|
|
38
|
-
// Get only filters from URL query state
|
|
39
|
-
const { filters, setFilters, granularity, setGranularity } = useNuqsQueryParams();
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// Get query result
|
|
43
|
-
const queryResult = useQueryWithParams({
|
|
44
|
-
getQueryOptions,
|
|
45
|
-
queryParams,
|
|
46
|
-
queryState: { filters },
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
params: { filters, setFilters, granularity, setGranularity },
|
|
51
|
-
query: queryResult,
|
|
52
|
-
};
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import type { QueryFilters } from "@m5kdev/commons/modules/schemas/query.schema";
|
|
2
|
-
import { filtersSchema } from "@m5kdev/commons/modules/schemas/query.schema";
|
|
3
|
-
import type { PaginationState, SortingState, Updater } from "@tanstack/react-table";
|
|
4
|
-
import {
|
|
5
|
-
parseAsInteger,
|
|
6
|
-
parseAsJson,
|
|
7
|
-
parseAsString,
|
|
8
|
-
parseAsStringLiteral,
|
|
9
|
-
useQueryState,
|
|
10
|
-
} from "nuqs";
|
|
11
|
-
import { useCallback, useEffect, useMemo } from "react";
|
|
12
|
-
|
|
13
|
-
const parseAsFilters = parseAsJson<QueryFilters>((value) => filtersSchema.parse(value)).withDefault(
|
|
14
|
-
[]
|
|
15
|
-
);
|
|
16
|
-
|
|
17
|
-
export type Order = "asc" | "desc";
|
|
18
|
-
|
|
19
|
-
export type Granularity = "daily" | "weekly" | "monthly" | "yearly";
|
|
20
|
-
|
|
21
|
-
export interface NuqsQueryParams {
|
|
22
|
-
filters?: QueryFilters;
|
|
23
|
-
setFilters?: (filters: QueryFilters) => void;
|
|
24
|
-
granularity?: Granularity;
|
|
25
|
-
setGranularity?: (value: Granularity) => void;
|
|
26
|
-
sort?: string;
|
|
27
|
-
order?: Order | null;
|
|
28
|
-
setSorting?: (updater: Updater<SortingState>) => void;
|
|
29
|
-
sorting?: SortingState;
|
|
30
|
-
page?: number;
|
|
31
|
-
limit?: number;
|
|
32
|
-
setPagination?: (updater: Updater<PaginationState>) => void;
|
|
33
|
-
pagination?: PaginationState;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Hook to manage all query parameters via nuqs (URL query parameters)
|
|
38
|
-
* Manages: filters, sort, order, page, limit
|
|
39
|
-
* Reusable for any paginated/sorted/filtered lists
|
|
40
|
-
*/
|
|
41
|
-
export const useNuqsQueryParams = (): NuqsQueryParams => {
|
|
42
|
-
const [sort, setSort] = useQueryState<string>("sort", parseAsString.withDefault(""));
|
|
43
|
-
const [order, setOrder] = useQueryState<Order>("order", parseAsStringLiteral(["asc", "desc"]));
|
|
44
|
-
const [page, setPage] = useQueryState<number>("page", parseAsInteger.withDefault(1));
|
|
45
|
-
const [limit, setLimit] = useQueryState<number>("limit", parseAsInteger.withDefault(10));
|
|
46
|
-
const [filters, setFilters] = useQueryState<QueryFilters>("filters", parseAsFilters);
|
|
47
|
-
const [granularity, setGranularity] = useQueryState<Granularity>(
|
|
48
|
-
"granularity",
|
|
49
|
-
parseAsStringLiteral(["daily", "weekly", "monthly", "yearly"]).withDefault("daily")
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
const sorting = useMemo(() => {
|
|
53
|
-
if (!sort) {
|
|
54
|
-
return [];
|
|
55
|
-
}
|
|
56
|
-
const effectiveOrder = order ?? "asc";
|
|
57
|
-
return [{ id: sort, desc: effectiveOrder === "desc" }];
|
|
58
|
-
}, [sort, order]);
|
|
59
|
-
|
|
60
|
-
const pagination = useMemo(() => ({ pageIndex: page - 1, pageSize: limit }), [page, limit]);
|
|
61
|
-
|
|
62
|
-
const setSorting = useCallback(
|
|
63
|
-
(updater: Updater<SortingState>) => {
|
|
64
|
-
// Build current sorting state from current values
|
|
65
|
-
const currentSorting: SortingState = sort
|
|
66
|
-
? [{ id: sort, desc: (order ?? "asc") === "desc" }]
|
|
67
|
-
: [];
|
|
68
|
-
|
|
69
|
-
const next = typeof updater === "function" ? updater(currentSorting) : updater;
|
|
70
|
-
const first = next[0];
|
|
71
|
-
|
|
72
|
-
if (!first || !first.id) {
|
|
73
|
-
setSort("");
|
|
74
|
-
setOrder(null);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
setSort(first.id);
|
|
79
|
-
setOrder(first.desc ? "desc" : "asc");
|
|
80
|
-
},
|
|
81
|
-
[sort, order, setSort, setOrder]
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
// Sync order to "asc" if sort is set but order is null (handles URL state inconsistencies)
|
|
85
|
-
useEffect(() => {
|
|
86
|
-
if (sort && !order) {
|
|
87
|
-
setOrder("asc");
|
|
88
|
-
}
|
|
89
|
-
}, [sort, order, setOrder]);
|
|
90
|
-
|
|
91
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: dependent on page and limit only
|
|
92
|
-
const setPagination = useCallback(
|
|
93
|
-
(updater: Updater<PaginationState>) => {
|
|
94
|
-
// TanStack Table uses 0-based indexing, but URL uses 1-based
|
|
95
|
-
const currentState = { pageIndex: page - 1, pageSize: limit };
|
|
96
|
-
const next = typeof updater === "function" ? updater(currentState) : updater;
|
|
97
|
-
// Convert back to 1-based for URL
|
|
98
|
-
setPage((next.pageIndex ?? 0) + 1);
|
|
99
|
-
setLimit(next.pageSize ?? 10);
|
|
100
|
-
},
|
|
101
|
-
[page, limit]
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
return useMemo(
|
|
105
|
-
() => ({
|
|
106
|
-
filters,
|
|
107
|
-
setFilters,
|
|
108
|
-
granularity,
|
|
109
|
-
setGranularity,
|
|
110
|
-
sort,
|
|
111
|
-
order,
|
|
112
|
-
setSorting,
|
|
113
|
-
sorting,
|
|
114
|
-
page,
|
|
115
|
-
limit,
|
|
116
|
-
setPagination,
|
|
117
|
-
pagination,
|
|
118
|
-
}),
|
|
119
|
-
[
|
|
120
|
-
filters,
|
|
121
|
-
setFilters,
|
|
122
|
-
granularity,
|
|
123
|
-
setGranularity,
|
|
124
|
-
sort,
|
|
125
|
-
order,
|
|
126
|
-
setSorting,
|
|
127
|
-
sorting,
|
|
128
|
-
page,
|
|
129
|
-
limit,
|
|
130
|
-
setPagination,
|
|
131
|
-
pagination,
|
|
132
|
-
]
|
|
133
|
-
);
|
|
134
|
-
};
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import type { UseQueryOptions, UseQueryResult } from "@tanstack/react-query";
|
|
2
|
-
import type { RowSelectionState, Updater } from "@tanstack/react-table";
|
|
3
|
-
import { useCallback, useMemo, useState } from "react";
|
|
4
|
-
import { type NuqsQueryParams, useNuqsQueryParams } from "./useNuqsQueryParams";
|
|
5
|
-
import { useQueryWithParams } from "./useQueryWithParams";
|
|
6
|
-
|
|
7
|
-
export interface TableParams extends NuqsQueryParams {
|
|
8
|
-
rowSelection: RowSelectionState;
|
|
9
|
-
setRowSelection: (updater: Updater<RowSelectionState>) => void;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Alias for TableParams - used by TablePagination and other table components
|
|
14
|
-
*/
|
|
15
|
-
export type TableProps = TableParams;
|
|
16
|
-
|
|
17
|
-
export interface NuqsTableReturn<TData> {
|
|
18
|
-
params: TableParams;
|
|
19
|
-
query: UseQueryResult<TData>;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Flexible query options type that accepts both standard TanStack Query options
|
|
24
|
-
* and tRPC's queryOptions function return type.
|
|
25
|
-
* Uses permissive generics to handle type differences between TanStack Query and tRPC.
|
|
26
|
-
*/
|
|
27
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
28
|
-
type QueryOptionsLike<TData> = UseQueryOptions<TData, any, TData, any>;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Function type that accepts both standard query options functions and tRPC's queryOptions.
|
|
32
|
-
* tRPC's queryOptions accepts (input, opts?) while standard functions may only accept (input).
|
|
33
|
-
*/
|
|
34
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
|
-
type GetQueryOptionsFn<TInput, TData> = (input: TInput, ...args: any[]) => QueryOptionsLike<TData>;
|
|
36
|
-
|
|
37
|
-
export interface NuqsTableOptions<TInput, TData> {
|
|
38
|
-
getQueryOptions: GetQueryOptionsFn<TInput, TData>;
|
|
39
|
-
queryParams?: TInput;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const useNuqsTable = <TInput, TData>({
|
|
43
|
-
getQueryOptions,
|
|
44
|
-
queryParams = {} as TInput,
|
|
45
|
-
}: NuqsTableOptions<TInput, TData>): NuqsTableReturn<TData> => {
|
|
46
|
-
// Get all URL query state
|
|
47
|
-
const queryState = useNuqsQueryParams();
|
|
48
|
-
|
|
49
|
-
// Get query result
|
|
50
|
-
const queryResult = useQueryWithParams({
|
|
51
|
-
getQueryOptions,
|
|
52
|
-
queryParams,
|
|
53
|
-
queryState,
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// Table-specific row selection state
|
|
57
|
-
const [rowSelection, setRowSelectionRaw] = useState<RowSelectionState>({});
|
|
58
|
-
|
|
59
|
-
console.log("rowSelection", rowSelection);
|
|
60
|
-
|
|
61
|
-
const setRowSelection = useCallback((updater: Updater<RowSelectionState>) => {
|
|
62
|
-
setRowSelectionRaw((prev: RowSelectionState) => {
|
|
63
|
-
const next = typeof updater === "function" ? updater(prev) : updater;
|
|
64
|
-
return next;
|
|
65
|
-
});
|
|
66
|
-
}, []);
|
|
67
|
-
|
|
68
|
-
const params = useMemo(
|
|
69
|
-
() => ({
|
|
70
|
-
...queryState,
|
|
71
|
-
rowSelection,
|
|
72
|
-
setRowSelection,
|
|
73
|
-
}),
|
|
74
|
-
[queryState, rowSelection, setRowSelection]
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
return {
|
|
78
|
-
params,
|
|
79
|
-
query: queryResult,
|
|
80
|
-
};
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
export default useNuqsTable;
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { type UseQueryOptions, type UseQueryResult, useQuery } from "@tanstack/react-query";
|
|
2
|
-
import { useMemo } from "react";
|
|
3
|
-
import type { NuqsQueryParams } from "./useNuqsQueryParams";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Flexible query options type that accepts both standard TanStack Query options
|
|
7
|
-
* and tRPC's queryOptions function return type.
|
|
8
|
-
* Uses permissive generics to handle type differences between TanStack Query and tRPC.
|
|
9
|
-
*/
|
|
10
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
-
type QueryOptionsLike<TData> = UseQueryOptions<TData, any, TData, any>;
|
|
12
|
-
|
|
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
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
-
type GetQueryOptionsFn<TInput, TData> = (input: TInput, ...args: any[]) => QueryOptionsLike<TData>;
|
|
19
|
-
|
|
20
|
-
export interface QueryWithParamsOptions<TInput, TData> {
|
|
21
|
-
getQueryOptions: GetQueryOptionsFn<TInput, TData>;
|
|
22
|
-
queryParams?: TInput;
|
|
23
|
-
queryState: Pick<NuqsQueryParams, "filters" | "sort" | "order" | "page" | "limit">;
|
|
24
|
-
enabled?: boolean;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Hook to integrate query state with React Query
|
|
29
|
-
* Combines queryParams with queryState and manages data fetching
|
|
30
|
-
*/
|
|
31
|
-
export const useQueryWithParams = <TInput, TData>({
|
|
32
|
-
getQueryOptions,
|
|
33
|
-
queryParams = {} as TInput,
|
|
34
|
-
queryState,
|
|
35
|
-
enabled = true,
|
|
36
|
-
}: QueryWithParamsOptions<TInput, TData>): UseQueryResult<TData> => {
|
|
37
|
-
const { filters, sort, order, page, limit } = queryState;
|
|
38
|
-
|
|
39
|
-
const input = useMemo(
|
|
40
|
-
() => ({
|
|
41
|
-
...queryParams,
|
|
42
|
-
page,
|
|
43
|
-
limit,
|
|
44
|
-
sort,
|
|
45
|
-
order: order ?? undefined,
|
|
46
|
-
filters,
|
|
47
|
-
}),
|
|
48
|
-
[queryParams, page, limit, sort, order, filters]
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
const queryOptions = useMemo(
|
|
52
|
-
() => ({
|
|
53
|
-
...getQueryOptions(input),
|
|
54
|
-
placeholderData: (previousData: TData | undefined) => previousData,
|
|
55
|
-
enabled,
|
|
56
|
-
}),
|
|
57
|
-
[getQueryOptions, input, enabled]
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
61
|
-
return useQuery(queryOptions as any) as UseQueryResult<TData>;
|
|
62
|
-
};
|
package/src/types.ts
DELETED
package/src/utils/date.ts
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import type { DateValue, RangeValue } from "@heroui/react";
|
|
2
|
-
import { CalendarDate, getLocalTimeZone, parseAbsolute } from "@internationalized/date";
|
|
3
|
-
import type { QueryFilter } from "@m5kdev/commons/modules/schemas/query.schema";
|
|
4
|
-
import { DateTime } from "luxon";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Convert CalendarDate to UTC ISO string at midnight UTC
|
|
8
|
-
*/
|
|
9
|
-
export const calendarDateToUTC = (date: CalendarDate): string => {
|
|
10
|
-
return DateTime.utc(date.year, date.month, date.day).toISO() ?? "";
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Convert CalendarDate to UTC ISO string at end of day UTC
|
|
15
|
-
*/
|
|
16
|
-
export const calendarDateToUTCEndOfDay = (date: CalendarDate): string => {
|
|
17
|
-
return DateTime.utc(date.year, date.month, date.day).endOf("day").toISO() ?? "";
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Convert RangeValue<DateValue> to URL string format "startISO,endISO"
|
|
22
|
-
*/
|
|
23
|
-
export const rangeToUrlString = (range: RangeValue<DateValue> | null): string | null => {
|
|
24
|
-
if (!range || !range.start || !range.end) return null;
|
|
25
|
-
|
|
26
|
-
const start = range.start as unknown as CalendarDate;
|
|
27
|
-
const end = range.end as unknown as CalendarDate;
|
|
28
|
-
|
|
29
|
-
if (!start?.year || !end?.year) return null;
|
|
30
|
-
|
|
31
|
-
// Format as ISO date strings (YYYY-MM-DD)
|
|
32
|
-
const startISO = `${start.year}-${String(start.month).padStart(2, "0")}-${String(start.day).padStart(2, "0")}`;
|
|
33
|
-
const endISO = `${end.year}-${String(end.month).padStart(2, "0")}-${String(end.day).padStart(2, "0")}`;
|
|
34
|
-
|
|
35
|
-
return `${startISO},${endISO}`;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Convert URL string "startISO,endISO" to RangeValue<DateValue>
|
|
40
|
-
*/
|
|
41
|
-
export const urlStringToRange = (value: string | null): RangeValue<DateValue> | null => {
|
|
42
|
-
if (!value) return null;
|
|
43
|
-
|
|
44
|
-
const [startISO, endISO] = value.split(",");
|
|
45
|
-
if (!startISO || !endISO) return null;
|
|
46
|
-
|
|
47
|
-
const tz = getLocalTimeZone();
|
|
48
|
-
|
|
49
|
-
// Parse ISO date strings using parseAbsolute for proper timezone handling
|
|
50
|
-
const startZoned = parseAbsolute(`${startISO}T00:00:00Z`, tz);
|
|
51
|
-
const endZoned = parseAbsolute(`${endISO}T00:00:00Z`, tz);
|
|
52
|
-
|
|
53
|
-
if (!startZoned || !endZoned) return null;
|
|
54
|
-
|
|
55
|
-
const startDate = new CalendarDate(startZoned.year, startZoned.month, startZoned.day);
|
|
56
|
-
const endDate = new CalendarDate(endZoned.year, endZoned.month, endZoned.day);
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
start: startDate as unknown as DateValue,
|
|
60
|
-
end: endDate as unknown as DateValue,
|
|
61
|
-
} as RangeValue<DateValue>;
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Convert date range to QueryFilter format
|
|
66
|
-
*/
|
|
67
|
-
export const dateRangeToFilter = (
|
|
68
|
-
dateRange: RangeValue<DateValue> | null,
|
|
69
|
-
columnId = "startedAt"
|
|
70
|
-
): QueryFilter | null => {
|
|
71
|
-
if (!dateRange || !dateRange.start || !dateRange.end) return null;
|
|
72
|
-
|
|
73
|
-
const start = dateRange.start as unknown as CalendarDate;
|
|
74
|
-
const end = dateRange.end as unknown as CalendarDate;
|
|
75
|
-
|
|
76
|
-
if (!start?.year || !end?.year) return null;
|
|
77
|
-
|
|
78
|
-
const startISO = calendarDateToUTC(start);
|
|
79
|
-
const endISO = calendarDateToUTCEndOfDay(end);
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
columnId,
|
|
83
|
-
type: "date",
|
|
84
|
-
method: "between",
|
|
85
|
-
value: startISO,
|
|
86
|
-
valueTo: endISO,
|
|
87
|
-
};
|
|
88
|
-
};
|