@carlonicora/nextjs-jsonapi 0.0.1 → 1.0.4

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 (303) hide show
  1. package/dist/BlockNoteEditor-VFWG6LXI.js.map +1 -1
  2. package/dist/JsonApiRequest-ZZLSP26T.js.map +1 -1
  3. package/dist/atoms/index.js.map +1 -1
  4. package/dist/chunk-2K3Q24UF.js.map +1 -1
  5. package/dist/chunk-3FBCC4G3.js.map +1 -1
  6. package/dist/chunk-4HCRAOS5.js.map +1 -1
  7. package/dist/chunk-6GKHCVF6.js.map +1 -1
  8. package/dist/chunk-7QVYU63E.js.map +1 -1
  9. package/dist/chunk-A5DDIABK.js.map +1 -1
  10. package/dist/chunk-AWONBQQP.js.map +1 -1
  11. package/dist/chunk-CXQOWQSY.js.map +1 -1
  12. package/dist/chunk-DO2HLAZO.js.map +1 -1
  13. package/dist/chunk-EFJEWLRL.js.map +1 -1
  14. package/dist/chunk-FY4SXJGU.js.map +1 -1
  15. package/dist/chunk-H6FMOA6B.js.map +1 -1
  16. package/dist/chunk-I2REI7OA.js.map +1 -1
  17. package/dist/chunk-IBS6NI7D.js.map +1 -1
  18. package/dist/chunk-J4Q36PMP.js.map +1 -1
  19. package/dist/chunk-JC3WJK65.js.map +1 -1
  20. package/dist/chunk-LXKSUWAV.js.map +1 -1
  21. package/dist/chunk-RAF7PNLG.js.map +1 -1
  22. package/dist/chunk-RUR22SVM.js.map +1 -1
  23. package/dist/chunk-TEGF6ZWG.js.map +1 -1
  24. package/dist/chunk-TMVHSY3Y.js.map +1 -1
  25. package/dist/chunk-V2JJPI7N.js.map +1 -1
  26. package/dist/client/index.js.map +1 -1
  27. package/dist/components/index.js.map +1 -1
  28. package/dist/contexts/index.js.map +1 -1
  29. package/dist/core/index.js.map +1 -1
  30. package/dist/features/index.js.map +1 -1
  31. package/dist/hooks/index.js.map +1 -1
  32. package/dist/index.js.map +1 -1
  33. package/dist/interfaces/index.js.map +1 -1
  34. package/dist/permissions/index.js.map +1 -1
  35. package/dist/request-QFS7NEIE.js.map +1 -1
  36. package/dist/request-ZYY6RI5X.js.map +1 -1
  37. package/dist/roles/index.js.map +1 -1
  38. package/dist/server/index.js.map +1 -1
  39. package/dist/shadcnui/index.js.map +1 -1
  40. package/dist/token-MJMC26ON.js.map +1 -1
  41. package/dist/token-UYE7CV6X.js.map +1 -1
  42. package/dist/utils/index.js.map +1 -1
  43. package/package.json +6 -1
  44. package/src/atoms/index.ts +1 -0
  45. package/src/atoms/recentPagesAtom.ts +10 -0
  46. package/src/client/context/JsonApiContext.ts +61 -0
  47. package/src/client/context/JsonApiProvider.tsx +27 -0
  48. package/src/client/context/index.ts +2 -0
  49. package/src/client/hooks/index.ts +3 -0
  50. package/src/client/hooks/useJsonApiGet.ts +188 -0
  51. package/src/client/hooks/useJsonApiMutation.ts +193 -0
  52. package/src/client/hooks/useRehydration.ts +47 -0
  53. package/src/client/index.ts +11 -0
  54. package/src/client/request.ts +97 -0
  55. package/src/client/token.ts +10 -0
  56. package/src/components/containers/PageContainer.tsx +15 -0
  57. package/src/components/containers/ReactMarkdownContainer.tsx +119 -0
  58. package/src/components/containers/TabsContainer.tsx +93 -0
  59. package/src/components/containers/index.ts +3 -0
  60. package/src/components/contents/AttributeElement.tsx +20 -0
  61. package/src/components/contents/index.ts +1 -0
  62. package/src/components/details/AllowedUsersDetails.tsx +23 -0
  63. package/src/components/details/index.ts +1 -0
  64. package/src/components/editors/BlockNoteDiffInlineContent.tsx +152 -0
  65. package/src/components/editors/BlockNoteEditor.tsx +404 -0
  66. package/src/components/editors/BlockNoteEditorContainer.tsx +13 -0
  67. package/src/components/editors/BlockNoteEditorFormattingToolbar.tsx +38 -0
  68. package/src/components/editors/index.ts +1 -0
  69. package/src/components/errors/ErrorDetails.tsx +41 -0
  70. package/src/components/errors/errorToast.ts +9 -0
  71. package/src/components/errors/index.ts +2 -0
  72. package/src/components/forms/CommonAssociationForm.tsx +162 -0
  73. package/src/components/forms/CommonDeleter.tsx +94 -0
  74. package/src/components/forms/CommonEditorButtons.tsx +30 -0
  75. package/src/components/forms/CommonEditorHeader.tsx +35 -0
  76. package/src/components/forms/CommonEditorTrigger.tsx +26 -0
  77. package/src/components/forms/DatePickerPopover.tsx +219 -0
  78. package/src/components/forms/DateRangeSelector.tsx +110 -0
  79. package/src/components/forms/FileUploader.tsx +324 -0
  80. package/src/components/forms/FormCheckbox.tsx +66 -0
  81. package/src/components/forms/FormContainerGeneric.tsx +39 -0
  82. package/src/components/forms/FormDate.tsx +247 -0
  83. package/src/components/forms/FormDateTime.tsx +231 -0
  84. package/src/components/forms/FormInput.tsx +110 -0
  85. package/src/components/forms/FormPassword.tsx +54 -0
  86. package/src/components/forms/FormPlaceAutocomplete.tsx +286 -0
  87. package/src/components/forms/FormSelect.tsx +72 -0
  88. package/src/components/forms/FormSlider.tsx +51 -0
  89. package/src/components/forms/FormSwitch.tsx +25 -0
  90. package/src/components/forms/FormTextarea.tsx +44 -0
  91. package/src/components/forms/MultiFileUploader.tsx +107 -0
  92. package/src/components/forms/PasswordInput.tsx +47 -0
  93. package/src/components/forms/index.ts +21 -0
  94. package/src/components/index.ts +11 -0
  95. package/src/components/navigations/Breadcrumb.tsx +83 -0
  96. package/src/components/navigations/ContentTitle.tsx +39 -0
  97. package/src/components/navigations/Header.tsx +27 -0
  98. package/src/components/navigations/ModeToggleSwitch.tsx +25 -0
  99. package/src/components/navigations/PageSection.tsx +64 -0
  100. package/src/components/navigations/RecentPagesNavigator.tsx +52 -0
  101. package/src/components/navigations/index.ts +6 -0
  102. package/src/components/pages/PageContainerContentDetails.tsx +76 -0
  103. package/src/components/pages/PageContentContainer.tsx +31 -0
  104. package/src/components/pages/index.ts +2 -0
  105. package/src/components/tables/ContentListTable.tsx +165 -0
  106. package/src/components/tables/ContentTableSearch.tsx +105 -0
  107. package/src/components/tables/cells/cell.component.tsx +18 -0
  108. package/src/components/tables/cells/cell.date.tsx +16 -0
  109. package/src/components/tables/cells/cell.id.tsx +27 -0
  110. package/src/components/tables/cells/cell.link.tsx +18 -0
  111. package/src/components/tables/cells/cell.text.tsx +12 -0
  112. package/src/components/tables/cells/cell.url.tsx +13 -0
  113. package/src/components/tables/cells/index.ts +5 -0
  114. package/src/components/tables/index.ts +3 -0
  115. package/src/contexts/SharedContext.tsx +35 -0
  116. package/src/contexts/index.ts +2 -0
  117. package/src/core/abstracts/AbstractApiData.ts +138 -0
  118. package/src/core/abstracts/AbstractService.ts +263 -0
  119. package/src/core/abstracts/index.ts +2 -0
  120. package/src/core/endpoint/EndpointCreator.ts +97 -0
  121. package/src/core/endpoint/index.ts +1 -0
  122. package/src/core/factories/JsonApiDataFactory.ts +12 -0
  123. package/src/core/factories/RehydrationFactory.ts +30 -0
  124. package/src/core/factories/index.ts +2 -0
  125. package/src/core/fields/FieldSelector.ts +15 -0
  126. package/src/core/fields/index.ts +1 -0
  127. package/src/core/index.ts +20 -0
  128. package/src/core/interfaces/ApiData.ts +8 -0
  129. package/src/core/interfaces/ApiDataInterface.ts +15 -0
  130. package/src/core/interfaces/ApiRequestDataTypeInterface.ts +14 -0
  131. package/src/core/interfaces/ApiResponseInterface.ts +17 -0
  132. package/src/core/interfaces/JsonApiHydratedDataInterface.ts +5 -0
  133. package/src/core/interfaces/index.ts +5 -0
  134. package/src/core/registry/DataClassRegistry.ts +51 -0
  135. package/src/core/registry/ModuleRegistrar.ts +43 -0
  136. package/src/core/registry/ModuleRegistry.ts +64 -0
  137. package/src/core/registry/index.ts +3 -0
  138. package/src/core/utils/index.ts +2 -0
  139. package/src/core/utils/rehydrate.ts +24 -0
  140. package/src/core/utils/translateResponse.ts +125 -0
  141. package/src/features/auth/auth.module.ts +9 -0
  142. package/src/features/auth/config.ts +57 -0
  143. package/src/features/auth/data/auth.interface.ts +31 -0
  144. package/src/features/auth/data/auth.service.ts +159 -0
  145. package/src/features/auth/data/auth.ts +54 -0
  146. package/src/features/auth/data/index.ts +3 -0
  147. package/src/features/auth/index.ts +3 -0
  148. package/src/features/company/company.module.ts +10 -0
  149. package/src/features/company/data/company.fields.ts +6 -0
  150. package/src/features/company/data/company.interface.ts +28 -0
  151. package/src/features/company/data/company.service.ts +73 -0
  152. package/src/features/company/data/company.ts +104 -0
  153. package/src/features/company/data/index.ts +4 -0
  154. package/src/features/company/index.ts +2 -0
  155. package/src/features/content/content.module.ts +20 -0
  156. package/src/features/content/data/content.fields.ts +13 -0
  157. package/src/features/content/data/content.interface.ts +23 -0
  158. package/src/features/content/data/content.service.ts +75 -0
  159. package/src/features/content/data/content.ts +85 -0
  160. package/src/features/content/data/index.ts +4 -0
  161. package/src/features/content/index.ts +2 -0
  162. package/src/features/feature/components/forms/FormFeatures.tsx +149 -0
  163. package/src/features/feature/components/index.ts +1 -0
  164. package/src/features/feature/data/feature.interface.ts +9 -0
  165. package/src/features/feature/data/feature.service.ts +19 -0
  166. package/src/features/feature/data/feature.ts +33 -0
  167. package/src/features/feature/data/index.ts +3 -0
  168. package/src/features/feature/feature.module.ts +10 -0
  169. package/src/features/feature/index.ts +3 -0
  170. package/src/features/index.ts +12 -0
  171. package/src/features/module/data/index.ts +2 -0
  172. package/src/features/module/data/module.interface.ts +12 -0
  173. package/src/features/module/data/module.ts +42 -0
  174. package/src/features/module/index.ts +2 -0
  175. package/src/features/module/module.module.ts +10 -0
  176. package/src/features/notification/data/index.ts +4 -0
  177. package/src/features/notification/data/notification.fields.ts +8 -0
  178. package/src/features/notification/data/notification.interface.ts +14 -0
  179. package/src/features/notification/data/notification.service.ts +34 -0
  180. package/src/features/notification/data/notification.ts +51 -0
  181. package/src/features/notification/index.ts +2 -0
  182. package/src/features/notification/notification.module.ts +10 -0
  183. package/src/features/push/data/index.ts +3 -0
  184. package/src/features/push/data/push.interface.ts +8 -0
  185. package/src/features/push/data/push.service.ts +17 -0
  186. package/src/features/push/data/push.ts +18 -0
  187. package/src/features/push/index.ts +2 -0
  188. package/src/features/push/push.module.ts +10 -0
  189. package/src/features/role/data/index.ts +4 -0
  190. package/src/features/role/data/role.fields.ts +8 -0
  191. package/src/features/role/data/role.interface.ts +16 -0
  192. package/src/features/role/data/role.service.ts +117 -0
  193. package/src/features/role/data/role.ts +62 -0
  194. package/src/features/role/index.ts +2 -0
  195. package/src/features/role/role.module.ts +10 -0
  196. package/src/features/s3/data/index.ts +3 -0
  197. package/src/features/s3/data/s3.interface.ts +11 -0
  198. package/src/features/s3/data/s3.service.ts +30 -0
  199. package/src/features/s3/data/s3.ts +60 -0
  200. package/src/features/s3/index.ts +2 -0
  201. package/src/features/s3/s3.module.ts +10 -0
  202. package/src/features/search/index.ts +1 -0
  203. package/src/features/search/interfaces/index.ts +1 -0
  204. package/src/features/search/interfaces/search.result.interface.ts +3 -0
  205. package/src/features/user/author.module.ts +10 -0
  206. package/src/features/user/components/index.ts +2 -0
  207. package/src/features/user/components/lists/ContributorsList.tsx +41 -0
  208. package/src/features/user/components/lists/index.ts +1 -0
  209. package/src/features/user/components/widgets/UserAvatar.tsx +86 -0
  210. package/src/features/user/components/widgets/index.ts +1 -0
  211. package/src/features/user/contexts/CurrentUserContext.tsx +156 -0
  212. package/src/features/user/contexts/index.ts +1 -0
  213. package/src/features/user/data/index.ts +4 -0
  214. package/src/features/user/data/user.fields.ts +8 -0
  215. package/src/features/user/data/user.interface.ts +41 -0
  216. package/src/features/user/data/user.service.ts +246 -0
  217. package/src/features/user/data/user.ts +162 -0
  218. package/src/features/user/index.ts +4 -0
  219. package/src/features/user/user.module.ts +21 -0
  220. package/src/hooks/TableGeneratorRegistry.ts +53 -0
  221. package/src/hooks/index.ts +33 -0
  222. package/src/hooks/types.ts +35 -0
  223. package/src/hooks/url.rewriter.ts +22 -0
  224. package/src/hooks/useCustomD3Graph.tsx +705 -0
  225. package/src/hooks/useDataListRetriever.ts +349 -0
  226. package/src/hooks/useDebounce.ts +33 -0
  227. package/src/hooks/usePageUrlGenerator.ts +50 -0
  228. package/src/hooks/useTableGenerator.ts +16 -0
  229. package/src/i18n/config.ts +73 -0
  230. package/src/i18n/index.ts +18 -0
  231. package/src/index.ts +16 -0
  232. package/src/interfaces/breadcrumb.item.data.interface.ts +4 -0
  233. package/src/interfaces/d3.link.interface.ts +7 -0
  234. package/src/interfaces/d3.node.interface.ts +12 -0
  235. package/src/interfaces/index.ts +3 -0
  236. package/src/permissions/check.ts +127 -0
  237. package/src/permissions/index.ts +2 -0
  238. package/src/permissions/types.ts +109 -0
  239. package/src/roles/config.ts +46 -0
  240. package/src/roles/index.ts +1 -0
  241. package/src/server/cache.ts +28 -0
  242. package/src/server/index.ts +3 -0
  243. package/src/server/request.ts +113 -0
  244. package/src/server/token.ts +10 -0
  245. package/src/shadcnui/custom/kanban.tsx +1001 -0
  246. package/src/shadcnui/custom/link.tsx +18 -0
  247. package/src/shadcnui/custom/multi-select.tsx +382 -0
  248. package/src/shadcnui/index.ts +49 -0
  249. package/src/shadcnui/ui/accordion.tsx +52 -0
  250. package/src/shadcnui/ui/alert-dialog.tsx +141 -0
  251. package/src/shadcnui/ui/alert.tsx +43 -0
  252. package/src/shadcnui/ui/avatar.tsx +50 -0
  253. package/src/shadcnui/ui/badge.tsx +40 -0
  254. package/src/shadcnui/ui/breadcrumb.tsx +115 -0
  255. package/src/shadcnui/ui/button.tsx +51 -0
  256. package/src/shadcnui/ui/calendar.tsx +73 -0
  257. package/src/shadcnui/ui/card.tsx +43 -0
  258. package/src/shadcnui/ui/carousel.tsx +225 -0
  259. package/src/shadcnui/ui/chart.tsx +320 -0
  260. package/src/shadcnui/ui/checkbox.tsx +29 -0
  261. package/src/shadcnui/ui/collapsible.tsx +11 -0
  262. package/src/shadcnui/ui/command.tsx +155 -0
  263. package/src/shadcnui/ui/context-menu.tsx +179 -0
  264. package/src/shadcnui/ui/dialog.tsx +96 -0
  265. package/src/shadcnui/ui/drawer.tsx +89 -0
  266. package/src/shadcnui/ui/dropdown-menu.tsx +205 -0
  267. package/src/shadcnui/ui/form.tsx +138 -0
  268. package/src/shadcnui/ui/hover-card.tsx +29 -0
  269. package/src/shadcnui/ui/input.tsx +21 -0
  270. package/src/shadcnui/ui/label.tsx +26 -0
  271. package/src/shadcnui/ui/navigation-menu.tsx +168 -0
  272. package/src/shadcnui/ui/popover.tsx +33 -0
  273. package/src/shadcnui/ui/progress.tsx +25 -0
  274. package/src/shadcnui/ui/radio-group.tsx +37 -0
  275. package/src/shadcnui/ui/resizable.tsx +47 -0
  276. package/src/shadcnui/ui/scroll-area.tsx +40 -0
  277. package/src/shadcnui/ui/select.tsx +164 -0
  278. package/src/shadcnui/ui/separator.tsx +28 -0
  279. package/src/shadcnui/ui/sheet.tsx +139 -0
  280. package/src/shadcnui/ui/sidebar.tsx +677 -0
  281. package/src/shadcnui/ui/skeleton.tsx +13 -0
  282. package/src/shadcnui/ui/slider.tsx +25 -0
  283. package/src/shadcnui/ui/sonner.tsx +25 -0
  284. package/src/shadcnui/ui/switch.tsx +31 -0
  285. package/src/shadcnui/ui/table.tsx +120 -0
  286. package/src/shadcnui/ui/tabs.tsx +55 -0
  287. package/src/shadcnui/ui/textarea.tsx +24 -0
  288. package/src/shadcnui/ui/toggle.tsx +39 -0
  289. package/src/shadcnui/ui/tooltip.tsx +61 -0
  290. package/src/unified/JsonApiRequest.ts +325 -0
  291. package/src/unified/index.ts +1 -0
  292. package/src/utils/blocknote-diff.util.ts +815 -0
  293. package/src/utils/blocknote-word-diff-renderer.util.ts +413 -0
  294. package/src/utils/cn.ts +6 -0
  295. package/src/utils/compose-refs.ts +61 -0
  296. package/src/utils/date-formatter.ts +53 -0
  297. package/src/utils/exists.ts +7 -0
  298. package/src/utils/index.ts +15 -0
  299. package/src/utils/schemas/entity.object.schema.ts +8 -0
  300. package/src/utils/schemas/index.ts +2 -0
  301. package/src/utils/schemas/user.object.schema.ts +9 -0
  302. package/src/utils/table-options.ts +67 -0
  303. package/src/utils/use-mobile.tsx +21 -0
