@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.
Files changed (78) hide show
  1. package/.cursor/rules/frontend.mdc +49 -0
  2. package/.turbo/turbo-build.log +5 -0
  3. package/.turbo/turbo-check-types.log +5 -0
  4. package/.turbo/turbo-lint$colon$fix.log +101 -0
  5. package/.turbo/turbo-lint.log +162 -0
  6. package/LICENSE +621 -0
  7. package/dist/src/modules/auth/auth.lib.d.ts +2222 -0
  8. package/dist/src/modules/auth/auth.lib.d.ts.map +1 -0
  9. package/dist/src/modules/auth/auth.lib.js +41 -0
  10. package/dist/src/modules/auth/components/AuthProvider.d.ts +15 -0
  11. package/dist/src/modules/auth/components/AuthProvider.d.ts.map +1 -0
  12. package/dist/src/modules/auth/components/AuthProvider.js +73 -0
  13. package/dist/src/modules/auth/hooks/useAuth.d.ts +4 -0
  14. package/dist/src/modules/auth/hooks/useAuth.d.ts.map +1 -0
  15. package/dist/src/modules/auth/hooks/useAuth.js +14 -0
  16. package/dist/src/modules/auth/hooks/useAuthAdmin.d.ts +21 -0
  17. package/dist/src/modules/auth/hooks/useAuthAdmin.d.ts.map +1 -0
  18. package/dist/src/modules/auth/hooks/useAuthAdmin.js +160 -0
  19. package/dist/src/modules/auth/hooks/useSession.d.ts +38 -0
  20. package/dist/src/modules/auth/hooks/useSession.d.ts.map +1 -0
  21. package/dist/src/modules/auth/hooks/useSession.js +5 -0
  22. package/dist/src/modules/billing/components/BillingProvider.d.ts +14 -0
  23. package/dist/src/modules/billing/components/BillingProvider.d.ts.map +1 -0
  24. package/dist/src/modules/billing/components/BillingProvider.js +25 -0
  25. package/dist/src/modules/billing/hooks/useSubscription.d.ts +5 -0
  26. package/dist/src/modules/billing/hooks/useSubscription.d.ts.map +1 -0
  27. package/dist/src/modules/billing/hooks/useSubscription.js +5 -0
  28. package/dist/src/modules/file/hooks/useS3DownloadUrl.d.ts +3 -0
  29. package/dist/src/modules/file/hooks/useS3DownloadUrl.d.ts.map +1 -0
  30. package/dist/src/modules/file/hooks/useS3DownloadUrl.js +14 -0
  31. package/dist/src/modules/file/hooks/useS3Upload.d.ts +10 -0
  32. package/dist/src/modules/file/hooks/useS3Upload.d.ts.map +1 -0
  33. package/dist/src/modules/file/hooks/useS3Upload.js +76 -0
  34. package/dist/src/modules/file/hooks/useUpload.d.ts +30 -0
  35. package/dist/src/modules/file/hooks/useUpload.d.ts.map +1 -0
  36. package/dist/src/modules/file/hooks/useUpload.js +167 -0
  37. package/dist/src/modules/table/hooks/useDateRangeFilter.d.ts +28 -0
  38. package/dist/src/modules/table/hooks/useDateRangeFilter.d.ts.map +1 -0
  39. package/dist/src/modules/table/hooks/useDateRangeFilter.js +20 -0
  40. package/dist/src/modules/table/hooks/useNuqsQueryParams.d.ts +25 -0
  41. package/dist/src/modules/table/hooks/useNuqsQueryParams.d.ts.map +1 -0
  42. package/dist/src/modules/table/hooks/useNuqsQueryParams.js +82 -0
  43. package/dist/src/modules/table/hooks/useNuqsTable.d.ts +33 -0
  44. package/dist/src/modules/table/hooks/useNuqsTable.d.ts.map +1 -0
  45. package/dist/src/modules/table/hooks/useNuqsTable.js +32 -0
  46. package/dist/src/modules/table/hooks/useQueryWithParams.d.ts +26 -0
  47. package/dist/src/modules/table/hooks/useQueryWithParams.d.ts.map +1 -0
  48. package/dist/src/modules/table/hooks/useQueryWithParams.js +24 -0
  49. package/dist/src/types.d.ts +4 -0
  50. package/dist/src/types.d.ts.map +1 -0
  51. package/dist/src/types.js +1 -0
  52. package/dist/src/utils/date.d.ts +24 -0
  53. package/dist/src/utils/date.d.ts.map +1 -0
  54. package/dist/src/utils/date.js +71 -0
  55. package/dist/src/utils/query.d.ts +8 -0
  56. package/dist/src/utils/query.d.ts.map +1 -0
  57. package/dist/src/utils/query.js +46 -0
  58. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  59. package/package.json +85 -0
  60. package/src/modules/auth/auth.lib.ts +49 -0
  61. package/src/modules/auth/components/AuthProvider.tsx +105 -0
  62. package/src/modules/auth/hooks/useAuth.ts +20 -0
  63. package/src/modules/auth/hooks/useAuthAdmin.ts +188 -0
  64. package/src/modules/auth/hooks/useSession.ts +6 -0
  65. package/src/modules/billing/components/BillingProvider.tsx +58 -0
  66. package/src/modules/billing/hooks/useSubscription.ts +6 -0
  67. package/src/modules/file/hooks/useS3DownloadUrl.ts +18 -0
  68. package/src/modules/file/hooks/useS3Upload.ts +89 -0
  69. package/src/modules/file/hooks/useUpload.ts +220 -0
  70. package/src/modules/table/hooks/useDateRangeFilter.ts +55 -0
  71. package/src/modules/table/hooks/useNuqsQueryParams.ts +134 -0
  72. package/src/modules/table/hooks/useNuqsTable.ts +83 -0
  73. package/src/modules/table/hooks/useQueryWithParams.ts +62 -0
  74. package/src/types.ts +4 -0
  75. package/src/utils/date.ts +88 -0
  76. package/src/utils/query.ts +72 -0
  77. package/src/vite-env.d.ts +1 -0
  78. 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,4 @@
1
+ import type { BackendTRPCRouter } from "@m5kdev/backend/types";
2
+ import type { createTRPCContext } from "@trpc/tanstack-react-query";
3
+ export type UseBackendTRPC = ReturnType<typeof createTRPCContext<BackendTRPCRouter>>["useTRPC"];
4
+ //# sourceMappingURL=types.d.ts.map
@@ -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();