@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.
- package/dist/BlockNoteEditor-VFWG6LXI.js.map +1 -1
- package/dist/JsonApiRequest-ZZLSP26T.js.map +1 -1
- package/dist/atoms/index.js.map +1 -1
- package/dist/chunk-2K3Q24UF.js.map +1 -1
- package/dist/chunk-3FBCC4G3.js.map +1 -1
- package/dist/chunk-4HCRAOS5.js.map +1 -1
- package/dist/chunk-6GKHCVF6.js.map +1 -1
- package/dist/chunk-7QVYU63E.js.map +1 -1
- package/dist/chunk-A5DDIABK.js.map +1 -1
- package/dist/chunk-AWONBQQP.js.map +1 -1
- package/dist/chunk-CXQOWQSY.js.map +1 -1
- package/dist/chunk-DO2HLAZO.js.map +1 -1
- package/dist/chunk-EFJEWLRL.js.map +1 -1
- package/dist/chunk-FY4SXJGU.js.map +1 -1
- package/dist/chunk-H6FMOA6B.js.map +1 -1
- package/dist/chunk-I2REI7OA.js.map +1 -1
- package/dist/chunk-IBS6NI7D.js.map +1 -1
- package/dist/chunk-J4Q36PMP.js.map +1 -1
- package/dist/chunk-JC3WJK65.js.map +1 -1
- package/dist/chunk-LXKSUWAV.js.map +1 -1
- package/dist/chunk-RAF7PNLG.js.map +1 -1
- package/dist/chunk-RUR22SVM.js.map +1 -1
- package/dist/chunk-TEGF6ZWG.js.map +1 -1
- package/dist/chunk-TMVHSY3Y.js.map +1 -1
- package/dist/chunk-V2JJPI7N.js.map +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/components/index.js.map +1 -1
- package/dist/contexts/index.js.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/features/index.js.map +1 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interfaces/index.js.map +1 -1
- package/dist/permissions/index.js.map +1 -1
- package/dist/request-QFS7NEIE.js.map +1 -1
- package/dist/request-ZYY6RI5X.js.map +1 -1
- package/dist/roles/index.js.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/shadcnui/index.js.map +1 -1
- package/dist/token-MJMC26ON.js.map +1 -1
- package/dist/token-UYE7CV6X.js.map +1 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +6 -1
- package/src/atoms/index.ts +1 -0
- package/src/atoms/recentPagesAtom.ts +10 -0
- package/src/client/context/JsonApiContext.ts +61 -0
- package/src/client/context/JsonApiProvider.tsx +27 -0
- package/src/client/context/index.ts +2 -0
- package/src/client/hooks/index.ts +3 -0
- package/src/client/hooks/useJsonApiGet.ts +188 -0
- package/src/client/hooks/useJsonApiMutation.ts +193 -0
- package/src/client/hooks/useRehydration.ts +47 -0
- package/src/client/index.ts +11 -0
- package/src/client/request.ts +97 -0
- package/src/client/token.ts +10 -0
- package/src/components/containers/PageContainer.tsx +15 -0
- package/src/components/containers/ReactMarkdownContainer.tsx +119 -0
- package/src/components/containers/TabsContainer.tsx +93 -0
- package/src/components/containers/index.ts +3 -0
- package/src/components/contents/AttributeElement.tsx +20 -0
- package/src/components/contents/index.ts +1 -0
- package/src/components/details/AllowedUsersDetails.tsx +23 -0
- package/src/components/details/index.ts +1 -0
- package/src/components/editors/BlockNoteDiffInlineContent.tsx +152 -0
- package/src/components/editors/BlockNoteEditor.tsx +404 -0
- package/src/components/editors/BlockNoteEditorContainer.tsx +13 -0
- package/src/components/editors/BlockNoteEditorFormattingToolbar.tsx +38 -0
- package/src/components/editors/index.ts +1 -0
- package/src/components/errors/ErrorDetails.tsx +41 -0
- package/src/components/errors/errorToast.ts +9 -0
- package/src/components/errors/index.ts +2 -0
- package/src/components/forms/CommonAssociationForm.tsx +162 -0
- package/src/components/forms/CommonDeleter.tsx +94 -0
- package/src/components/forms/CommonEditorButtons.tsx +30 -0
- package/src/components/forms/CommonEditorHeader.tsx +35 -0
- package/src/components/forms/CommonEditorTrigger.tsx +26 -0
- package/src/components/forms/DatePickerPopover.tsx +219 -0
- package/src/components/forms/DateRangeSelector.tsx +110 -0
- package/src/components/forms/FileUploader.tsx +324 -0
- package/src/components/forms/FormCheckbox.tsx +66 -0
- package/src/components/forms/FormContainerGeneric.tsx +39 -0
- package/src/components/forms/FormDate.tsx +247 -0
- package/src/components/forms/FormDateTime.tsx +231 -0
- package/src/components/forms/FormInput.tsx +110 -0
- package/src/components/forms/FormPassword.tsx +54 -0
- package/src/components/forms/FormPlaceAutocomplete.tsx +286 -0
- package/src/components/forms/FormSelect.tsx +72 -0
- package/src/components/forms/FormSlider.tsx +51 -0
- package/src/components/forms/FormSwitch.tsx +25 -0
- package/src/components/forms/FormTextarea.tsx +44 -0
- package/src/components/forms/MultiFileUploader.tsx +107 -0
- package/src/components/forms/PasswordInput.tsx +47 -0
- package/src/components/forms/index.ts +21 -0
- package/src/components/index.ts +11 -0
- package/src/components/navigations/Breadcrumb.tsx +83 -0
- package/src/components/navigations/ContentTitle.tsx +39 -0
- package/src/components/navigations/Header.tsx +27 -0
- package/src/components/navigations/ModeToggleSwitch.tsx +25 -0
- package/src/components/navigations/PageSection.tsx +64 -0
- package/src/components/navigations/RecentPagesNavigator.tsx +52 -0
- package/src/components/navigations/index.ts +6 -0
- package/src/components/pages/PageContainerContentDetails.tsx +76 -0
- package/src/components/pages/PageContentContainer.tsx +31 -0
- package/src/components/pages/index.ts +2 -0
- package/src/components/tables/ContentListTable.tsx +165 -0
- package/src/components/tables/ContentTableSearch.tsx +105 -0
- package/src/components/tables/cells/cell.component.tsx +18 -0
- package/src/components/tables/cells/cell.date.tsx +16 -0
- package/src/components/tables/cells/cell.id.tsx +27 -0
- package/src/components/tables/cells/cell.link.tsx +18 -0
- package/src/components/tables/cells/cell.text.tsx +12 -0
- package/src/components/tables/cells/cell.url.tsx +13 -0
- package/src/components/tables/cells/index.ts +5 -0
- package/src/components/tables/index.ts +3 -0
- package/src/contexts/SharedContext.tsx +35 -0
- package/src/contexts/index.ts +2 -0
- package/src/core/abstracts/AbstractApiData.ts +138 -0
- package/src/core/abstracts/AbstractService.ts +263 -0
- package/src/core/abstracts/index.ts +2 -0
- package/src/core/endpoint/EndpointCreator.ts +97 -0
- package/src/core/endpoint/index.ts +1 -0
- package/src/core/factories/JsonApiDataFactory.ts +12 -0
- package/src/core/factories/RehydrationFactory.ts +30 -0
- package/src/core/factories/index.ts +2 -0
- package/src/core/fields/FieldSelector.ts +15 -0
- package/src/core/fields/index.ts +1 -0
- package/src/core/index.ts +20 -0
- package/src/core/interfaces/ApiData.ts +8 -0
- package/src/core/interfaces/ApiDataInterface.ts +15 -0
- package/src/core/interfaces/ApiRequestDataTypeInterface.ts +14 -0
- package/src/core/interfaces/ApiResponseInterface.ts +17 -0
- package/src/core/interfaces/JsonApiHydratedDataInterface.ts +5 -0
- package/src/core/interfaces/index.ts +5 -0
- package/src/core/registry/DataClassRegistry.ts +51 -0
- package/src/core/registry/ModuleRegistrar.ts +43 -0
- package/src/core/registry/ModuleRegistry.ts +64 -0
- package/src/core/registry/index.ts +3 -0
- package/src/core/utils/index.ts +2 -0
- package/src/core/utils/rehydrate.ts +24 -0
- package/src/core/utils/translateResponse.ts +125 -0
- package/src/features/auth/auth.module.ts +9 -0
- package/src/features/auth/config.ts +57 -0
- package/src/features/auth/data/auth.interface.ts +31 -0
- package/src/features/auth/data/auth.service.ts +159 -0
- package/src/features/auth/data/auth.ts +54 -0
- package/src/features/auth/data/index.ts +3 -0
- package/src/features/auth/index.ts +3 -0
- package/src/features/company/company.module.ts +10 -0
- package/src/features/company/data/company.fields.ts +6 -0
- package/src/features/company/data/company.interface.ts +28 -0
- package/src/features/company/data/company.service.ts +73 -0
- package/src/features/company/data/company.ts +104 -0
- package/src/features/company/data/index.ts +4 -0
- package/src/features/company/index.ts +2 -0
- package/src/features/content/content.module.ts +20 -0
- package/src/features/content/data/content.fields.ts +13 -0
- package/src/features/content/data/content.interface.ts +23 -0
- package/src/features/content/data/content.service.ts +75 -0
- package/src/features/content/data/content.ts +85 -0
- package/src/features/content/data/index.ts +4 -0
- package/src/features/content/index.ts +2 -0
- package/src/features/feature/components/forms/FormFeatures.tsx +149 -0
- package/src/features/feature/components/index.ts +1 -0
- package/src/features/feature/data/feature.interface.ts +9 -0
- package/src/features/feature/data/feature.service.ts +19 -0
- package/src/features/feature/data/feature.ts +33 -0
- package/src/features/feature/data/index.ts +3 -0
- package/src/features/feature/feature.module.ts +10 -0
- package/src/features/feature/index.ts +3 -0
- package/src/features/index.ts +12 -0
- package/src/features/module/data/index.ts +2 -0
- package/src/features/module/data/module.interface.ts +12 -0
- package/src/features/module/data/module.ts +42 -0
- package/src/features/module/index.ts +2 -0
- package/src/features/module/module.module.ts +10 -0
- package/src/features/notification/data/index.ts +4 -0
- package/src/features/notification/data/notification.fields.ts +8 -0
- package/src/features/notification/data/notification.interface.ts +14 -0
- package/src/features/notification/data/notification.service.ts +34 -0
- package/src/features/notification/data/notification.ts +51 -0
- package/src/features/notification/index.ts +2 -0
- package/src/features/notification/notification.module.ts +10 -0
- package/src/features/push/data/index.ts +3 -0
- package/src/features/push/data/push.interface.ts +8 -0
- package/src/features/push/data/push.service.ts +17 -0
- package/src/features/push/data/push.ts +18 -0
- package/src/features/push/index.ts +2 -0
- package/src/features/push/push.module.ts +10 -0
- package/src/features/role/data/index.ts +4 -0
- package/src/features/role/data/role.fields.ts +8 -0
- package/src/features/role/data/role.interface.ts +16 -0
- package/src/features/role/data/role.service.ts +117 -0
- package/src/features/role/data/role.ts +62 -0
- package/src/features/role/index.ts +2 -0
- package/src/features/role/role.module.ts +10 -0
- package/src/features/s3/data/index.ts +3 -0
- package/src/features/s3/data/s3.interface.ts +11 -0
- package/src/features/s3/data/s3.service.ts +30 -0
- package/src/features/s3/data/s3.ts +60 -0
- package/src/features/s3/index.ts +2 -0
- package/src/features/s3/s3.module.ts +10 -0
- package/src/features/search/index.ts +1 -0
- package/src/features/search/interfaces/index.ts +1 -0
- package/src/features/search/interfaces/search.result.interface.ts +3 -0
- package/src/features/user/author.module.ts +10 -0
- package/src/features/user/components/index.ts +2 -0
- package/src/features/user/components/lists/ContributorsList.tsx +41 -0
- package/src/features/user/components/lists/index.ts +1 -0
- package/src/features/user/components/widgets/UserAvatar.tsx +86 -0
- package/src/features/user/components/widgets/index.ts +1 -0
- package/src/features/user/contexts/CurrentUserContext.tsx +156 -0
- package/src/features/user/contexts/index.ts +1 -0
- package/src/features/user/data/index.ts +4 -0
- package/src/features/user/data/user.fields.ts +8 -0
- package/src/features/user/data/user.interface.ts +41 -0
- package/src/features/user/data/user.service.ts +246 -0
- package/src/features/user/data/user.ts +162 -0
- package/src/features/user/index.ts +4 -0
- package/src/features/user/user.module.ts +21 -0
- package/src/hooks/TableGeneratorRegistry.ts +53 -0
- package/src/hooks/index.ts +33 -0
- package/src/hooks/types.ts +35 -0
- package/src/hooks/url.rewriter.ts +22 -0
- package/src/hooks/useCustomD3Graph.tsx +705 -0
- package/src/hooks/useDataListRetriever.ts +349 -0
- package/src/hooks/useDebounce.ts +33 -0
- package/src/hooks/usePageUrlGenerator.ts +50 -0
- package/src/hooks/useTableGenerator.ts +16 -0
- package/src/i18n/config.ts +73 -0
- package/src/i18n/index.ts +18 -0
- package/src/index.ts +16 -0
- package/src/interfaces/breadcrumb.item.data.interface.ts +4 -0
- package/src/interfaces/d3.link.interface.ts +7 -0
- package/src/interfaces/d3.node.interface.ts +12 -0
- package/src/interfaces/index.ts +3 -0
- package/src/permissions/check.ts +127 -0
- package/src/permissions/index.ts +2 -0
- package/src/permissions/types.ts +109 -0
- package/src/roles/config.ts +46 -0
- package/src/roles/index.ts +1 -0
- package/src/server/cache.ts +28 -0
- package/src/server/index.ts +3 -0
- package/src/server/request.ts +113 -0
- package/src/server/token.ts +10 -0
- package/src/shadcnui/custom/kanban.tsx +1001 -0
- package/src/shadcnui/custom/link.tsx +18 -0
- package/src/shadcnui/custom/multi-select.tsx +382 -0
- package/src/shadcnui/index.ts +49 -0
- package/src/shadcnui/ui/accordion.tsx +52 -0
- package/src/shadcnui/ui/alert-dialog.tsx +141 -0
- package/src/shadcnui/ui/alert.tsx +43 -0
- package/src/shadcnui/ui/avatar.tsx +50 -0
- package/src/shadcnui/ui/badge.tsx +40 -0
- package/src/shadcnui/ui/breadcrumb.tsx +115 -0
- package/src/shadcnui/ui/button.tsx +51 -0
- package/src/shadcnui/ui/calendar.tsx +73 -0
- package/src/shadcnui/ui/card.tsx +43 -0
- package/src/shadcnui/ui/carousel.tsx +225 -0
- package/src/shadcnui/ui/chart.tsx +320 -0
- package/src/shadcnui/ui/checkbox.tsx +29 -0
- package/src/shadcnui/ui/collapsible.tsx +11 -0
- package/src/shadcnui/ui/command.tsx +155 -0
- package/src/shadcnui/ui/context-menu.tsx +179 -0
- package/src/shadcnui/ui/dialog.tsx +96 -0
- package/src/shadcnui/ui/drawer.tsx +89 -0
- package/src/shadcnui/ui/dropdown-menu.tsx +205 -0
- package/src/shadcnui/ui/form.tsx +138 -0
- package/src/shadcnui/ui/hover-card.tsx +29 -0
- package/src/shadcnui/ui/input.tsx +21 -0
- package/src/shadcnui/ui/label.tsx +26 -0
- package/src/shadcnui/ui/navigation-menu.tsx +168 -0
- package/src/shadcnui/ui/popover.tsx +33 -0
- package/src/shadcnui/ui/progress.tsx +25 -0
- package/src/shadcnui/ui/radio-group.tsx +37 -0
- package/src/shadcnui/ui/resizable.tsx +47 -0
- package/src/shadcnui/ui/scroll-area.tsx +40 -0
- package/src/shadcnui/ui/select.tsx +164 -0
- package/src/shadcnui/ui/separator.tsx +28 -0
- package/src/shadcnui/ui/sheet.tsx +139 -0
- package/src/shadcnui/ui/sidebar.tsx +677 -0
- package/src/shadcnui/ui/skeleton.tsx +13 -0
- package/src/shadcnui/ui/slider.tsx +25 -0
- package/src/shadcnui/ui/sonner.tsx +25 -0
- package/src/shadcnui/ui/switch.tsx +31 -0
- package/src/shadcnui/ui/table.tsx +120 -0
- package/src/shadcnui/ui/tabs.tsx +55 -0
- package/src/shadcnui/ui/textarea.tsx +24 -0
- package/src/shadcnui/ui/toggle.tsx +39 -0
- package/src/shadcnui/ui/tooltip.tsx +61 -0
- package/src/unified/JsonApiRequest.ts +325 -0
- package/src/unified/index.ts +1 -0
- package/src/utils/blocknote-diff.util.ts +815 -0
- package/src/utils/blocknote-word-diff-renderer.util.ts +413 -0
- package/src/utils/cn.ts +6 -0
- package/src/utils/compose-refs.ts +61 -0
- package/src/utils/date-formatter.ts +53 -0
- package/src/utils/exists.ts +7 -0
- package/src/utils/index.ts +15 -0
- package/src/utils/schemas/entity.object.schema.ts +8 -0
- package/src/utils/schemas/index.ts +2 -0
- package/src/utils/schemas/user.object.schema.ts +9 -0
- package/src/utils/table-options.ts +67 -0
- 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,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
|
+
}
|