@antscorp/antsomi-ui 1.3.5-beta.235 → 1.3.5-beta.237

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 (38) hide show
  1. package/es/components/molecules/AccountSelection/AccountListing.d.ts +0 -1
  2. package/es/components/molecules/AccountSelection/AccountListing.js +1 -13
  3. package/es/components/molecules/AccountSelection/AccountSelection.js +3 -3
  4. package/es/components/molecules/HeaderV2/HeaderV2.js +11 -25
  5. package/es/components/molecules/TemplateSaveAs/TemplateSaveAs.js +1 -0
  6. package/es/components/molecules/TemplateSaveAs/components/ImageSlider/BigImage.d.ts +1 -0
  7. package/es/components/molecules/TemplateSaveAs/components/ImageSlider/BigImage.js +2 -2
  8. package/es/components/molecules/TemplateSaveAs/components/ImageSlider/ImageSlider.d.ts +1 -0
  9. package/es/components/molecules/TemplateSaveAs/components/ImageSlider/ImageSlider.js +3 -3
  10. package/es/components/molecules/TemplateSaveAs/components/ImageSlider/SmallImage.d.ts +1 -0
  11. package/es/components/molecules/TemplateSaveAs/components/ImageSlider/SmallImage.js +2 -2
  12. package/es/components/molecules/ThumbnailCard/ThumbnailCard.js +2 -2
  13. package/es/components/molecules/ThumbnailCard/types.d.ts +2 -1
  14. package/es/components/organism/LeftMenu/components/CustomMenu/index.d.ts +6 -0
  15. package/es/components/organism/LeftMenu/components/CustomMenu/index.js +14 -0
  16. package/es/components/organism/LeftMenu/components/common/ChildMenu/index.js +35 -16
  17. package/es/components/organism/LeftMenu/components/common/ChildMenu/utils.d.ts +3 -2
  18. package/es/components/organism/LeftMenu/components/common/ChildMenu/utils.js +3 -3
  19. package/es/components/organism/LeftMenu/components/index.d.ts +1 -0
  20. package/es/components/organism/LeftMenu/components/index.js +1 -0
  21. package/es/components/organism/LeftMenu/constants/sampleData.d.ts +1 -0
  22. package/es/components/organism/LeftMenu/constants/sampleData.js +90 -0
  23. package/es/components/organism/LeftMenu/hooks/index.d.ts +1 -0
  24. package/es/components/organism/LeftMenu/hooks/index.js +1 -0
  25. package/es/components/organism/LeftMenu/hooks/useLeftMenu.d.ts +3 -0
  26. package/es/components/organism/LeftMenu/hooks/useLeftMenu.js +86 -35
  27. package/es/components/organism/LeftMenu/hooks/useNavigatePath.js +2 -4
  28. package/es/components/organism/LeftMenu/index.d.ts +7 -3
  29. package/es/components/organism/LeftMenu/index.js +43 -24
  30. package/es/components/organism/LeftMenu/stores/index.d.ts +6 -0
  31. package/es/components/organism/LeftMenu/stores/index.js +10 -1
  32. package/es/components/organism/LeftMenu/styled.js +1 -9
  33. package/es/components/organism/LeftMenu/utils/index.d.ts +31 -0
  34. package/es/components/organism/LeftMenu/utils/index.js +50 -0
  35. package/es/providers/AppConfigProvider/contexts/index.d.ts +5 -2
  36. package/es/providers/AppConfigProvider/contexts/index.js +6 -3
  37. package/es/providers/AppConfigProvider/selector.d.ts +1 -1
  38. package/package.json +1 -1
@@ -11,7 +11,6 @@ export interface AccountListingProps {
11
11
  };
12
12
  showAllAccount?: boolean;
13
13
  currentAccount?: string | 'ALL_ACCOUNT';
14
- onLabelChange?: (label: string) => void;
15
14
  onChange?: (userId: number | 'ALL_ACCOUNT') => void;
16
15
  }
17
16
  export declare const AccountListing: React.FC<AccountListingProps>;
@@ -7,7 +7,7 @@ import { PORTALS_IDS } from '@antscorp/antsomi-ui/es/constants';
7
7
  import { searchStringQuery } from '@antscorp/antsomi-ui/es/utils';
8
8
  import { useGetAccountList, useGetPermissionAccountList, useGetRecentAccount } from '../../..';