@@ -0,0 +1,349 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
+ import { AbstractService } from "../core";
5
+
6
+ export type PageInfo = {
7
+ startItem: number;
8
+ endItem: number;
9
+ pageSize: number;
10
+ };
11
+
12
+ export type DataListRetriever<T> = {
13
+ ready?: boolean;
14
+ setReady: (state: boolean) => void;
15
+ isLoaded: boolean;
16
+ data: T[] | undefined;
17
+ next?: (onlyNewRecords?: boolean) => Promise<void>;
18
+ previous?: (onlyNewRecords?: boolean) => Promise<void>;
19
+ search: (search: string) => Promise<void>;
20
+ refresh: () => Promise<void>;
21
+ addAdditionalParameter: (key: string, value: any | null) => void;
22
+ removeAdditionalParameter: (key: string) => void;
23
+ setRefreshedElement: (element: T) => void;
24
+ removeElement: (element: T) => void;
25
+ isSearch: boolean;
26
+ pageInfo?: PageInfo;
27
+ };
28
+
29
+ export function useDataListRetriever<T>(params: {
30
+ ready?: boolean;
31
+ retriever: (params: any) => Promise<T[]>;
32
+ retrieverParams?: any;
33
+ search?: string;
34
+ addAdditionalParameter?: (key: string, value: any | null) => void;
35
+ requiresSearch?: boolean;
36
+ module: any;
37
+ }): DataListRetriever<T> {
38
+ const [data, setData] = useState<T[] | undefined>(undefined);
39
+ const [nextPage, setNextPage] = useState<string | undefined>(undefined);
40
+ const [previousPage, setPreviousPage] = useState<string | undefined>(undefined);
41
+ const [isLoaded, setIsLoaded] = useState(false);
42
+ const [ready, setReady] = useState<boolean>(params.ready ?? true);
43
+ const searchTermRef = useRef<string>("");
44
+ const additionalParamsRef = useRef<any>({});
45
+ const requestIdRef = useRef(0);
46
+ const abortControllerRef = useRef<AbortController | null>(null);
47
+ const isFetchingRef = useRef(false);
48
+
49
+ const resolvedType = params.module;
50
+ const resolvedService = AbstractService; // We'll just use AbstractService directly for pagination
51
+
52
+ // Helper to parse page params from pagination URLs
53
+ const parsePageParams = useCallback((url: string | undefined): { offset: number; size: number } | null => {
54
+ if (!url) return null;
55
+ try {
56
+ const urlObj = new URL(url);
57
+ const offset = parseInt(urlObj.searchParams.get("page[offset]") || "0", 10);
58
+ const size = parseInt(urlObj.searchParams.get("page[size]") || "25", 10);
59
+ return { offset, size };
60
+ } catch {
61
+ return null;
62
+ }
63
+ }, []);
64
+
65
+ // Helper to adjust pagination URL offset (used when removing elements)
66
+ const adjustPaginationUrl = useCallback((url: string | undefined, delta: number): string | undefined => {
67
+ if (!url) return undefined;
68
+ try {
69
+ const urlObj = new URL(url);
70
+ const currentOffset = parseInt(urlObj.searchParams.get("page[offset]") || "0", 10);
71
+ const newOffset = Math.max(0, currentOffset + delta);
72
+ urlObj.searchParams.set("page[offset]", String(newOffset));
73
+ return urlObj.toString();
74
+ } catch {
75
+ return url;
76
+ }
77
+ }, []);
78
+
79
+ // Calculate pageInfo from pagination URLs and current data
80
+ const pageInfo = useMemo((): PageInfo | undefined => {
81
+ if (!data || data.length === 0) return undefined;
82
+
83
+ // Try to determine current offset and page size from pagination URLs
84
+ const nextParams = parsePageParams(nextPage);
85
+ const prevParams = parsePageParams(previousPage);
86
+
87
+ let currentOffset = 0;
88
+ let pageSize = 25; // default
89
+
90
+ if (nextParams) {
91
+ // If we have a next page, current offset = next offset - page size
92
+ pageSize = nextParams.size;
93
+ currentOffset = Math.max(0, nextParams.offset - pageSize);
94
+ } else if (prevParams) {
95
+ // If we only have a previous page (we're on the last page)
96
+ pageSize = prevParams.size;
97
+ currentOffset = prevParams.offset + pageSize;
98
+ }
99
+
100
+ const startItem = currentOffset + 1;
101
+ const endItem = currentOffset + data.length;
102
+
103
+ return { startItem, endItem, pageSize };
104
+ }, [data, nextPage, previousPage, parsePageParams]);
105
+
106
+ const stableParams = useMemo(
107
+ () => ({
108
+ service: resolvedService,
109
+ type: resolvedType,
110
+ retriever: params.retriever,
111
+ retrieverParams: params.retrieverParams,
112
+ requiresSearch: params.requiresSearch,
113
+ }),
114
+ [resolvedService, resolvedType, params.retriever, params.retrieverParams, params.requiresSearch],
115
+ );
116
+
117
+ const fetchData = useCallback(
118
+ async (fetchParams?: { isRefine?: boolean; isRefresh?: boolean; callNext?: boolean; callPrevious?: boolean }) => {
119
+ if (ready === false) {
120
+ return;
121
+ }
122
+
123
+ // Prevent concurrent fetches (unless it's a pagination call)
124
+ if (isFetchingRef.current && !fetchParams?.callNext && !fetchParams?.callPrevious) {
125
+ return;
126
+ }
127
+
128
+ const thisRequestId = ++requestIdRef.current;
129
+ isFetchingRef.current = true;
130
+
131
+ if (stableParams.requiresSearch === true && fetchParams?.isRefine !== true && fetchParams?.isRefresh !== true)
132
+ return;
133
+
134
+ if (
135
+ !nextPage &&
136
+ !previousPage &&
137
+ isLoaded &&
138
+ fetchParams?.callNext !== true &&
139
+ fetchParams?.callPrevious !== true &&
140
+ params.search === searchTermRef.current
141
+ )
142
+ return;
143
+
144
+ const currentSearchTerm = searchTermRef.current;
145
+
146
+ setIsLoaded(false);
147
+
148
+ try {
149
+ let response: T[];
150
+ const nextRef = { next: undefined };
151
+ const previousRef = { previous: undefined };
152
+ const selfRef = { self: undefined };
153
+
154
+ if (nextPage && fetchParams?.callNext && fetchParams?.isRefine !== true) {
155
+ const ServiceClass = stableParams.service as typeof AbstractService;
156
+
157
+ response = await ServiceClass.next<T[]>({
158
+ type: stableParams.type,
159
+ endpoint: nextPage,
160
+ next: nextRef,
161
+ previous: previousRef,
162
+ self: selfRef,
163
+ });
164
+ } else if (previousPage && fetchParams?.callPrevious && fetchParams?.isRefine !== true) {
165
+ const ServiceClass = stableParams.service as typeof AbstractService;
166
+
167
+ response = await ServiceClass.previous<T[]>({
168
+ type: stableParams.type,
169
+ endpoint: previousPage,
170
+ next: nextRef,
171
+ previous: previousRef,
172
+ self: selfRef,
173
+ });
174
+ } else {
175
+ let retrieverParams = stableParams.retrieverParams ? { ...stableParams.retrieverParams } : {};
176
+
177
+ retrieverParams = {
178
+ ...retrieverParams,
179
+ ...additionalParamsRef.current,
180
+ };
181
+
182
+ retrieverParams.search = currentSearchTerm;
183
+ retrieverParams.next = nextRef;
184
+ retrieverParams.previous = previousRef;
185
+ retrieverParams.self = selfRef;
186
+
187
+ response = await stableParams.retriever(retrieverParams);
188
+ }
189
+
190
+ // Only update state if this is still the latest request and wasn't aborted
191
+ if (thisRequestId === requestIdRef.current && !abortControllerRef.current?.signal.aborted) {
192
+ if (fetchParams?.isRefresh === true) {
193
+ setData(response);
194
+ } else {
195
+ setData((prevData) => [...(prevData ? (prevData as T[]) : []), ...response]);
196
+ }
197
+ setIsLoaded(true);
198
+ setNextPage(nextRef.next ? nextRef.next : undefined);
199
+ setPreviousPage(previousRef.previous ? previousRef.previous : undefined);
200
+ }
201
+ } catch (error) {
202
+ // Don't update state if request was aborted (AbortController disabled)
203
+ if (thisRequestId === requestIdRef.current) {
204
+ setIsLoaded(true);
205
+ console.error("Error fetching data:", error);
206
+ }
207
+ } finally {
208
+ // Always reset fetching flag when done
209
+ if (thisRequestId === requestIdRef.current) {
210
+ isFetchingRef.current = false;
211
+ }
212
+ }
213
+ },
214
+ [stableParams, ready, params.search],
215
+ );
216
+
217
+ const setRefreshedElement = useCallback(
218
+ (element: T) => {
219
+ setData((prevData) => {
220
+ if (!prevData) return prevData;
221
+
222
+ const index = prevData.findIndex((data) => (data as any).id === (element as any).id);
223
+ if (index === -1) return prevData;
224
+
225
+ // Use immutable update pattern instead of mutation
226
+ return prevData.map((item, i) => (i === index ? element : item));
227
+ });
228
+ },
229
+ [setData],
230
+ );
231
+
232
+ const removeElement = useCallback(
233
+ (element: T) => {
234
+ setData((prevData) => {
235
+ if (!prevData) return prevData;
236
+
237
+ const index = prevData.findIndex((data) => (data as any).id === (element as any).id);
238
+
239
+ if (index === -1) return prevData;
240
+
241
+ // Adjust nextPage offset since we're removing an item
242
+ setNextPage((prev) => adjustPaginationUrl(prev, -1));
243
+
244
+ const newData = [...prevData];
245
+ newData.splice(index, 1);
246
+ return newData;
247
+ });
248
+ },
249
+ [adjustPaginationUrl],
250
+ );
251
+
252
+ // Consolidated effect: Only fetch once when ready and not loaded
253
+ // This prevents the duplicate API calls that occurred when both the mount effect
254
+ // and ready effect fired simultaneously on initial render
255
+ useEffect(() => {
256
+ // Only fetch if ready and haven't loaded yet
257
+ if (ready && !isLoaded) {
258
+ fetchData({ isRefresh: true });
259
+ }
260
+ }, [ready, fetchData]);
261
+
262
+ const loadNext = useCallback(
263
+ async (onlyNewRecords?: boolean) => {
264
+ if (nextPage) fetchData({ isRefresh: onlyNewRecords, callNext: true });
265
+ },
266
+ [fetchData, nextPage],
267
+ );
268
+
269
+ const loadPrevious = useCallback(
270
+ async (onlyNewRecords?: boolean) => {
271
+ if (previousPage) fetchData({ isRefresh: onlyNewRecords, callPrevious: true });
272
+ },
273
+ [fetchData, previousPage],
274
+ );
275
+
276
+ const addAdditionalParameter = useCallback(
277
+ (key: string, value: any | null) => {
278
+ if (value === null) {
279
+ delete additionalParamsRef.current[key];
280
+ } else {
281
+ additionalParamsRef.current[key] = value;
282
+ }
283
+
284
+ setReady(true);
285
+ setNextPage(undefined);
286
+ setPreviousPage(undefined);
287
+ fetchData({ isRefine: true, isRefresh: true });
288
+ },
289
+ [fetchData],
290
+ );
291
+
292
+ const removeAdditionalParameter = useCallback(
293
+ (key: string) => {
294
+ if (additionalParamsRef.current[key] !== undefined) {
295
+ delete additionalParamsRef.current[key];
296
+ setNextPage(undefined);
297
+ setPreviousPage(undefined);
298
+ fetchData({ isRefine: true, isRefresh: true });
299
+ }
300
+ },
301
+ [fetchData],
302
+ );
303
+
304
+ const search = useCallback(
305
+ async (search: string) => {
306
+ if (search === searchTermRef.current) return;
307
+
308
+ setNextPage(undefined);
309
+ setPreviousPage(undefined);
310
+ searchTermRef.current = search;
311
+ fetchData({ isRefine: true, isRefresh: true });
312
+ },
313
+ [fetchData],
314
+ );
315
+
316
+ const isSearch = !!searchTermRef.current;
317
+
318
+ const refresh = useCallback(async () => {
319
+ setNextPage(undefined);
320
+ setPreviousPage(undefined);
321
+ fetchData({ isRefresh: true });
322
+ }, [fetchData]);
323
+
324
+ // Cleanup abort controller on unmount
325
+ useEffect(() => {
326
+ return () => {
327
+ if (abortControllerRef.current) {
328
+ abortControllerRef.current.abort();
329
+ }
330
+ };
331
+ }, []);
332
+
333
+ return {
334
+ ready,
335
+ setReady,
336
+ isLoaded: isLoaded,
337
+ data: data as T[],
338
+ next: nextPage ? loadNext : undefined,
339
+ previous: previousPage ? loadPrevious : undefined,
340
+ search: search,
341
+ addAdditionalParameter: addAdditionalParameter,
342
+ removeAdditionalParameter: removeAdditionalParameter,
343
+ refresh: refresh,
344
+ setRefreshedElement: setRefreshedElement,
345
+ isSearch: isSearch,
346
+ removeElement: removeElement,
347
+ pageInfo: pageInfo,
348
+ };
349
+ }
@@ -0,0 +1,33 @@
1
+ import { useCallback, useEffect, useRef } from "react";
2
+
3
+ export function useDebounce<T extends (...args: any[]) => any>(callback: T, delay: number): T & { cancel: () => void } {
4
+ const timeoutRef = useRef<NodeJS.Timeout | null>(null);
5
+ const callbackRef = useRef(callback);
6
+
7
+ useEffect(() => {
8
+ callbackRef.current = callback;
9
+ }, [callback]);
10
+
11
+ const debouncedFunction = useCallback(
12
+ (...args: Parameters<T>) => {
13
+ if (timeoutRef.current) {
14
+ clearTimeout(timeoutRef.current);
15
+ }
16
+ timeoutRef.current = setTimeout(() => {
17
+ callbackRef.current(...args);
18
+ }, delay);
19
+ },
20
+ [delay],
21
+ );
22
+
23
+ // Attach a cancel method to clear pending timeout
24
+ const cancel = () => {
25
+ if (timeoutRef.current) {
26
+ clearTimeout(timeoutRef.current);
27
+ }
28
+ };
29
+
30
+ return Object.assign(debouncedFunction, { cancel }) as T & { cancel: () => void };
31
+ }
32
+
33
+ export default useDebounce;
@@ -0,0 +1,50 @@
1
+ import { PageUrl } from "../permissions/types";
2
+
3
+ export function usePageUrlGenerator(): (params: {
4
+ page?: PageUrl | string;
5
+ id?: string;
6
+ childPage?: PageUrl | string;
7
+ childId?: string;
8
+ additionalParameters?: { [key: string]: string | string[] | undefined };
9
+ language?: string;
10
+ }) => string {
11
+ const generateUrl = (params: {
12
+ page?: PageUrl | string;
13
+ id?: string;
14
+ childPage?: PageUrl | string;
15
+ childId?: string;
16
+ additionalParameters?: { [key: string]: string | string[] | undefined };
17
+ language?: string;
18
+ }): string => {
19
+ if (!params.page) return "/";
20
+
21
+ const pathParams: string[] = [
22
+ `${params.language ? `/${params.language}` : ""}${typeof params.page === "string" ? params.page : params.page.pageUrl}`,
23
+ ];
24
+
25
+ if (params.id) {
26
+ pathParams.push(params.id);
27
+ if (params.childPage) {
28
+ pathParams.push(typeof params.childPage === "string" ? params.childPage : (params.childPage.pageUrl ?? ""));
29
+ if (params.childId) {
30
+ pathParams.push(params.childId);
31
+ }
32
+ }
33
+ }
34
+ const response = pathParams.join(`/`);
35
+
36
+ if (params.additionalParameters) {
37
+ const searchParams = new URLSearchParams();
38
+ for (const key in params.additionalParameters) {
39
+ if (params.additionalParameters[key]) {
40
+ searchParams.append(key, params.additionalParameters[key] as string);
41
+ }
42
+ }
43
+ return `${response}?${searchParams.toString()}`;
44
+ }
45
+
46
+ return response;
47
+ };
48
+
49
+ return generateUrl;
50
+ }
@@ -0,0 +1,16 @@
1
+ "use client";
2
+
3
+ import { UseTableStructureHook, UseTableStructureHookParams, UseTableStructureHookReturn } from "./types";
4
+ import { ModuleWithPermissions } from "../permissions";
5
+ import { tableGeneratorRegistry } from "./TableGeneratorRegistry";
6
+
7
+ export function registerTableGenerator<T, U>(type: ModuleWithPermissions, hook: UseTableStructureHook<T, U>): void {
8
+ tableGeneratorRegistry.register(type.name, hook);
9
+ }
10
+
11
+ export function useTableGenerator<T, U>(
12
+ type: ModuleWithPermissions,
13
+ params: UseTableStructureHookParams<T, U>,
14
+ ): UseTableStructureHookReturn<T> {
15
+ return tableGeneratorRegistry.get(type.name, params);
16
+ }
@@ -0,0 +1,73 @@
1
+ import { ComponentType } from "react";
2
+
3
+ // Types for injected hooks
4
+ export interface I18nRouter {
5
+ push: (href: string) => void;
6
+ replace: (href: string) => void;
7
+ back: () => void;
8
+ forward: () => void;
9
+ refresh: () => void;
10
+ prefetch: (href: string) => void;
11
+ }
12
+
13
+ export type UseRouterHook = () => I18nRouter;
14
+ export type UseTranslationsHook = (namespace?: string) => (key: string, values?: Record<string, any>) => string;
15
+ export type UseLocaleHook = () => string;
16
+
17
+ export type UseDateFnsLocaleHook = () => any; // date-fns Locale type
18
+ export type LinkComponent = ComponentType<{ href: string; children: React.ReactNode; [key: string]: any }>;
19
+
20
+ export interface I18nConfig {
21
+ useRouter: UseRouterHook;
22
+ useTranslations: UseTranslationsHook;
23
+ useLocale?: UseLocaleHook;
24
+ useDateFnsLocale?: UseDateFnsLocaleHook;
25
+ Link: LinkComponent;
26
+ }
27
+
28
+ // Private storage
29
+ let _config: I18nConfig | null = null;
30
+
31
+ // Configuration function (called by app at startup)
32
+ export function configureI18n(config: I18nConfig): void {
33
+ _config = config;
34
+ }
35
+
36
+ // Hooks for library components to use
37
+ export function useI18nRouter(): I18nRouter {
38
+ if (!_config?.useRouter) {
39
+ throw new Error("i18n not configured. Call configureI18n() at app startup.");
40
+ }
41
+ return _config.useRouter();
42
+ }
43
+
44
+ export function useI18nTranslations(namespace?: string): (key: string, values?: Record<string, any>) => string {
45
+ if (!_config?.useTranslations) {
46
+ // Fallback: return key as-is (safe for server/client)
47
+ return (key: string) => key;
48
+ }
49
+ return _config.useTranslations(namespace);
50
+ }
51
+
52
+ export function getI18nLink(): LinkComponent {
53
+ if (!_config?.Link) {
54
+ throw new Error("i18n not configured. Call configureI18n() at app startup.");
55
+ }
56
+ return _config.Link;
57
+ }
58
+
59
+ export function useI18nLocale(): string {
60
+ if (_config?.useLocale) {
61
+ return _config.useLocale();
62
+ }
63
+ // Fallback to English (safe for server/client)
64
+ return "en";
65
+ }
66
+
67
+ export function useI18nDateFnsLocale(): any {
68
+ if (_config?.useDateFnsLocale) {
69
+ return _config.useDateFnsLocale();
70
+ }
71
+ // Fallback to undefined (Calendar will use default)
72
+ return undefined;
73
+ }
@@ -0,0 +1,18 @@
1
+ export {
2
+ configureI18n,
3
+ useI18nRouter,
4
+ useI18nTranslations,
5
+ useI18nLocale,
6
+ useI18nDateFnsLocale,
7
+ getI18nLink,
8
+ } from "./config";
9
+
10
+ export type {
11
+ I18nConfig,
12
+ I18nRouter,
13
+ UseRouterHook,
14
+ UseTranslationsHook,
15
+ UseLocaleHook,
16
+ UseDateFnsLocaleHook,
17
+ LinkComponent,
18
+ } from "./config";
package/src/index.ts ADDED
@@ -0,0 +1,16 @@
1
+ // Unified API (auto-detects environment)
2
+ export * from "./unified";
3
+
4
+ // Core exports for convenience
5
+ export * from "./core";
6
+
7
+ // Permissions
8
+ export * from "./permissions";
9
+
10
+ // I18n configuration (NOT hooks - those are in /hooks with "use client")
11
+ export { configureI18n } from "./i18n";
12
+ export type { I18nConfig } from "./i18n";
13
+
14
+ // Roles configuration
15
+ export { configureRoles, getRoleId, isRolesConfigured } from "./roles";
16
+ export type { RoleIdConfig } from "./roles";
@@ -0,0 +1,4 @@
1
+ export type BreadcrumbItemData = {
2
+ name: string;
3
+ href?: string;
4
+ };
@@ -0,0 +1,7 @@
1
+ import { D3Node } from "./d3.node.interface";
2
+
3
+ export interface D3Link extends d3.SimulationLinkDatum<D3Node> {
4
+ source: string | D3Node;
5
+ target: string | D3Node;
6
+ relationshipType?: string;
7
+ }
@@ -0,0 +1,12 @@
1
+ import { LucideIcon } from "lucide-react";
2
+
3
+ export interface D3Node extends d3.SimulationNodeDatum {
4
+ id: string;
5
+ name: string;
6
+ instanceType: string;
7
+ relationshipType?: string;
8
+ isBrowsableNode?: boolean;
9
+ icon?: LucideIcon;
10
+ visible?: boolean;
11
+ washedOut?: boolean;
12
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./breadcrumb.item.data.interface";
2
+ export * from "./d3.link.interface";
3
+ export * from "./d3.node.interface";