9
9
  export const AccountListing = props => {
10
- const { className, apiConfig, showAllAccount = true, currentAccount, onLabelChange, onChange, } = props;
10
+ const { className, apiConfig, showAllAccount = true, currentAccount, onChange } = props;
11
11
  const { domain, token, userId, appCode, portalId } = apiConfig;
12
12
  const { data } = useGetAccountList({
13
13
  apiConfig,
@@ -48,21 +48,9 @@ export const AccountListing = props => {
48
48
  return (data === null || data === void 0 ? void 0 : data.entries) || [];
49
49
  }, [data === null || data === void 0 ? void 0 : data.entries, permissionAccountList, portalId]);
50
50
  const [searchValue, setSearchValue] = useState('');
51
- // const currentAccountInfo = useMemo(
52
- // () => accountData?.find(account => String(account.userId) === currentAccount),
53
- // [accountData, currentAccount],
54
- // );
55
51
  const isAllAccount = !currentAccount || currentAccount === 'ALL_ACCOUNT';
56
- // useEffect(() => {
57
- // // HACK: this use for popover input label
58
- // const inputLabel = isAllAccount ? 'All account' : currentAccountInfo?.userName;
59
- // onLabelChange?.(inputLabel);
60
- // }, [currentAccountInfo?.userName, isAllAccount, onLabelChange]);
61
52
  const handleSelectAccount = useCallback((userId) => {
62
53
  onChange === null || onChange === void 0 ? void 0 : onChange(userId);
63
- // const userInfo = accountData?.find(account => String(account.userId) === userId);
64
- // const inputLabel = userId === 'ALL_ACCOUNT' ? 'All account' : userInfo?.userName;
65
- // onLabelChange?.(inputLabel);
66
54
  }, [onChange]);
67
55
  return (React.createElement(AccountSelectionStyled, { className: className },
68
56
  React.createElement(Input, { placeholder: "Search", suffix: React.createElement(Icon, { type: "icon-ants-search-2", style: { fontSize: '24px' } }), value: searchValue, onChange: event => setSearchValue(event.target.value) }),
@@ -24,6 +24,7 @@ export const AccountSelection = props => {
24
24
  // const [searchValue, setSearchValue] = useState<string>('');
25
25
  const { apiConfig, currentAccount } = props;
26
26
  const { domain, token, userId, appCode, portalId } = apiConfig;
27
+ // Queries
27
28
  const { data } = useGetAccountList({
28
29
  apiConfig,
29
30
  options: {
@@ -57,6 +58,7 @@ export const AccountSelection = props => {
57
58
  return (data === null || data === void 0 ? void 0 : data.entries) || [];
58
59
  }, [data === null || data === void 0 ? void 0 : data.entries, permissionAccountList, portalId]);
59
60
  const currentAccountInfo = useMemo(() => accountData === null || accountData === void 0 ? void 0 : accountData.find(account => String(account.userId) === currentAccount), [accountData, currentAccount]);
61
+ /** If oid is null, default is 'ALL_ACCOUNT' */
60
62
  const isAllAccount = !currentAccount || currentAccount === 'ALL_ACCOUNT';
61
63
  useEffect(() => {
62
64
  const newLabel = isAllAccount ? 'All account' : currentAccountInfo === null || currentAccountInfo === void 0 ? void 0 : currentAccountInfo.userName;
@@ -66,9 +68,7 @@ export const AccountSelection = props => {
66
68
  setOpen(false);
67
69
  onChange === null || onChange === void 0 ? void 0 : onChange(userId);
68
70
  }, [onChange]);
69
- return (show && (React.createElement(Popover, { content: React.createElement(AccountListing, Object.assign({}, accountListingProps, { onChange: handleSelectAccount, onLabelChange: newLabel => setInputLabel(newLabel) })), trigger: "click", overlayInnerStyle: { padding: 0 },
70
- // getPopupContainer={triggerNode => triggerNode}
71
- getPopupContainer: getPopupContainer, placement: "bottomLeft", arrow: false, open: open, onOpenChange: setOpen, zIndex: zIndex || 1300 },
71
+ return (show && (React.createElement(Popover, { content: React.createElement(AccountListing, Object.assign({}, accountListingProps, { onChange: handleSelectAccount })), trigger: "click", overlayInnerStyle: { padding: 0 }, getPopupContainer: getPopupContainer, placement: "bottomLeft", arrow: false, open: open, onOpenChange: setOpen, zIndex: zIndex || 1300 },
72
72
  React.createElement(PopoverFieldStyled, { className: `${inputStyle || ''} ${inputClassName || ''}` },
73
73
  React.createElement(Tooltip, { title: inputLabel },
74
74
  React.createElement("span", { className: !inputLabel ? 'is-placeholder' : '' }, inputLabel || 'Select an account')),
@@ -9,7 +9,7 @@ var __rest = (this && this.__rest) || function (s, e) {
9
9
  }
10
10
  return t;
11
11
  };
12
- import React, { memo, useEffect, useMemo, useState } from 'react';
12
+ import React, { memo, useMemo, useState } from 'react';
13
13
  // Styled
14
14
  import { HeaderV2Styled } from './styled';
15
15
  // Components
@@ -34,22 +34,6 @@ export const HeaderV2 = memo(props => {
34
34
  const oidParam = urlParams.get('oid');
35
35
  // States
36
36
  const [selectedAccount, setSelectedAccount] = useState(`${useURLParam ? oidParam !== null && oidParam !== void 0 ? oidParam : '' : (_a = currentAccount !== null && currentAccount !== void 0 ? currentAccount : userId) !== null && _a !== void 0 ? _a : ''}`);
37
- // Side effects
38
- useEffect(() => {
39
- if (!useURLParam)
40
- return;
41
- if (!oidParam) {
42
- // urlParams.append('oid', 'ALL_ACCOUNT');
43
- // urlParams.append('oid', `${currentAccount ?? userId ?? 'ALL_ACCOUNT'}`);
44
- // const newUrl = `${window.location.pathname}?${urlParams.toString()}${window.location.hash}`;
45
- // window.history.replaceState(null, '', newUrl);
46
- }
47
- else {
48
- // const newUrl = `${window.location.pathname}?${urlParams.toString()}${window.location.hash}`;
49
- // window.history.replaceState(null, '', newUrl);
50
- }
51
- // eslint-disable-next-line react-hooks/exhaustive-deps
52
- }, [oidParam, useURLParam, userId]);
53
37
  // Memo
54
38
  const accountSelectionDomain = useMemo(() => {
55
39
  var _a;
@@ -96,16 +80,18 @@ export const HeaderV2 = memo(props => {
96
80
  token,
97
81
  lang: language,
98
82
  };
83
+ // Handlers
84
+ const handleChangeAccount = (userId) => {
85
+ setSelectedAccount(String(userId));
86
+ if (useURLParam) {
87
+ urlParams.set('oid', `${userId}`);
88
+ history.replace({ search: urlParams.toString() });
89
+ }
90
+ onSelectAccount === null || onSelectAccount === void 0 ? void 0 : onSelectAccount(String(userId));
91
+ };
99
92
  return (React.createElement(HeaderV2Styled, { className: className || '', style: style },
100
93
  React.createElement("div", { className: "left-side" },
101
- React.createElement(AccountSelection, Object.assign({}, accountSelectionProps, { currentAccount: selectedAccount, apiConfig: accountSelectionApiConfig, onChange: userId => {
102
- setSelectedAccount(String(userId));
103
- if (useURLParam) {
104
- urlParams.set('oid', `${userId}`);
105
- history.replace({ search: urlParams.toString() });
106
- }
107
- onSelectAccount === null || onSelectAccount === void 0 ? void 0 : onSelectAccount(String(userId));
108
- }, inputStyle: inputStyle })),
94
+ React.createElement(AccountSelection, Object.assign({}, accountSelectionProps, { currentAccount: selectedAccount, apiConfig: accountSelectionApiConfig, onChange: handleChangeAccount, inputStyle: inputStyle })),
109
95
  typeof pageTitle === 'string' ? React.createElement("div", { className: "page-title" }, pageTitle) : pageTitle),
110
96
  React.createElement("div", { className: "right-side" },
111
97
  showHelp ? React.createElement(Help, Object.assign({ configs: helpConfigProps }, helpProps)) : null,
@@ -32,6 +32,7 @@ export const TemplateSaveAs = props => {
32
32
  const form = formProps || internalForm;
33
33
  const { show: isShowShareAccess } = shareAccess, shareAccessProps = __rest(shareAccess, ["show"]);
34
34
  const { label: imageReviewLabel } = imageReview, restImageReviewProps = __rest(imageReview, ["label"]);
35
+ console.log('🚀 ~ restImageReviewProps:', restImageReviewProps);
35
36
  const { isLoading, label: templateLabel, defaultNewTemplateName = `Untitled Media Template#${dayjs().format('YYYY-MM-DD HH:mm:ss')}`, selectPlaceholder = 'Please select an item', placeholder = 'Enter your media template name', validateName, onSearch, onNamePopupScroll, onScrollToBottom, } = templateNamesOptions || {};
36
37
  const { label: descriptionLabel, placeholder: descriptionPlaceholder } = descriptionOptions || {};
37
38
  const { saveNewText = 'Save as a new media template', saveExistText = 'Save as an existing media template', saveNewValue = 'save-new', saveExistValue = 'save-exist', } = saveOptions || {};
@@ -4,6 +4,7 @@ interface BigImageProps {
4
4
  showSkeleton?: boolean;
5
5
  isDefaultThumbnail?: boolean;
6
6
  isHideDefaultButton?: boolean;
7
+ imageFit?: React.CSSProperties['objectFit'];
7
8
  index: number;
8
9
  onClickDefaultButton?: (index: number) => void;
9
10
  }
@@ -4,13 +4,13 @@ import { Button } from '@antscorp/antsomi-ui/es/components';
4
4
  import Icon from '@antscorp/icons';
5
5
  import { checkShowSkeletonBaseUrl, getUrlNoCache } from '@antscorp/antsomi-ui/es/utils';
6
6
  export const BigImage = React.memo(props => {
7
- const { url, isDefaultThumbnail, isHideDefaultButton, index, showSkeleton, onClickDefaultButton, } = props;
7
+ const { url, isDefaultThumbnail, isHideDefaultButton, index, showSkeleton, imageFit, onClickDefaultButton, } = props;
8
8
  const currentTime = useMemo(() => new Date(), []);
9
9
  const timeGenerated = Math.floor(currentTime.getTime() / 1000 - (currentTime.getTime() % 3)) * 1000;
10
10
  const urlNoCache = getUrlNoCache(url, timeGenerated);
11
11
  const showSkeletonMemo = showSkeleton !== undefined ? showSkeleton : checkShowSkeletonBaseUrl(urlNoCache);
12
12
  return (React.createElement("div", { className: `image-container--big ${!showSkeletonMemo ? 'hide-skeleton' : ''}` },
13
- url ? (React.createElement(Image, { preview: false, width: "100%", height: "100%", src: urlNoCache })) : null,
13
+ url ? (React.createElement(Image, { preview: false, width: "100%", height: "100%", src: urlNoCache, style: { objectFit: imageFit } })) : null,
14
14
  isHideDefaultButton ? null : (React.createElement(Button, { className: "set-default-button", onClick: () => onClickDefaultButton && onClickDefaultButton(index) },
15
15
  isDefaultThumbnail ? React.createElement(Icon, { type: "icon-ants-check", style: { fontSize: '12px' } }) : null,
16
16
  isDefaultThumbnail ? 'Default thumbnail' : 'Set as default'))));
@@ -11,6 +11,7 @@ export interface ImageSliderProps {
11
11
  imageWidth?: number;
12
12
  imageHeight?: number;
13
13
  infinity?: boolean;
14
+ thumbnailFit?: React.CSSProperties['objectFit'];
14
15
  /**
15
16
  * Default thumbnail - order of default thumbnail in thumbnails array
16
17
  */
@@ -12,7 +12,7 @@ const ButtonSlider = React.memo(({ direction, type, onClick }) => (React.createE
12
12
  : `change-preview-button change-preview-button--${direction}`, onClick: onClick },
13
13
  React.createElement(Icon, { type: `icon-ants-angle-${direction}` }))));
14
14
  export const ImageSlider = props => {
15
- const { thumbnails, isLoading, className, skeleton, previewNavigation, slidesPerView = 3, hideThumbnailsList, hideDefaultButton, imageWidth = 330, imageHeight = 230, infinity = true, defaultThumbnail, onSelectDefault, } = props;
15
+ const { thumbnails, isLoading, className, skeleton, previewNavigation, slidesPerView = 3, hideThumbnailsList, hideDefaultButton, imageWidth = 330, imageHeight = 230, infinity = true, defaultThumbnail, thumbnailFit, onSelectDefault, } = props;
16
16
  const sliderContainerRef = useRef(null);
17
17
  const thumbnailSelectedRef = useRef(null);
18
18
  const smallImageWidth = useMemo(() => imageWidth / slidesPerView - (10 * (slidesPerView - 1)) / slidesPerView, [imageWidth, slidesPerView]);
@@ -55,8 +55,8 @@ export const ImageSlider = props => {
55
55
  }
56
56
  }, [onSelectDefault]);
57
57
  return (React.createElement(ImageSliderStyled, { className: className || '', "$skeleton": skeleton, "$imageWidth": imageWidth, "$smallImageWidth": smallImageWidth, "$imageHeight": imageHeight, vertical: true, gap: 10 },
58
- React.createElement(Flex, { className: "thumbnail-preview-container", ref: thumbnailSelectedRef }, isLoading ? (React.createElement(Spin, { spinning: true })) : (thumbnails.map((url, index) => (React.createElement(BigImage, { key: `image-big_${url}_${index * 2}`, url: url, index: index, showSkeleton: skeleton, isDefaultThumbnail: defaultThumbnailInternal === index, isHideDefaultButton: hideDefaultButton || hideThumbnailsList || thumbnails.length <= 1, onClickDefaultButton: handleClickDefault }))))),
59
- hideThumbnailsList || thumbnails.length <= 1 ? null : (React.createElement(Flex, { gap: 10, className: "thumbnail-slider-container", ref: sliderContainerRef, style: { justifyContent: thumbnails.length < slidesPerView ? 'center' : 'flex-start' } }, isLoading ? (React.createElement(Spin, { spinning: true })) : (thumbnails.map((url, index) => (React.createElement(SmallImage, { key: `${url}_${index * 2}`, showSkeleton: skeleton, isSelected: index === selectedThumbnail, url: url, index: index, onClick: handleSelectThumbnail })))))),
58
+ React.createElement(Flex, { className: "thumbnail-preview-container", ref: thumbnailSelectedRef }, isLoading ? (React.createElement(Spin, { spinning: true })) : (thumbnails.map((url, index) => (React.createElement(BigImage, { key: `image-big_${url}_${index * 2}`, url: url, index: index, showSkeleton: skeleton, isDefaultThumbnail: defaultThumbnailInternal === index, isHideDefaultButton: hideDefaultButton || hideThumbnailsList || thumbnails.length <= 1, imageFit: thumbnailFit, onClickDefaultButton: handleClickDefault }))))),
59
+ hideThumbnailsList || thumbnails.length <= 1 ? null : (React.createElement(Flex, { gap: 10, className: "thumbnail-slider-container", ref: sliderContainerRef, style: { justifyContent: thumbnails.length < slidesPerView ? 'center' : 'flex-start' } }, isLoading ? (React.createElement(Spin, { spinning: true })) : (thumbnails.map((url, index) => (React.createElement(SmallImage, { imageFit: thumbnailFit, key: `${url}_${index * 2}`, showSkeleton: skeleton, isSelected: index === selectedThumbnail, url: url, index: index, onClick: handleSelectThumbnail })))))),
60
60
  previewNavigation && !isLoading ? (React.createElement(React.Fragment, null,
61
61
  React.createElement(ButtonSlider, { direction: "left", type: "preview", onClick: handlePrevThumbnail }),
62
62
  React.createElement(ButtonSlider, { direction: "right", type: "preview", onClick: handleNextThumbnail }))) : null,
@@ -4,6 +4,7 @@ interface SmallImageProps {
4
4
  isSelected?: boolean;
5
5
  index: number;
6
6
  showSkeleton?: boolean;
7
+ imageFit?: React.CSSProperties['objectFit'];
7
8
  onClick?: (index: number) => void;
8
9
  }
9
10
  export declare const SmallImage: React.FC<SmallImageProps>;
@@ -2,12 +2,12 @@ import React, { useMemo } from 'react';
2
2
  import { Image } from 'antd';
3
3
  import { checkShowSkeletonBaseUrl, getUrlNoCache } from '@antscorp/antsomi-ui/es/utils';
4
4
  export const SmallImage = React.memo(props => {
5
- const { url, isSelected, index, onClick, showSkeleton } = props;
5
+ const { url, isSelected, index, imageFit, showSkeleton, onClick } = props;
6
6
  const currentTime = useMemo(() => new Date(), []);
7
7
  const timeGenerated = Math.floor(currentTime.getTime() / 1000 - (currentTime.getTime() % 3)) * 1000;
8
8
  const urlNoCache = getUrlNoCache(url, timeGenerated);
9
9
  const showSkeletonMemo = showSkeleton !== undefined ? showSkeleton : checkShowSkeletonBaseUrl(urlNoCache);
10
- return (React.createElement("div", { className: `image-container--small ${isSelected ? 'image-container--selected' : ''} ${!showSkeletonMemo ? 'hide-skeleton' : ''}`, onClick: () => onClick && onClick(index) }, url ? (React.createElement(Image, { preview: false, width: "100%", height: "100%",
10
+ return (React.createElement("div", { className: `image-container--small ${isSelected ? 'image-container--selected' : ''} ${!showSkeletonMemo ? 'hide-skeleton' : ''}`, onClick: () => onClick && onClick(index) }, url ? (React.createElement(Image, { preview: false, width: "100%", height: "100%", style: { objectFit: imageFit },
11
11
  // src={imageUrl}
12
12
  // src={url}
13
13
  src: urlNoCache })) : null));
@@ -32,7 +32,7 @@ import { getUrlNoCache, checkShowSkeletonBaseUrl } from '@antscorp/antsomi-ui/es
32
32
  const cacheValue = new Date().getTime();
33
33
  export const ThumbnailCard = memo(props => {
34
34
  const [modal, contextHolder] = Modal.useModal();
35
- const { id, name, width = THUMBNAIL_CARD_DEFAULT_WIDTH, height = THUMBNAIL_CARD_DEFAULT_HEIGHT, thumbnail, removable = true, actionAvailable = true, showSkeleton, loading = false, removeModalProps, editBtnProps, previewBtnProps, onClickWrapper } = props, restOfProps = __rest(props, ["id", "name", "width", "height", "thumbnail", "removable", "actionAvailable", "showSkeleton", "loading", "removeModalProps", "editBtnProps", "previewBtnProps", "onClickWrapper"]);
35
+ const { id, name, width = THUMBNAIL_CARD_DEFAULT_WIDTH, height = THUMBNAIL_CARD_DEFAULT_HEIGHT, thumbnail, thumbnailFit, removable = true, actionAvailable = true, showSkeleton, loading = false, removeModalProps, editBtnProps, previewBtnProps, onClickWrapper } = props, restOfProps = __rest(props, ["id", "name", "width", "height", "thumbnail", "thumbnailFit", "removable", "actionAvailable", "showSkeleton", "loading", "removeModalProps", "editBtnProps", "previewBtnProps", "onClickWrapper"]);
36
36
  const _a = removeModalProps || {}, { onOk } = _a, restOfRemoveTemplateModalProps = __rest(_a, ["onOk"]);
37
37
  const _b = editBtnProps || {}, { text: editText = 'Use template', onClick: onClickEdit } = _b, restOfEditBtnProps = __rest(_b, ["text", "onClick"]);
38
38
  const _c = previewBtnProps || {}, { text: previewText = 'Preview', onClick: onClickPreview } = _c, restOfPreviewBtnProps = __rest(_c, ["text", "onClick"]);
@@ -60,7 +60,7 @@ export const ThumbnailCard = memo(props => {
60
60
  };
61
61
  return (React.createElement(Flex, Object.assign({ gap: 10, vertical: true }, restOfProps, { style: Object.assign({ width }, restOfProps.style) }),
62
62
  React.createElement(ThumbnailCardWrapper, { "$showSkeleton": showSkeletonMemo, style: { height, cursor: actionAvailable ? 'default' : 'pointer' }, onClick: handleWrapperClick },
63
- React.createElement("div", { className: "screen" }, thumbnail && React.createElement("img", { src: getUrlNoCache(thumbnail, cacheValue), alt: "" })),
63
+ React.createElement("div", { className: "screen" }, thumbnail && (React.createElement("img", { src: getUrlNoCache(thumbnail, cacheValue), alt: "", style: { objectFit: thumbnailFit } }))),
64
64
  actionAvailable && !loading && (React.createElement(React.Fragment, null,
65
65
  React.createElement(Flex, { className: "center-action", align: "center", gap: 10, vertical: true },
66
66
  React.createElement(Button, Object.assign({ type: "primary", className: "animate__animated animate__fadeIn" }, restOfEditBtnProps, { onClick: e => {
@@ -1,5 +1,5 @@
1
1
  import { ModalFuncProps, ButtonProps } from 'antd';
2
- import { HTMLAttributes } from 'react';
2
+ import React, { HTMLAttributes } from 'react';
3
3
  export type TThumbnailCardId = string | number;
4
4
  export type TRemoveModalProps = Omit<ModalFuncProps, 'onOk'> & {
5
5
  onOk?: (id: TThumbnailCardId) => void;
@@ -21,5 +21,6 @@ export interface ThumbnailCardProps extends Omit<HTMLAttributes<HTMLDivElement>,
21
21
  previewBtnProps?: TThumbnailButton;
22
22
  actionAvailable?: boolean;
23
23
  removeModalProps?: TRemoveModalProps;
24
+ thumbnailFit?: React.CSSProperties['objectFit'];
24
25
  onClickWrapper?: (id: TThumbnailCardId) => void;
25
26
  }
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ interface CustomMenuProps {
3
+ onMenuItemClick?: (key: string, keyPath: string[]) => void;
4
+ }
5
+ export declare const CustomMenu: React.FC<CustomMenuProps>;
6
+ export {};
@@ -0,0 +1,14 @@
1
+ // Libraries
2
+ import React, { memo } from 'react';
3
+ // Components
4
+ import { ChildMenu } from '../common';
5
+ // Hooks
6
+ import { useLeftMenuStore } from '../../stores';
7
+ export const CustomMenu = memo(({ onMenuItemClick }) => {
8
+ const appCustomMenuChildren = useLeftMenuStore(store => store.state.customMenuChildren);
9
+ const customActiveAppKey = useLeftMenuStore(store => store.state.customActiveAppKey);
10
+ const handleMenuItemClick = (key, keyPath) => {
11
+ onMenuItemClick === null || onMenuItemClick === void 0 ? void 0 : onMenuItemClick(key, [...keyPath, customActiveAppKey]);
12
+ };
13
+ return React.createElement(ChildMenu, { items: appCustomMenuChildren, onMenuClick: handleMenuItemClick });
14
+ });
@@ -14,44 +14,60 @@ import { IconWrapper } from '../../../styled';
14
14
  // Constants
15
15
  import { ICON_SIZE } from '../../../constants';
16
16
  // Utils
17
- import { findActiveAppCodeByUrl, findLastMatchedItemByUrl } from '../../../utils';
17
+ import { findActiveAppCodeByUrl, findLastMatchedItemByUrl, getMenuItem } from '../../../utils';
18
18
  import { recursiveFindParentOfActiveItem } from './utils';
19
- import { random } from '@antscorp/antsomi-ui/es/utils';
20
19
  // Hooks
21
20
  import { useLeftMenuStore } from '../../../stores';
22
- import { useNavigatePath } from '../../../hooks/useNavigatePath';
21
+ import { useNavigatePath } from '../../../hooks';
23
22
  export const ChildMenu = memo(props => {
24
23
  const { items = [], parentKey, onMenuClick } = props;
25
24
  // Stores
26
25
  const auth = useLeftMenuStore(store => { var _a; return (_a = store.state.appConfig) === null || _a === void 0 ? void 0 : _a.auth; });
27
26
  const activeAppCode = useLeftMenuStore(store => store.state.activeAppCode);
28
27
  const menuItems = useLeftMenuStore(store => store.state.menuItems);
28
+ const customItems = useLeftMenuStore(store => store.state.customItems);
29
+ const isCustomized = useLeftMenuStore(store => store.state.isCustomized);
30
+ const customActiveCurrentKey = useLeftMenuStore(store => store.state.customActiveCurrentKey);
29
31
  const setLeftMenuState = useLeftMenuStore(store => store.setState);
30
32
  // States
31
33
  const [currentActiveItem, setCurrentActiveItem] = useState('');
32
34
  const [openKeys, setOpenKeys] = useState([]);
33
- // Variables
34
- const randomMenuWrapperId = random(10);
35
35
  // Hooks
36
36
  const { pathname, hash } = window.location;
37
37
  const { isPushDifferentDomain, getPath, navigatePath } = useNavigatePath();
38
38
  // Side Effects
39
39
  useEffect(() => {
40
40
  var _a;
41
- const url = `${pathname}${hash}`;
42
- /** Active menu item by current url */
43
- const activeMenuItem = (_a = findLastMatchedItemByUrl({ url, menuItems, auth })) === null || _a === void 0 ? void 0 : _a.menu_item_code;
44
- if (activeMenuItem && activeMenuItem !== currentActiveItem) {
45
- setCurrentActiveItem(activeMenuItem);
41
+ if (isCustomized) {
42
+ setCurrentActiveItem(customActiveCurrentKey);
46
43
  /** Find parent key of active menu item to open it */
47
- const parentKey = recursiveFindParentOfActiveItem({ activeMenuItem, menuItems });
44
+ const parentKey = recursiveFindParentOfActiveItem({
45
+ activeMenuItem: customActiveCurrentKey,
46
+ menuItems: customItems,
47
+ });
48
48
  if (parentKey) {
49
49
  setOpenKeys(prev => [...prev, parentKey]);
50
50
  }
51
- /** Active App Item having code matching url */
52
- const matchAppCode = findActiveAppCodeByUrl({ menuItems, url, auth });
53
- if (matchAppCode && matchAppCode !== activeAppCode) {
54
- setLeftMenuState({ activeAppCode: matchAppCode });
51
+ }
52
+ else {
53
+ const url = `${pathname}${hash}`;
54
+ /** Active menu item by current url */
55
+ const activeMenuItem = (_a = findLastMatchedItemByUrl({ url, menuItems, auth })) === null || _a === void 0 ? void 0 : _a.menu_item_code;
56
+ if (activeMenuItem && activeMenuItem !== currentActiveItem) {
57
+ setCurrentActiveItem(activeMenuItem);
58
+ /** Find parent key of active menu item to open it */
59
+ const parentKey = recursiveFindParentOfActiveItem({
60
+ activeMenuItem,
61
+ menuItems: menuItems === null || menuItems === void 0 ? void 0 : menuItems.map(item => getMenuItem(item)),
62
+ });
63
+ if (parentKey) {
64
+ setOpenKeys(prev => [...prev, parentKey]);
65
+ }
66
+ /** Active App Item having code matching url */
67
+ const matchAppCode = findActiveAppCodeByUrl({ menuItems, url, auth });
68
+ if (matchAppCode && matchAppCode !== activeAppCode) {
69
+ setLeftMenuState({ activeAppCode: matchAppCode });
70
+ }
55
71
  }
56
72
  }
57
73
  }, [
@@ -64,6 +80,9 @@ export const ChildMenu = memo(props => {
64
80
  activeAppCode,
65
81
  menuItems,
66
82
  setLeftMenuState,
83
+ isCustomized,
84
+ customActiveCurrentKey,
85
+ customItems,
67
86
  ]);
68
87
  // Handlers
69
88
  const onClick = e => {
@@ -95,7 +114,7 @@ export const ChildMenu = memo(props => {
95
114
  };
96
115
  });
97
116
  };
98
- return (React.createElement(MenuWrapper, { className: "child-menu", id: randomMenuWrapperId },
117
+ return (React.createElement(MenuWrapper, null,
99
118
  React.createElement(Menu, { selectedKeys: [currentActiveItem], defaultOpenKeys: [currentActiveItem], openKeys: uniq(openKeys), mode: "inline", items: customMenuItems({ items }), inlineIndent: 10, onOpenChange: openKeys => {
100
119
  setOpenKeys(openKeys);
101
120
  }, expandIcon: ({ isOpen }) => (React.createElement(Icon, { type: "icon-ants-expand-more", style: {
@@ -1,5 +1,6 @@
1
1
  import { TFeatureMenu } from '@antscorp/antsomi-ui/es/models/LeftMenu';
2
2
  import { PayloadInfo } from '@antscorp/antsomi-ui/es/types';
3
+ import { TMenuItem } from '../../../types';
3
4
  /**
4
5
  * Finds the active menu item code based on the provided URL within the given menu items.
5
6
  * ATTENTION: Start finding from level 2 because level 1 contains apps
@@ -21,10 +22,10 @@ export declare const findActiveMenuItemByUrl: (args: {
21
22
  * Recursively finds the parent key of the active menu item within the given menu items.
22
23
  * @param {object} args - The arguments object.
23
24
  * @param {string} args.activeMenuItem - The key of the active menu item.
24
- * @param {TFeatureMenu[]} args.menuItems - The array of menu items to search within.
25
+ * @param {TMenuItem[]} args.menuItems - The array of menu items to search within.
25
26
  * @returns {string | undefined} The key of the parent menu item or undefined if not found.
26
27
  */
27
28
  export declare const recursiveFindParentOfActiveItem: (args: {
28
29
  activeMenuItem: string;
29
- menuItems: TFeatureMenu[];
30
+ menuItems: TMenuItem[];
30
31
  }) => string | undefined;
@@ -55,7 +55,7 @@ export const findActiveMenuItemByUrl = (args) => {
55
55
  * Recursively finds the parent key of the active menu item within the given menu items.
56
56
  * @param {object} args - The arguments object.
57
57
  * @param {string} args.activeMenuItem - The key of the active menu item.
58
- * @param {TFeatureMenu[]} args.menuItems - The array of menu items to search within.
58
+ * @param {TMenuItem[]} args.menuItems - The array of menu items to search within.
59
59
  * @returns {string | undefined} The key of the parent menu item or undefined if not found.
60
60
  */
61
61
  export const recursiveFindParentOfActiveItem = (args) => {
@@ -64,9 +64,9 @@ export const recursiveFindParentOfActiveItem = (args) => {
64
64
  const { activeMenuItem, menuItems } = args;
65
65
  let parentKey;
66
66
  for (const item of menuItems) {
67
- const isHasActiveMenuItem = !!((_a = item === null || item === void 0 ? void 0 : item.children) === null || _a === void 0 ? void 0 : _a.find(child => child.menu_item_code === activeMenuItem));
67
+ const isHasActiveMenuItem = !!((_a = item === null || item === void 0 ? void 0 : item.children) === null || _a === void 0 ? void 0 : _a.find(child => child.key === activeMenuItem));
68
68
  if (isHasActiveMenuItem) {
69
- parentKey = item.menu_item_code;
69
+ parentKey = item.key;
70
70
  break;
71
71
  }
72
72
  const findNestedParentKey = recursiveFindParentOfActiveItem({
@@ -6,3 +6,4 @@ export { MarketingMenu } from './MarketingMenu';
6
6
  export { InsightsMenu } from './InsightsMenu';
7
7
  export { TemplatesMenu } from './TemplatesMenu';
8
8
  export { SettingsMenu } from './SettingsMenu';
9
+ export { CustomMenu } from './CustomMenu';
@@ -6,3 +6,4 @@ export { MarketingMenu } from './MarketingMenu';
6
6
  export { InsightsMenu } from './InsightsMenu';
7
7
  export { TemplatesMenu } from './TemplatesMenu';
8
8
  export { SettingsMenu } from './SettingsMenu';
9
+ export { CustomMenu } from './CustomMenu';
@@ -2,3 +2,4 @@ import { TFeatureMenu } from '@antscorp/antsomi-ui/es/models/LeftMenu';
2
2
  import { TMenuItem } from '../types';
3
3
  export declare const MENU_ITEMS: TMenuItem[];
4
4
  export declare const MENU_SAMPLE: TFeatureMenu[];
5
+ export declare const CUSTOM_MENU_ITEMS: TMenuItem[];
@@ -983,3 +983,93 @@ export const MENU_SAMPLE = [
983
983
  ],
984
984
  },
985
985
  ];
986
+ export const CUSTOM_MENU_ITEMS = [
987
+ {
988
+ key: 'survey',
989
+ label: 'Survey',
990
+ icon: 'icon-ants-survey-v2',
991
+ children: [
992
+ {
993
+ key: 'ANALYSIS',
994
+ label: 'Analysis',
995
+ icon: null,
996
+ children: [
997
+ {
998
+ key: 'SQL_WORKSPACE',
999
+ label: 'SQL Workspace',
1000
+ icon: null,
1001
+ },
1002
+ {
1003
+ key: 'EXPLORE',
1004
+ label: 'Explore',
1005
+ icon: null,
1006
+ },
1007
+ {
1008
+ key: 'SCHEDULE_QUERY',
1009
+ label: 'Schedule Query',
1010
+ icon: null,
1011
+ },
1012
+ ],
1013
+ },
1014
+ {
1015
+ label: 'Reports',
1016
+ icon: null,
1017
+ key: 'REPORT',
1018
+ },
1019
+ {
1020
+ key: 'DATA_SOURCE',
1021
+ label: 'Data Sources',
1022
+ icon: null,
1023
+ },
1024
+ {
1025
+ key: 'GALLERY',
1026
+ label: 'Gallery',
1027
+ icon: null,
1028
+ },
1029
+ ],
1030
+ },
1031
+ {
1032
+ key: 'data',
1033
+ label: 'Data',
1034
+ icon: 'icon-ants-survey-data',
1035
+ children: [
1036
+ {
1037
+ key: 'COUPON',
1038
+ label: 'Coupons',
1039
+ icon: 'icon-ants-coupons',
1040
+ },
1041
+ {
1042
+ label: 'Short Links',
1043
+ icon: 'icon-ants-short-link',
1044
+ key: 'SHORT_LINK',
1045
+ },
1046
+ ],
1047
+ },
1048
+ {
1049
+ key: 'report',
1050
+ label: 'Report',
1051
+ icon: 'icon-ants-overview',
1052
+ children: [
1053
+ {
1054
+ label: 'Overview',
1055
+ icon: 'icon-ants-overview',
1056
+ key: 'JOURNEY_OVERVIEW',
1057
+ },
1058
+ {
1059
+ key: 'ALL_CHANNEL',
1060
+ label: 'All Channels',
1061
+ icon: 'icon-ants-window',
1062
+ },
1063
+ {
1064
+ key: 'CAMPAIGN',
1065
+ label: 'Campaigns',
1066
+ icon: 'icon-ants-campaign',
1067
+ },
1068
+ {
1069
+ key: 'ORCHESTRATION',
1070
+ label: 'Orchestration',
1071
+ icon: 'icon-ants-orchestration',
1072
+ },
1073
+ ],
1074
+ },
1075
+ ];
@@ -1 +1,2 @@
1
1
  export { useLeftMenu } from './useLeftMenu';
2
+ export { useNavigatePath } from './useNavigatePath';
@@ -1 +1,2 @@
1
1
  export { useLeftMenu } from './useLeftMenu';
2
+ export { useNavigatePath } from './useNavigatePath';
@@ -7,8 +7,11 @@ export declare const useLeftMenu: (props: LeftMenuProps) => {
7
7
  state: TState;
8
8
  activeAppCode: string;
9
9
  permissionMenu: import("@antscorp/antsomi-ui/es/models/LeftMenu").TFeatureMenu[];
10
+ /** Variables */
11
+ customActiveAppKey: string;
10
12
  onToggleChildMenu: () => void;
11
13
  onMouseEnter: (key: string) => void;
12
14
  onMouseLeave: () => void;
15
+ onHoverMenuBar: () => void;
13
16
  };
14
17
  export {};
@@ -4,7 +4,7 @@ import { isArray, sortBy } from 'lodash';
4
4
  // Queries
5
5
  import { useGetDashboard, useGetDestinationChannel, useGetListMenu, useGetListMenuPermission, } from '@antscorp/antsomi-ui/es/queries/LeftMenu';
6
6
  // Utils
7
- import { findActiveAppCodeByUrl, findLastMatchedItemByUrl, findPathOfActiveItem, flattenMenuArray, getMappingAppChildren, recursivePermissionMenu, } from '../utils';
7
+ import { findActiveAppCodeByChild, findActiveAppCodeByUrl, findLastMatchedItemByUrl, findPathOfActiveItem, flattenMenuArray, getMappingAppChildren, recursivePermissionMenu, } from '../utils';
8
8
  // Store
9
9
  import { useLeftMenuStore } from '../stores';
10
10
  import { POST_MESSAGE_TYPES } from '@antscorp/antsomi-ui/es/constants/postMessage';
@@ -14,12 +14,13 @@ const initialState = {
14
14
  };
15
15
  export const useLeftMenu = (props) => {
16
16
  // Props
17
- const { objectType = 'OVERVIEW', objectId = 1, isGrouped = true, appConfig, onActiveMenuCodeChange, } = props;
17
+ const { objectType = 'OVERVIEW', objectId = 1, isGrouped = true, appConfig, customization, onActiveMenuCodeChange, } = props;
18
+ const { isExpandable = true, isCustomized = false, items, activeKey } = customization || {};
18
19
  // Hooks
19
20
  const { pathname, hash } = window.location;
20
- console.log({ pathname, hash, locationWindow: window.location });
21
21
  // Stores
22
22
  const activeAppCode = useLeftMenuStore(store => store.state.activeAppCode);
23
+ const customActiveAppKey = useLeftMenuStore(store => store.state.customActiveAppKey);
23
24
  const setLeftMenuState = useLeftMenuStore(store => store.setState);
24
25
  // Destructuring variables
25
26
  const { auth, languageCode } = appConfig || {};
@@ -112,37 +113,71 @@ export const useLeftMenu = (props) => {
112
113
  // Side Effects
113
114
  // Receive Post Message
114
115
  useEffect(() => {
115
- window.addEventListener('message', receivePostMessage);
116
- return () => window.removeEventListener('message', receivePostMessage);
116
+ if (!isCustomized) {
117
+ window.addEventListener('message', receivePostMessage);
118
+ return () => window.removeEventListener('message', receivePostMessage);
119
+ }
117
120
  // eslint-disable-next-line react-hooks/exhaustive-deps
118
121
  }, []);
119
122
  useEffect(() => {
120
- if (appConfig)
121
- setLeftMenuState({ appConfig });
122
- }, [appConfig, setLeftMenuState]);
123
+ setLeftMenuState({ appConfig, isCustomized });
124
+ }, [appConfig, isCustomized, setLeftMenuState]);
125
+ /**
126
+ * Active Custom App Key when active key is changed
127
+ */
128
+ useEffect(() => {
129
+ if (activeKey && items)
130
+ setLeftMenuState({
131
+ customActiveAppKey: findActiveAppCodeByChild({
132
+ activeCurrentKey: activeKey,
133
+ menuItems: items,
134
+ }) || '',
135
+ customActiveCurrentKey: activeKey,
136
+ });
137
+ }, [activeKey, appConfig, isCustomized, items, setLeftMenuState]);
123
138
  /**
124
139
  * Change list menu children when hovering or activating items, giving priority to hovering items
125
140
  */
126
141
  useEffect(() => {
127
- var _a;
128
- const appCode = state.hoverItem ? state.hoverItem : activeAppCode;
129
- if (isArray(mappingChildrenMenu)) {
142
+ var _a, _b;
143
+ if (isCustomized) {
144
+ const appActiveKey = state.hoverItem ? state.hoverItem : customActiveAppKey;
130
145
  setLeftMenuState({
131
- menuItems: mappingChildrenMenu,
132
- appMenuChildren: ((_a = mappingChildrenMenu === null || mappingChildrenMenu === void 0 ? void 0 : mappingChildrenMenu.find(item => item.menu_item_code === appCode)) === null || _a === void 0 ? void 0 : _a.children) || [],
146
+ customItems: items,
147
+ customMenuChildren: ((_a = items === null || items === void 0 ? void 0 : items.find(item => item.key === appActiveKey)) === null || _a === void 0 ? void 0 : _a.children) || [],
133
148
  });
134
149
  }
135
- }, [activeAppCode, mappingChildrenMenu, state.hoverItem, setLeftMenuState]);
150
+ else {
151
+ const appCode = state.hoverItem ? state.hoverItem : activeAppCode;
152
+ if (isArray(mappingChildrenMenu)) {
153
+ setLeftMenuState({
154
+ menuItems: mappingChildrenMenu,
155
+ appMenuChildren: ((_b = mappingChildrenMenu === null || mappingChildrenMenu === void 0 ? void 0 : mappingChildrenMenu.find(item => item.menu_item_code === appCode)) === null || _b === void 0 ? void 0 : _b.children) || [],
156
+ });
157
+ }
158
+ }
159
+ }, [
160
+ isCustomized,
161
+ activeAppCode,
162
+ mappingChildrenMenu,
163
+ state.hoverItem,
164
+ setLeftMenuState,
165
+ items,
166
+ customActiveAppKey,
167
+ ]);
136
168
  /**
137
169
  * Active App Item having code matching url
138
170
  */
139
171
  useEffect(() => {
140
- const url = `${pathname}${hash}`;
141
- const matchAppCode = findActiveAppCodeByUrl({ url, menuItems: mappingChildrenMenu, auth });
142
- if (matchAppCode && matchAppCode !== activeAppCode) {
143
- setLeftMenuState({ activeAppCode: matchAppCode });
172
+ if (!isCustomized) {
173
+ const url = `${pathname}${hash}`;
174
+ const matchAppCode = findActiveAppCodeByUrl({ url, menuItems: mappingChildrenMenu, auth });
175
+ if (matchAppCode && matchAppCode !== activeAppCode) {
176
+ setLeftMenuState({ activeAppCode: matchAppCode });
177
+ }
144
178
  }
145
179
  }, [
180
+ isCustomized,
146
181
  activeAppCode,
147
182
  appConfig === null || appConfig === void 0 ? void 0 : appConfig.auth,
148
183
  auth,
@@ -157,34 +192,47 @@ export const useLeftMenu = (props) => {
157
192
  */
158
193
  useEffect(() => {
159
194
  var _a;
160
- const url = `${pathname}${hash}`;
161
- const matchedCode = (_a = findLastMatchedItemByUrl({
162
- url,
163
- menuItems: mappingChildrenMenu,
164
- auth,
165
- })) === null || _a === void 0 ? void 0 : _a.menu_item_code;
166
- const activeItemPath = matchedCode
167
- ? findPathOfActiveItem({
168
- activeMenuItem: matchedCode,
195
+ if (!isCustomized) {
196
+ const url = `${pathname}${hash}`;
197
+ const matchedCode = (_a = findLastMatchedItemByUrl({
198
+ url,
169
199
  menuItems: mappingChildrenMenu,
170
- })
171
- : [];
172
- onActiveMenuCodeChange === null || onActiveMenuCodeChange === void 0 ? void 0 : onActiveMenuCodeChange(activeItemPath, flattenMenuPermission);
173
- }, [auth, flattenMenuPermission, hash, mappingChildrenMenu, onActiveMenuCodeChange, pathname]);
200
+ auth,
201
+ })) === null || _a === void 0 ? void 0 : _a.menu_item_code;
202
+ const activeItemPath = matchedCode
203
+ ? findPathOfActiveItem({
204
+ activeMenuItem: matchedCode,
205
+ menuItems: mappingChildrenMenu,
206
+ })
207
+ : [];
208
+ onActiveMenuCodeChange === null || onActiveMenuCodeChange === void 0 ? void 0 : onActiveMenuCodeChange(activeItemPath, flattenMenuPermission);
209
+ }
210
+ }, [
211
+ isCustomized,
212
+ auth,
213
+ flattenMenuPermission,
214
+ hash,
215
+ mappingChildrenMenu,
216
+ onActiveMenuCodeChange,
217
+ pathname,
218
+ ]);
174
219
  /* Callbacks */
175
220
  const onToggleChildMenu = useCallback(() => {
176
221
  setState(prev => (Object.assign(Object.assign({}, prev), { isExpandMenu: !prev.isExpandMenu })));
177
222
  }, []);
178
223
  const onMouseEnter = useCallback((key) => {
179
- if (!state.isExpandMenu) {
224
+ if (!state.isExpandMenu || !isExpandable) {
180
225
  setState(prev => (Object.assign(Object.assign({}, prev), { hoverItem: key })));
181
226
  }
182
- }, [state.isExpandMenu]);
227
+ }, [isExpandable, state.isExpandMenu]);
183
228
  const onMouseLeave = useCallback(() => {
184
- if (!state.isExpandMenu) {
229
+ if (!state.isExpandMenu || !isExpandable) {
185
230
  setState(prev => (Object.assign(Object.assign({}, prev), { hoverItem: '' })));
186
231
  }
187
- }, [state.isExpandMenu]);
232
+ }, [isExpandable, state.isExpandMenu]);
233
+ const onHoverMenuBar = () => {
234
+ onMouseEnter(isCustomized ? customActiveAppKey : activeAppCode);
235
+ };
188
236
  return {
189
237
  /* State */
190
238
  state,
@@ -192,9 +240,12 @@ export const useLeftMenu = (props) => {
192
240
  activeAppCode,
193
241
  /* Data Queries */
194
242
  permissionMenu,
243
+ /** Variables */
244
+ customActiveAppKey,
195
245
  /* Callbacks */
196
246
  onToggleChildMenu,
197
247
  onMouseEnter,
198
248
  onMouseLeave,
249
+ onHoverMenuBar,
199
250
  };
200
251
  };
@@ -37,13 +37,11 @@ export const useNavigatePath = () => {
37
37
  const customPath = ((_a = path === null || path === void 0 ? void 0 : path.split('#')) === null || _a === void 0 ? void 0 : _a[1]) || '';
38
38
  return getGeneratePath(customPath, auth);
39
39
  }
40
- return `${getGeneratePath(path !== null && path !== void 0 ? path : '', auth)}`;
41
- // return `${getGeneratePath(path ?? '', auth)}${searchParams}`;
40
+ return getGeneratePath(`${path !== null && path !== void 0 ? path : ''}${searchParams}`, auth);
42
41
  }, [auth, searchParams]);
43
42
  const navigatePath = useCallback((domain, path) => {
44
43
  const newDomain = isNil(domain) || env === ENV.DEV ? '' : domain;
45
- window.location.assign(`${getGeneratePath(`${newDomain}${path}`, auth)}`);
46
- // window.location.assign(`${getGeneratePath(`${newDomain}${path}`, auth)}${searchParams}`);
44
+ window.location.assign(getGeneratePath(`${newDomain}${path}${searchParams}`, auth));
47
45
  }, [auth, env, searchParams]);
48
46
  return { isPushDifferentDomain, getPath, navigatePath };
49
47
  };
@@ -10,9 +10,13 @@ export interface LeftMenuProps {
10
10
  isGrouped?: boolean;
11
11
  style?: React.CSSProperties;
12
12
  className?: string;
13
- isExpandable?: boolean;
14
- items?: TMenuItem[];
15
- isCustomized?: boolean;
13
+ customization?: {
14
+ isCustomized?: boolean;
15
+ isExpandable?: boolean;
16
+ items?: TMenuItem[];
17
+ activeKey?: string;
18
+ onMenuItemClick?: (key: string, keyPath: string[]) => void;
19
+ };
16
20
  onActiveMenuCodeChange?: (activeItemPath: TFeatureMenu[], flattenPermissionList?: FeatureMenuPermission[]) => void;
17
21
  }
18
22
  export declare const LeftMenu: React.FC<LeftMenuProps>;
@@ -1,5 +1,5 @@
1
1
  // Libraries
2
- import React, { memo, useMemo } from 'react';
2
+ import React, { memo, useCallback, useMemo } from 'react';
3
3
  import Icon from '@antscorp/icons';
4
4
  import { Typography } from 'antd';
5
5
  import classNames from 'classnames';
@@ -8,17 +8,16 @@ import { Link } from 'react-router-dom';
8
8
  // Assets
9
9
  import SubLogoAntsomi from '@antscorp/antsomi-ui/es/assets/images/logo/sub-logo-antsomi.png';
10
10
  // Styled
11
- import { ChildMenuWrapper, ExpandWrapper, FeatureMenu, FeatureMenuItem, LeftMenuNav, LeftMenuNavWrapper, PopoverWrapper, NavLogoWrapper, FeatureMenuWrapper, } from './styled';
11
+ import { ChildMenuWrapper, ExpandWrapper, FeatureMenu, FeatureMenuItem, LeftMenuNav, LeftMenuNavWrapper, PopoverWrapper, NavLogoWrapper, FeatureMenuWrapper, IconWrapper, } from './styled';
12
12
  // Constants
13
- import { APP_KEYS, ICON_SIZE } from './constants';
13
+ import { APP_KEYS } from './constants';
14
14
  // Components
15
- import { ProfilesMenu, ContentMenu, DataMenu, MarketingMenu, HomeMenu, InsightsMenu, SettingsMenu, TemplatesMenu, } from './components';
15
+ import { ProfilesMenu, ContentMenu, DataMenu, MarketingMenu, HomeMenu, InsightsMenu, SettingsMenu, TemplatesMenu, CustomMenu, } from './components';
16
16
  // Hooks
17
- import { useLeftMenu } from './hooks';
18
- import { useNavigatePath } from './hooks/useNavigatePath';
17
+ import { useLeftMenu, useNavigatePath } from './hooks';
19
18
  export const LeftMenu = memo(props => {
20
- var _a;
21
- const { style, className, isExpandable = true, isCustomized = false } = props;
19
+ const { style, className, customization } = props;
20
+ const { isExpandable = true, isCustomized = false, items = [], onMenuItemClick, } = customization || {};
22
21
  // Hooks
23
22
  const {
24
23
  /* State */
@@ -27,12 +26,16 @@ export const LeftMenu = memo(props => {
27
26
  activeAppCode,
28
27
  /* Data Queries */
29
28
  permissionMenu,
29
+ /** Variables */
30
+ customActiveAppKey,
30
31
  /* Callbacks */
31
- onToggleChildMenu, onMouseEnter, onMouseLeave, } = useLeftMenu(props);
32
+ onToggleChildMenu, onMouseEnter, onMouseLeave, onHoverMenuBar, } = useLeftMenu(props);
32
33
  const { isPushDifferentDomain, getPath, navigatePath } = useNavigatePath();
33
34
  // Memo
34
35
  const childMenu = useMemo(() => {
35
36
  const selectedFeatureKey = state.hoverItem ? state.hoverItem : activeAppCode;
37
+ if (isCustomized)
38
+ return React.createElement(CustomMenu, { onMenuItemClick: onMenuItemClick });
36
39
  switch (selectedFeatureKey) {
37
40
  case APP_KEYS.HOME: {
38
41
  return React.createElement(HomeMenu, null);
@@ -61,38 +64,54 @@ export const LeftMenu = memo(props => {
61
64
  default:
62
65
  return null;
63
66
  }
64
- }, [activeAppCode, state.hoverItem]);
67
+ }, [activeAppCode, isCustomized, onMenuItemClick, state.hoverItem]);
65
68
  // Render components
66
- const renderFeatureMenuItems = (item) => {
69
+ const renderFeatureMenuItems = useCallback((item) => {
67
70
  const { menu_item_name, menu_item, icon_name, menu_item_code, menu_item_path, menu_item_domain, } = item;
68
71
  const isActive = activeAppCode === menu_item_code;
69
72
  const isHover = state.hoverItem === menu_item_code;
70
73
  const labelComponent = () => (React.createElement("li", { role: "menuitem", className: classNames({ isActive, isHover }) },
71
- React.createElement("div", { className: "icon-wrapper" }, isNil(icon_name) ? null : React.createElement(Icon, { type: icon_name, style: { fontSize: ICON_SIZE } })),
74
+ React.createElement(IconWrapper, null, isNil(icon_name) ? null : React.createElement(Icon, { type: icon_name })),
72
75
  React.createElement(Typography.Text, null, menu_item_name),
73
76
  React.createElement("div", { className: "popup-triangle" })));
74
77
  return (React.createElement(FeatureMenuItem, { key: menu_item, onMouseEnter: () => onMouseEnter(menu_item_code) }, isString(menu_item_path) ? (isPushDifferentDomain(menu_item_domain, menu_item_path) ? (React.createElement("div", { onClick: () => navigatePath(menu_item_domain, menu_item_path) }, labelComponent())) : (React.createElement(Link, { to: getPath(menu_item_path), className: "menu-link" }, labelComponent()))) : (labelComponent())));
78
+ }, [activeAppCode, getPath, isPushDifferentDomain, navigatePath, onMouseEnter, state.hoverItem]);
79
+ const renderCustomizedMenuItems = useCallback((items) => items === null || items === void 0 ? void 0 : items.map(item => {
80
+ const { key, icon, label } = item;
81
+ const isActive = key === customActiveAppKey;
82
+ const isHover = key === state.hoverItem;
83
+ return (React.createElement(FeatureMenuItem, { key: key, onClick: () => onMenuItemClick === null || onMenuItemClick === void 0 ? void 0 : onMenuItemClick(key, [key]), onMouseEnter: () => onMouseEnter(key) },
84
+ React.createElement("li", { role: "menuitem", className: classNames({ isActive, isHover }) },
85
+ React.createElement(IconWrapper, null, isNil(icon) ? null : React.createElement(Icon, { type: icon })),
86
+ React.createElement(Typography.Text, null, label),
87
+ isExpandable && React.createElement("div", { className: "popup-triangle" }))));
88
+ }), [customActiveAppKey, state.hoverItem, isExpandable, onMenuItemClick, onMouseEnter]);
89
+ const renderAppMenuItems = useCallback(() => {
90
+ var _a;
91
+ return isCustomized
92
+ ? renderCustomizedMenuItems(items)
93
+ : (_a = permissionMenu === null || permissionMenu === void 0 ? void 0 : permissionMenu.filter(item => item.menu_item_code !== APP_KEYS.SETTINGS)) === null || _a === void 0 ? void 0 : _a.map(item => renderFeatureMenuItems(item));
94
+ }, [isCustomized, items, permissionMenu, renderCustomizedMenuItems, renderFeatureMenuItems]);
95
+ const renderSettings = () => {
96
+ if (!isCustomized) {
97
+ const settingApp = permissionMenu === null || permissionMenu === void 0 ? void 0 : permissionMenu.find(item => item.menu_item_code === APP_KEYS.SETTINGS);
98
+ if (settingApp)
99
+ return renderFeatureMenuItems(settingApp);
100
+ }
101
+ return null;
75
102
  };
76
- // const renderCustomizedMenuItems = (items: TMenuItem) => {
77
- // return items?.map(item => {
78
- // return <FeatureMenuItem key={menu_item} onMouseEnter={() => onMouseEnter(menu_item_code)} />;
79
- // });
80
- // };
81
103
  return (React.createElement(LeftMenuNavWrapper, { style: style, className: className },
82
104
  React.createElement(LeftMenuNav, null,
83
105
  React.createElement(FeatureMenuWrapper, { isExpandMenu: state.isExpandMenu, vertical: true },
84
- React.createElement(NavLogoWrapper, { align: "center", justify: "center", onMouseEnter: () => onMouseEnter(activeAppCode) },
106
+ React.createElement(NavLogoWrapper, { align: "center", justify: "center", onMouseEnter: onHoverMenuBar },
85
107
  React.createElement("div", { className: "image-wrapper" },
86
108
  React.createElement("img", { src: SubLogoAntsomi, alt: "Antsomi sub logo" }))),
87
109
  React.createElement(FeatureMenu, { role: "menu", className: "antsomi-scroll-box", onMouseLeave: onMouseLeave },
88
- React.createElement("div", { className: "menu-content scroll-content" }, (_a = permissionMenu === null || permissionMenu === void 0 ? void 0 : permissionMenu.filter(item => item.menu_item_code !== APP_KEYS.SETTINGS)) === null || _a === void 0 ? void 0 : _a.map(item => renderFeatureMenuItems(item))),
89
- React.createElement("div", { className: "nav-blank", onMouseEnter: () => onMouseEnter(activeAppCode) }),
110
+ React.createElement("div", { className: "menu-content scroll-content" }, renderAppMenuItems()),
111
+ React.createElement("div", { className: "nav-blank", onMouseEnter: onHoverMenuBar }),
90
112
  React.createElement(PopoverWrapper, { className: "antsomi-scroll-box antsomi-child-menu-popover", onMouseLeave: onMouseLeave },
91
113
  React.createElement("div", { className: "scroll-content" }, childMenu))),
92
- React.createElement("div", { style: { flexShrink: 0 } }, (() => {
93
- const settingFeature = permissionMenu === null || permissionMenu === void 0 ? void 0 : permissionMenu.find(item => item.menu_item_code === APP_KEYS.SETTINGS);
94
- return settingFeature ? renderFeatureMenuItems(settingFeature) : null;
95
- })())),
114
+ React.createElement("div", { style: { flexShrink: 0 } }, renderSettings())),
96
115
  isExpandable && (React.createElement(ExpandWrapper, { onMouseEnter: onMouseLeave },
97
116
  React.createElement(Icon, { type: "icon-ants-expand-more", style: {
98
117
  cursor: 'pointer',
@@ -1,10 +1,16 @@
1
1
  import { TFeatureMenu } from '@antscorp/antsomi-ui/es/models/LeftMenu';
2
2
  import { AppConfigProviderProps } from '@antscorp/antsomi-ui/es/providers';
3
+ import { TMenuItem } from '../types';
3
4
  export interface LeftMenuState {
4
5
  activeAppCode: string;
6
+ customActiveAppKey: string;
7
+ customActiveCurrentKey: string;
5
8
  menuItems: TFeatureMenu[];
9
+ customItems: TMenuItem[];
10
+ customMenuChildren: TMenuItem[];
6
11
  appMenuChildren: TFeatureMenu[];
7
12
  appConfig?: AppConfigProviderProps;
13
+ isCustomized?: boolean;
8
14
  }
9
15
  interface LeftMenuStore {
10
16
  state: LeftMenuState;
@@ -1,6 +1,15 @@
1
1
  // Libraries
2
2
  import { create } from 'zustand';
3
- const initialState = { activeAppCode: '', menuItems: [], appMenuChildren: [] };
3
+ const initialState = {
4
+ activeAppCode: '',
5
+ customActiveAppKey: '',
6
+ customActiveCurrentKey: '',
7
+ menuItems: [],
8
+ appMenuChildren: [],
9
+ customItems: [],
10
+ customMenuChildren: [],
11
+ isCustomized: false,
12
+ };
4
13
  export const useLeftMenuStore = create(set => ({
5
14
  state: initialState,
6
15
  setState: newState => set(store => ({ state: Object.assign(Object.assign({}, store.state), newState) })),
@@ -129,14 +129,6 @@ export const FeatureMenuItem = styled.div `
129
129
  display: none !important;
130
130
  }
131
131
 
132
- .icon-wrapper {
133
- height: 30px;
134
- width: 30px;
135
- display: flex;
136
- align-items: center;
137
- justify-content: center;
138
- }
139
-
140
132
  span {
141
133
  font-size: 11px !important;
142
134
  font-weight: 500;
@@ -198,6 +190,6 @@ export const IconWrapper = styled.div `
198
190
  align-items: center;
199
191
 
200
192
  i {
201
- font-size: ${ICON_SIZE};
193
+ font-size: ${ICON_SIZE}px;
202
194
  }
203
195
  `;
@@ -39,6 +39,25 @@ export declare const getComparePath: (path: string) => string;
39
39
  * @returns {TFeatureMenu} The initial feature menu item object.
40
40
  */
41
41
  export declare const getInitialFeatureMenuItem: (featureMenuItem: Partial<TFeatureMenu>) => TFeatureMenu;
42
+ export declare const recursiveGetInitialFeatureMenuItem: (featureMenuItem: Partial<TFeatureMenu>) => {
43
+ menu_item: string | number;
44
+ network_id: number;
45
+ menu_item_name: string;
46
+ level_position: string | number;
47
+ icon_name: string | null;
48
+ menu_item_path: string | null;
49
+ menu_item_code: string;
50
+ permission_code: string | null;
51
+ menu_item_type: string | number;
52
+ menu_item_parent: string | number;
53
+ render_option: string | null;
54
+ menu_item_domain: string | null;
55
+ total_row: number;
56
+ ctime: string | null;
57
+ utime: string | null;
58
+ logo_url?: string | undefined;
59
+ children?: TFeatureMenu[] | undefined;
60
+ };
42
61
  /** Map children to each App Item */
43
62
  export declare const getMappingAppChildren: (args: {
44
63
  menuList: TFeatureMenu[];
@@ -68,3 +87,15 @@ export declare const findPathOfActiveItem: (args: {
68
87
  activeMenuItem: string;
69
88
  menuItems: TFeatureMenu[];
70
89
  }) => TFeatureMenu[];
90
+ /**
91
+ * Finds the app code of the menu item or its parent based on the provided active key.
92
+ *
93
+ * @param {Object} args - The arguments object.
94
+ * @param {string} args.activeCurrentKey - The key of the currently active menu item.
95
+ * @param {TMenuItem[]} args.menuItems - The array of menu items to search through.
96
+ * @returns {string | undefined} - The app code of the matching menu item or its parent, or undefined if not found.
97
+ */
98
+ export declare const findActiveAppCodeByChild: (args: {
99
+ activeCurrentKey: string;
100
+ menuItems: TMenuItem[];
101
+ }) => string | undefined;
@@ -105,6 +105,7 @@ export const getComparePath = (path) => {
105
105
  * @returns {TFeatureMenu} The initial feature menu item object.
106
106
  */
107
107
  export const getInitialFeatureMenuItem = (featureMenuItem) => (Object.assign({ menu_item: '', network_id: -1, menu_item_name: '', level_position: 0, icon_name: null, menu_item_path: null, menu_item_code: '', permission_code: null, menu_item_type: 3, menu_item_parent: 0, render_option: null, menu_item_domain: null, total_row: 0, ctime: null, utime: null }, featureMenuItem));
108
+ export const recursiveGetInitialFeatureMenuItem = (featureMenuItem) => (Object.assign({ menu_item: '', network_id: -1, menu_item_name: '', level_position: 0, icon_name: null, menu_item_path: null, menu_item_code: '', permission_code: null, menu_item_type: 3, menu_item_parent: 0, render_option: null, menu_item_domain: null, total_row: 0, ctime: null, utime: null }, featureMenuItem));
108
109
  /** Map children to each App Item */
109
110
  export const getMappingAppChildren = (args) => {
110
111
  const { menuList, dashboardData, auth, destinationChannelEntries } = args;
@@ -270,3 +271,52 @@ export const findPathOfActiveItem = (args) => {
270
271
  return [];
271
272
  }
272
273
  };
274
+ /**
275
+ * Finds the app code of the menu item or its parent based on the provided active key.
276
+ *
277
+ * @param {Object} args - The arguments object.
278
+ * @param {string} args.activeCurrentKey - The key of the currently active menu item.
279
+ * @param {TMenuItem[]} args.menuItems - The array of menu items to search through.
280
+ * @returns {string | undefined} - The app code of the matching menu item or its parent, or undefined if not found.
281
+ */
282
+ export const findActiveAppCodeByChild = (args) => {
283
+ try {
284
+ const { activeCurrentKey, menuItems } = args;
285
+ let matchAppCode;
286
+ const recursiveCheckHasActiveChild = (items) => {
287
+ let matchAppCode = false;
288
+ for (const item of items) {
289
+ if (item.key === activeCurrentKey) {
290
+ matchAppCode = true;
291
+ break;
292
+ }
293
+ if ((item === null || item === void 0 ? void 0 : item.children) && item.children.length) {
294
+ matchAppCode = recursiveCheckHasActiveChild(item.children);
295
+ if (matchAppCode) {
296
+ break;
297
+ }
298
+ }
299
+ }
300
+ return matchAppCode;
301
+ };
302
+ for (const menuItem of menuItems) {
303
+ if (menuItem.key === activeCurrentKey) {
304
+ matchAppCode = activeCurrentKey;
305
+ break;
306
+ }
307
+ const hasActiveChild = recursiveCheckHasActiveChild((menuItem === null || menuItem === void 0 ? void 0 : menuItem.children) || []);
308
+ if (hasActiveChild) {
309
+ matchAppCode = menuItem.key;
310
+ break;
311
+ }
312
+ }
313
+ return matchAppCode;
314
+ }
315
+ catch (error) {
316
+ handleError(error, {
317
+ path: PATH,
318
+ name: findActiveAppCodeByChild.name,
319
+ args,
320
+ });
321
+ }
322
+ };
@@ -1,14 +1,17 @@
1
1
  import React from 'react';
2
2
  import { PayloadInfo } from '../../..';
3
3
  import { envType } from '@antscorp/antsomi-ui/es/types/config';
4
- export interface AppConfigProviderProps {
4
+ export interface AppConfigProps {
5
5
  env?: envType;
6
6
  auth?: Omit<PayloadInfo, 'url'>;
7
7
  appCode?: string;
8
8
  languageCode?: string;
9
9
  urlLogout?: string;
10
+ setAppConfig: React.Dispatch<React.SetStateAction<AppConfigProviderProps>>;
10
11
  }
11
- declare const AppConfigContext: import("use-context-selector").Context<AppConfigProviderProps>;
12
+ export interface AppConfigProviderProps extends Omit<AppConfigProps, 'setAppConfig'> {
13
+ }
14
+ declare const AppConfigContext: import("use-context-selector").Context<AppConfigProps>;
12
15
  declare const AppConfigProvider: React.FC<React.PropsWithChildren<{
13
16
  value?: AppConfigProviderProps;
14
17
  }>>;
@@ -1,10 +1,13 @@
1
1
  // Libraries
2
- import React from 'react';
2
+ import React, { useMemo, useState } from 'react';
3
3
  import { createContext } from 'use-context-selector';
4
- const initialContext = {};
4
+ import { merge } from 'lodash';
5
+ const initialContext = { setAppConfig: () => { } };
5
6
  const AppConfigContext = createContext(initialContext);
6
7
  const AppConfigProvider = props => {
7
8
  const { value = initialContext, children } = props;
8
- return React.createElement(AppConfigContext.Provider, { value: value }, children);
9
+ const [appConfig, setAppConfig] = useState(value);
10
+ const mergedValue = useMemo(() => (Object.assign(Object.assign({}, merge(value, appConfig)), { setAppConfig })), [appConfig, value]);
11
+ return React.createElement(AppConfigContext.Provider, { value: mergedValue }, children);
9
12
  };
10
13
  export { AppConfigContext, AppConfigProvider };
@@ -1,3 +1,3 @@
1
1
  export declare const useAppConfigContext: () => {
2
- appConfig: import("./contexts").AppConfigProviderProps;
2
+ appConfig: import("./contexts").AppConfigProps;
3
3
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antscorp/antsomi-ui",
3
- "version": "1.3.5-beta.235",
3
+ "version": "1.3.5-beta.237",
4
4
  "description": "An enterprise-class UI design language and React UI library.",
5
5
  "sideEffects": [
6
6
  "dist/*",