UNPKG

@elastic-suite/gally-admin-components

Version:
1 lines 1.23 MB
{"version":3,"sources":["../src/components/atoms/Alert/Alert.tsx","../src/components/atoms/IonIcon/IonIcon.tsx","../src/utils/testIds.ts","../src/components/atoms/boost/Boost.tsx","../src/components/atoms/breadcrumb/Breadcrumb.tsx","../src/components/atoms/breadcrumb/Breadcrumbs.tsx","../src/components/atoms/Chip/Chip.tsx","../src/components/atoms/closeComponent/CloseComponent.tsx","../src/components/atoms/form/CheckboxWithoutError.tsx","../src/components/atoms/form/Checkbox.tsx","../src/hooks/useApi.ts","../src/hooks/useLog.ts","../src/hooks/useResource.ts","../src/store/data.ts","../src/store/hooks.ts","../src/store/i18n.ts","../src/store/menu.ts","../src/store/store.ts","../src/store/user.ts","../src/hooks/useApiFilters.ts","../src/hooks/useApiTable.ts","../src/hooks/useApiForm.ts","../src/hooks/useCatalogs.ts","../src/hooks/useDataLoader.ts","../src/hooks/useFilters.ts","../src/hooks/useFormError.ts","../src/hooks/useGraphql.ts","../src/hooks/useIsHorizontalOverflow.ts","../src/hooks/useOptions.ts","../src/hooks/useSingletonLoader.ts","../src/hooks/useRuleEngineGraphqlFilters.ts","../src/hooks/useRuleOperators.ts","../src/hooks/useTabs.ts","../src/contexts/breadcrumb.ts","../src/contexts/catalog.ts","../src/contexts/i18n.ts","../src/contexts/metadata.ts","../src/contexts/options.ts","../src/contexts/ruleOptions.ts","../src/contexts/tabVisibility.ts","../src/hooks/useUser.ts","../src/hooks/useFormValdation.ts","../src/components/atoms/form/DatePickerWithoutError.tsx","../src/components/atoms/form/InputTextWithoutError.tsx","../src/services/category.ts","../src/services/customTable.ts","../src/services/form.ts","../src/services/row.ts","../src/components/atoms/form/InfoTooltip.tsx","../src/components/atoms/modals/Tooltip.tsx","../src/components/atoms/form/InputText.styled.ts","../src/components/atoms/form/DatePicker.tsx","../src/components/atoms/form/DoubleDatePickerWithoutError.tsx","../src/components/atoms/form/FormControl.tsx","../src/components/atoms/form/DoubleDatePicker.tsx","../src/components/atoms/form/DropDownWithoutError.tsx","../src/components/atoms/form/DropDown.styled.tsx","../src/components/atoms/form/DropDown.tsx","../src/components/atoms/form/InputText.tsx","../src/components/atoms/form/RadioGroupWithoutError.tsx","../src/components/atoms/form/RadioGroup.tsx","../src/components/atoms/form/Range.tsx","../src/components/atoms/form/RangeWithoutError.tsx","../src/components/atoms/form/Range.styled.tsx","../src/components/atoms/form/Slider.tsx","../src/components/atoms/form/Slider.styled.tsx","../src/components/atoms/form/Switch.tsx","../src/components/atoms/form/Switch.styled.tsx","../src/components/atoms/form/TextareaWithoutError.tsx","../src/components/atoms/form/Textarea.tsx","../src/components/atoms/form/TreeSelectorWithoutError.tsx","../src/components/atoms/tree/Tree.tsx","../src/components/atoms/tree/Tree.styled.ts","../src/components/atoms/form/TreeSelector.styled.tsx","../src/components/atoms/form/TreeSelector.tsx","../src/components/atoms/format/FormatText.tsx","../src/components/atoms/help/Help.tsx","../src/components/atoms/help/HelpOver.tsx","../src/components/atoms/menu/MenuItem.tsx","../src/components/atoms/menu/MenuItemIcon.tsx","../src/components/atoms/modals/PopIn.tsx","../src/hooks/usePopin.ts","../src/components/atoms/modals/CustomDialog.tsx","../src/components/atoms/modals/PopIn.styled.ts","../src/components/atoms/buttons/Button.tsx","../src/components/atoms/buttons/Button.styled.tsx","../src/components/atoms/noAttributes/NoAttributes.tsx","../src/components/stateful-pages/Grid/Grid.tsx","../src/components/stateful-pages/ResourceTable/ResourceTable.tsx","../src/components/stateful/FieldGuesser/FieldGuesser.tsx","../src/components/stateful/FieldGuesser/EditableFieldGuesser.tsx","../src/components/atoms/form/InputNumber.tsx","../src/components/stateful/FieldGuesser/ReadableFieldGuesser.tsx","../src/components/atoms/score/Score.tsx","../src/components/atoms/stock/Stock.tsx","../src/components/atoms/price/Price.tsx","../src/components/stateful/FieldGuesser/ReadableDropDownGuesser.tsx","../src/components/molecules/format/FormatRowArray.tsx","../src/components/atoms/format/FormatArray.tsx","../src/components/atoms/form/PreviewGridBoostConfiguration/PreviewGridBoostConfiguration.tsx","../src/components/molecules/FiltersPreviewBoostingTabs/FiltersPreviewBoostingTabs.tsx","../src/components/molecules/layout/tabs/CustomTabs.tsx","../src/components/molecules/layout/tabs/TabPanel.tsx","../src/components/stateful-providers/TabVisibilityProvider/TabVisibilityProvider.tsx","../src/components/molecules/layout/tabs/a11yProps.tsx","../src/components/molecules/FiltersPreviewBoostingTabs/FiltersPreviewBoostingTab.tsx","../src/components/atoms/form/Form.tsx","../src/components/atoms/form/SearchBar.tsx","../src/components/organisms/PreviewBoostingTable/PreviewBoostingTableManager.tsx","../src/components/molecules/CustomTable/Pagination/Pagination.tsx","../src/components/organisms/PreviewBoostingTable/PreviewBoostingTable.tsx","../src/components/organisms/CustomTable/CustomTable.tsx","../src/components/organisms/CustomTable/CustomTable.styled.tsx","../src/components/organisms/CustomTable/CustomTableBody/DraggableBody.tsx","../src/components/organisms/CustomTable/CustomTableRow/DraggableRow.tsx","../src/components/organisms/CustomTable/CustomTableBody/NonDraggableBody.tsx","../src/components/organisms/CustomTable/CustomTableRow/NonDraggableRow.tsx","../src/components/organisms/CustomTable/CustomTableHeader/CustomTableHeader.tsx","../src/components/atoms/image/Image.tsx","../src/components/atoms/positionEffect/PositionEffect.tsx","../src/components/molecules/Logs/Logs.tsx","../src/components/molecules/Logs/LogsList.tsx","../src/components/molecules/FileDownloader/FileDownloader.tsx","../src/components/atoms/Status/Status.tsx","../src/components/stateful/FieldGuesser/EditableDropDownGuesser.tsx","../src/hooks/useDropdownTextWidth.ts","../src/components/stateful/FieldGuesser/MultipleInput.tsx","../src/components/stateful/RequestTypeManager/RequestTypeManager.tsx","../src/components/atoms/form/RequestType.tsx","../src/components/atoms/form/RequestTypeItem.styled.tsx","../src/components/atoms/form/RequestTypeItem.tsx","../src/components/atoms/form/TextFieldTagsMultiple.tsx","../src/components/atoms/form/TextFieldTags.tsx","../src/components/atoms/form/TextFieldTagsWithoutError.tsx","../src/components/stateful/RulesManager/RulesManager.tsx","../src/components/atoms/rules/CombinationRules.tsx","../src/components/atoms/rules/Rule.tsx","../src/components/atoms/rules/Rule.styled.tsx","../src/components/atoms/rules/RuleLink.tsx","../src/components/stateful-providers/RuleOptionsProvider/RuleOptionsProvider.tsx","../src/components/atoms/form/Synonym.tsx","../src/components/atoms/form/Expansion.tsx","../src/components/molecules/ProportionalToAttributes/ProportionalToAttributesManager.tsx","../src/components/molecules/ProportionalToAttributes/ProportionalToAttributes.tsx","../src/components/molecules/ProportionalToAttributes/ProportionalToAttributes.styled.tsx","../src/components/stateful/FiltersGuesser/FiltersGuesser.tsx","../src/components/molecules/Filters/Filters.tsx","../src/components/molecules/Filters/Filters.styled.tsx","../src/components/stateful/TableGuesser/TableGuesser.tsx","../src/components/molecules/CustomTable/StickyBar/StickyBar.tsx","../src/components/organisms/PagerTable/PagerTable.tsx","../src/components/stateful/TableStickyBar/TableStickyBar.tsx","../src/components/molecules/JobButtons/JobButtons.tsx","../src/components/atoms/PageTitle/PageTitle.tsx","../src/components/atoms/Placeholder/Placeholder.tsx","../src/components/atoms/scope/Language.tsx","../src/components/atoms/scope/PopInCatalogs.tsx","../src/components/atoms/scope/TitleScope.tsx","../src/components/atoms/scope/NbActiveLocales.tsx","../src/components/atoms/subTabs/SubTabPanel.tsx","../src/components/atoms/subTabs/SubTabs.tsx","../src/components/atoms/userMenu/UserMenu.tsx","../src/components/atoms/userMenu/UserMenuShow.tsx","../src/components/molecules/CustomTable/MassiveSelection/MassiveSelection.tsx","../src/components/molecules/CustomTable/TopProductsBanner/TopProductsBanner.tsx","../src/components/molecules/layout/appBar/AppBar.tsx","../src/components/molecules/layout/user/User.tsx","../src/components/molecules/layout/Menu/Menu.tsx","../src/components/molecules/layout/scope/ActiveLocales.tsx","../src/components/molecules/layout/scope/Catalogs.tsx","../src/components/molecules/layout/Sidebar/Sidebar.tsx","../src/hooks/useHomePageUrl.ts","../src/components/molecules/layout/TitleBlock/TitleBlock.tsx","../src/components/molecules/layout/twoColsLayout/TwoColsLayout.tsx","../src/components/stateful/CatalogSwitcher/CatalogSwitcher.tsx","../src/components/stateful/CategoryTree/CategoryTree.tsx","../src/components/stateful/Merchandize/Merchandize.tsx","../src/components/stateful/ProductsContainer/ProductsContainer.tsx","../src/components/stateful/ProductsTopAndBottom/ProductsTopAndBottom.tsx","../src/components/stateful/TopAndBottomTable/BottomTable.tsx","../src/components/stateful/TopAndBottomTable/TopTable.tsx","../src/components/stateful/TopProductsTable/TopProductsTable.tsx","../src/components/stateful/Merchandize/SearchBar/SearchBar.tsx","../src/components/stateful/Merchandize/SearchBar/Search.styled.tsx","../src/components/stateful-layout/Layout/Layout.tsx","../src/components/stateful-pages/SettingsAttributes/SettingsAttributes.tsx","../src/components/atoms/metadata/Metadata.tsx","../src/components/stateful-pages/SettingsScope/SettingsScope.tsx","../src/components/stateful-pages/ResourceForm/ResourceForm.tsx","../src/components/organisms/CustomForm/CustomForm.tsx","../src/components/stateful/FieldGuesser/FormFieldGuesser.tsx","../src/components/organisms/CustomForm/CustomForm.styled.tsx","../src/components/atoms/backToLastPage/BackToLastPage.tsx","../src/components/stateful-providers/AppProvider/AppProvider.tsx","../src/components/stateful-providers/BreadcrumbProvider/BreadcrumbProvider.tsx","../src/components/stateful-providers/I18nProvider/I18nProvider.tsx","../src/components/stateful-providers/CatalogProvider/CatalogProvider.tsx","../src/components/stateful-providers/MetadataProvider/MetadataProvider.tsx","../src/components/stateful-providers/DataProvider/DataProvider.tsx","../src/components/stateful-providers/OptionsProvider/OptionsProvider.tsx","../src/constants/i18n.ts","../src/hocs/withAuth.tsx","../src/hocs/withOptions.tsx","../src/pages/admin/analyze/Results.tsx","../src/components/stateful/ProductPreview/MerchandiseBar.tsx","../src/components/stateful/ProductPreview/ProductsPreviewBottom.tsx","../src/components/molecules/Explain/ExplainQueryDetails.tsx","../src/components/molecules/Explain/ExplainDetails.tsx","../src/components/atoms/ReadOnlyTable/ReadOnlyTable.tsx","../src/components/atoms/ReadOnlyTable/ReadOnlyTable.styled.tsx","../src/components/atoms/modals/Tooltip.stories.tsx","../src/components/molecules/Explain/ExplainDetails.styled.tsx","../src/pages/admin/analyze/SearchUsage.tsx","../src/components/molecules/KPIGroup/KPIGroup.tsx","../src/components/atoms/KPI/KPI.tsx","../src/components/atoms/KPI/KPI.styled.ts","../src/hooks/useAnimatedValue.ts","../src/components/molecules/KPIGroup/KPIGroup.styled.ts","../src/pages/admin/ImportExport.tsx","../src/pages/admin/importexport/Index.tsx","../src/components/stateful-pages/ImportExport/Import.tsx","../src/components/organisms/RunnableJobProfileGrid/RunnableJobProfileGrid.tsx","../src/components/atoms/profileRunner/ProfileRunner.tsx","../src/components/molecules/UploadJobFileModal/UploadJobFileModal.tsx","../src/components/molecules/FileUploadDropzone/FileUploadDropzone.tsx","../src/components/stateful-pages/ImportExport/Export.tsx","../src/pages/admin/merchandize/categories.tsx","../src/pages/admin/merchandize/recommender/auto_recommender/Grid.tsx","../src/pages/admin/merchandize/recommender/auto_recommender/Create.tsx","../src/pages/admin/merchandize/recommender/auto_recommender/Edit.tsx","../src/pages/admin/merchandize/recommender/recommender_type/Grid.tsx","../src/pages/admin/merchandize/recommender/recommender_type/Create.tsx","../src/pages/admin/merchandize/recommender/recommender_type/Edit.tsx","../src/pages/admin/merchandize/boost/Grid.tsx","../src/pages/admin/merchandize/boost/Create.tsx","../src/pages/admin/merchandize/boost/Edit.tsx","../src/pages/admin/search/thesaurus/Grid.tsx","../src/pages/admin/search/thesaurus/Create.tsx","../src/pages/admin/search/thesaurus/Edit.tsx","../src/pages/admin/search/configuration/Attributes.tsx","../src/pages/admin/search/Facets.tsx","../src/pages/admin/search/Vector.tsx","../src/pages/admin/settings/Index.tsx","../src/components/stateful-pages/SettingsUsers/Grid.tsx","../src/components/stateful-pages/SettingsConfigurations/SettingsConfigurations.tsx","../src/components/stateful-pages/ConfigurationForm/ConfigurationForm.tsx","../src/services/configuration.ts","../src/pages/admin/Dashboard.tsx","../src/pages/admin/Settings.tsx","../src/pages/admin/settings/user/Create.tsx","../src/pages/admin/settings/user/Edit.tsx","../src/pages/swagger/Docs.tsx","../src/pages/GallyApp.tsx","../src/components/stateful-layout/Layout/BlankLayout.tsx","../src/pages/Login.tsx","../src/pages/ForgotPassword.tsx","../src/pages/ResetPassword.tsx","../src/components/atoms/form/ConfirmPassword.tsx","../src/pages/Welcome.tsx"],"sourcesContent":["import React, { ForwardedRef, ReactNode, forwardRef } from 'react'\nimport { IconButton, Alert as MuiAlert } from '@mui/material'\nimport { styled } from '@mui/system'\nimport { CustomContentProps, SnackbarKey } from 'notistack'\nimport { useTranslation } from 'next-i18next'\n\nimport IonIcon from '../IonIcon/IonIcon'\nimport { TestId, generateTestId } from '../../../utils/testIds'\n\nconst StyledAlert = styled(MuiAlert)(({ severity, theme }) => ({\n padding: `6px ${theme.spacing(2)}`,\n background: theme.palette.colors.neutral[200],\n border: `1px solid ${theme.palette.colors.neutral[300]}`,\n color: theme.palette.colors.neutral[600],\n borderRadius: 8,\n alignItems: 'center',\n ...(severity === 'success' && {\n background: theme.palette.success.light,\n color: theme.palette.success.main,\n }),\n ...(severity === 'warning' && {\n background: theme.palette.warning.light,\n color: theme.palette.warning.main,\n }),\n ...(severity === 'error' && {\n background: theme.palette.error.light,\n color: theme.palette.error.main,\n }),\n '& .MuiAlert-icon': {\n color: 'inherit',\n marginRight: theme.spacing(1),\n },\n '& .MuiAlert-message': {\n padding: 0,\n fontWeight: 400,\n fontSize: '12px',\n lineHeight: '18px',\n whiteSpace: 'pre-line',\n },\n '& .MuiAlert-action': {\n paddingLeft: '10px',\n paddingTop: 0,\n paddingBottom: 0,\n paddingRight: 0,\n },\n}))\n\ninterface IProps extends Partial<CustomContentProps> {\n onShut?: (id: SnackbarKey) => void\n mb?: number\n children?: ReactNode\n}\n\nfunction Alert(props: IProps, ref: ForwardedRef<HTMLDivElement>): JSX.Element {\n const { id, message, onShut, variant = 'info', mb = 2, children } = props\n const { t } = useTranslation('alert')\n\n function handleClick(): void {\n onShut?.(id)\n }\n\n const content = children || t(message as string)\n\n return (\n <StyledAlert\n action={\n <IconButton\n aria-label=\"close\"\n color=\"inherit\"\n size=\"small\"\n onClick={handleClick}\n >\n <IonIcon name=\"close\" style={{ fontSize: 18, padding: '0px' }} />\n </IconButton>\n }\n ref={ref}\n severity={variant as 'success' | 'info' | 'warning' | 'error'}\n sx={{ mb }}\n data-testid={generateTestId(TestId.ALERT, variant)}\n >\n {content}\n </StyledAlert>\n )\n}\n\nexport default forwardRef(Alert)\n","/* eslint-disable @typescript-eslint/no-namespace */\nimport React, { CSSProperties, MouseEvent } from 'react'\nimport { JSX as IonIconJSX } from 'ionicons'\nimport { JSXBase } from 'ionicons/dist/types/stencil-public-runtime'\nimport { TestId, generateTestId } from '../../../utils/testIds'\n\nif (typeof window != 'undefined') {\n // @ts-expect-error: ionicons don't have declaration type files.\n import('ionicons/dist/cjs/ionicons.cjs')\n}\n\nexport type IIonIconProps = Omit<\n IonIconJSX.IonIcon & JSXBase.HTMLAttributes<HTMLIonIconElement>,\n 'style' | 'onClick'\n> & {\n style?: CSSProperties\n onClick?: (event: MouseEvent<IIonIconProps>) => void\n}\n\ndeclare global {\n namespace JSX {\n // eslint-disable-next-line @typescript-eslint/naming-convention\n interface IntrinsicElements {\n 'ion-icon': IIonIconProps\n }\n }\n}\n\ninterface IProps extends IIonIconProps {\n name: string\n tooltip?: boolean\n componentId?: string\n}\n\nexport const iconSrcMapping = {\n dashboard: 'home2',\n arrow: 'arrow',\n telescope: 'telescope-outline',\n 'push-pin': 'push-pin',\n}\n\nexport const iconAliasMapping = {\n analyze: 'analytics',\n informationCircle: 'information-circle-outline',\n merchandize: 'funnel-outline',\n minus: 'remove-circle',\n monitoring: 'list',\n more: 'add-circle',\n settings: 'settings-outline',\n import_export: 'swap-vertical-outline',\n}\nexport const customIcons = [\n ...Object.keys(iconSrcMapping),\n ...Object.keys(iconAliasMapping),\n]\n\n/*\n * Creation of special props to be clean on Typescripts files\n * See: https://github.com/ionic-team/ionicons\n * List: https://ionic.io/ionicons/\n *\n * Setup switch for special names that need svg or have another name in ion-icons\n */\n\nfunction getIonIconURL(iconName: string, custom?: boolean): string {\n return `/images/ionicons/${custom ? 'custom/' : ''}${iconName}.svg`\n}\n\nfunction IonIcon(props: IProps): JSX.Element {\n const { name, tooltip, componentId, ...other } = props\n const style = tooltip\n ? {\n color: '#8187B9',\n width: '20px',\n height: '20px',\n display: 'flex',\n }\n : {}\n const iconProps = { ...other, style: { ...style, ...other.style } }\n if (name in iconSrcMapping) {\n return (\n <ion-icon\n src={getIonIconURL(\n iconSrcMapping[name as keyof typeof iconSrcMapping],\n true\n )}\n data-testid={generateTestId(TestId.IONICON, componentId)}\n {...iconProps}\n />\n )\n } else if (name in iconAliasMapping) {\n return (\n <ion-icon\n src={getIonIconURL(\n iconAliasMapping[name as keyof typeof iconAliasMapping]\n )}\n data-testid={generateTestId(TestId.IONICON, componentId)}\n {...iconProps}\n />\n )\n }\n return (\n <ion-icon\n src={getIonIconURL(name)}\n data-testid={generateTestId(TestId.IONICON, componentId)}\n {...iconProps}\n />\n )\n}\n\nexport default IonIcon\n","// TODO: factorize this with front/e2e/tests/helper/testIds.ts\nexport enum TestId {\n CATALOGS = 'catalogs',\n NB_ACTIVE_LOCALES = 'nbActiveLocales',\n LANGUAGE = 'language',\n INPUT_TEXT = 'inputText',\n INPUT_INTEGER = 'inputInteger',\n INPUT_FLOAT = 'inputFloat',\n RANGE_TO_INPUT = 'rangeToInput',\n RANGE_FROM_INPUT = 'rangeFromInput',\n SLIDER = 'slider',\n RANGE_DATE_FROM = 'rangeDateFrom',\n RANGE_DATE_TO = 'rangeDateTo',\n RULE_ENGINE = 'ruleEngine',\n REQUEST_TYPE = 'requestType',\n REQUEST_TYPE_ITEM = 'requestTypeItem',\n SYNONYM = 'synonym',\n SYNONYM_ADD_BUTTON = 'synonymAddButton',\n SYNONYM_REMOVE_BUTTON = 'synonymRemoveButton',\n TEXT_FIELD_TAGS = 'textFieldTags',\n TEXT_FIELD_TAGS_CLEAN_BUTTON = 'textFieldTagsCleanButton',\n TEXT_FIELD_TAGS_MULTIPLE = 'textFieldTagsMultiple',\n TEXT_FIELD_TAGS_MULTIPLE_ADD_BUTTON = 'textFieldTagsMultipleAddButton',\n EXPANSION = 'expansion',\n EXPANSION_REFERENCE_TERM = 'expensionReferenceTerm',\n EXPANSION_TERMS = 'expensionTerms',\n EXPANSION_TRASH_BUTTON = 'expensionTrashButton',\n EXPANSION_ADD_BUTTON = 'expensionAddButton',\n MULTIPLE_INPUT = 'multipleInput',\n SELECT = 'select',\n SWITCH = 'switch',\n TREE_SELECTOR = 'treeSelector',\n DROPDOWN = 'dropdown',\n DROPDOWN_OPTION = 'dropdownOption',\n DROPDOWN_COLLAPSING_ICON = 'dropdownCollapsingIcon',\n DROPDOWN_OPTION_GROUP_TITLE = 'dropdownOptionGroupTitle',\n DROPDOWN_CLEAR_ICON = 'dropdownClearIcon',\n HELPER_TEXT = 'helperText',\n CHECKBOX = 'checkbox',\n CHIP = 'chip',\n CHIP_DELETE_ICON = 'chipDeleteIcon',\n IONICON = 'ionicon',\n FILTER = 'filter',\n FILTER_CHIPS_BOX = 'filterChipsBox',\n FILTER_APPLY_BUTTON = 'filterApplyButton',\n FILTER_CLEAR_BUTTON = 'filterClearIcon',\n FILTER_NB_ACTIVE_FILTERS = 'filterNbActiveFilters',\n FILTER_TOGGLE_BUTTON = 'filterToggleButton',\n FILTER_SEARCH_BAR = 'filterSearchBar',\n BUTTON = 'button',\n APP_BAR = 'appBar',\n USER_MENU = 'userMenu',\n USER_MENU_CONTENT = 'userMenuContent',\n LOG_OUT_BUTTON = 'logOutButton',\n ALERT = 'alert',\n TABLE = 'table',\n PAGINATION = 'pagination',\n PAGINATION_FROM = 'paginationFrom',\n PAGINATION_TO = 'paginationTo',\n PAGINATION_COUNT = 'paginationCount',\n POPIN_CATALOGS = 'popinCatalogs',\n SIDE_BAR = 'sidebar',\n SIDE_BAR_COLLAPSING_BUTTON = 'sidebarCollapsingButton',\n MENU_ITEM = 'menuItem',\n MENU_ITEM_CHILDREN = 'menuItemChildren',\n MENU_ITEM_CHILDREN_COLLAPSING_BUTTON = 'menuItemChildrenCollapsingButton',\n MENU_ITEM_ICON = 'menuItemIcon',\n LABEL_MENU_ITEM_ICON = 'labelMenuItemIcon',\n MENU_ITEM_LINK = 'menuItemLink',\n MENU_ITEM_LINK_LABEL = 'menuItemLinkLabel',\n BREADCRUMBS = 'breadcrumbs',\n HELP_TOOLTIP = 'helpTooltip',\n HELP_OVER = 'helpOver',\n USER_NAME = 'userName',\n USER_EMAIL = 'userEmail',\n SCORE = 'score',\n SCORE_CONTAINER = 'scoreContainer',\n STOCK = 'stock',\n PRICE = 'price',\n OTHER_READABLE_FIELD = 'otherReadableField',\n EXPLAIN_DETAILS = 'explainDetails',\n EXPLAIN_DETAILS_NAME = 'explainDetailsName',\n EXPLAIN_DETAILS_GENERAL_INFORMATION = 'explainDetailsGeneralInformation',\n READ_ONLY_TABLE = 'readOnlyTable',\n READ_ONLY_TABLE_ROW = 'readOnlyTableRow',\n READ_ONLY_TABLE_ROW_IMPORTANT = 'readOnlyTableRowImportant',\n READ_ONLY_TABLE_COLLAPSING_BUTTON = 'readOnlyTableCollapsingButton',\n DIALOG = 'dialog',\n DIALOG_CONTENT = 'dialogContent',\n DIALOG_CLOSE_BUTTON = 'dialogCloseButton',\n DIALOG_CANCEL_BUTTON = 'dialogCancelButton',\n DIALOG_CONFIRM_BUTTON = 'dialogConfirmButton',\n GRID_CREATE_BUTTON = 'gridCreateButton',\n GRID_JOB_BUTTON = 'gridJobButton',\n FIELD_SET = 'fieldSet',\n PREVIEW_REQUIRED_MESSAGE = 'previewRequiredMessage',\n RESOURCE_TABLE_NB_CUSTOM_VALUES_MESSAGE = 'resourceTableNbCustomValuesMessage',\n TREE = 'tree',\n TREE_ITEM_TITLE_BUTTON = 'treeItemTitleButton',\n TREE_COLLAPSING_ITEM_LIST_BUTTON = 'treeCollapsingItemListButton',\n FACETS_SETTINGS_BUTTON = 'facetDefaultValuesButton',\n FACETS_PAGE_TITLE = 'facetsPageTitle',\n PAGE_TITLE = 'pageTitle',\n PRODUCTS_CONTAINER = 'productsContainer',\n PRODUCTS_CONTAINER_SAVE_BUTTON = 'productsContainerSaveButton',\n PRODUCTS_CONTAINER_SEARCH_BAR = 'productsContainerSearchBar',\n TOP_AND_BOTTOM_TABLE = 'topAndBottomTable',\n TITLE_BLOCK = 'titleBlock',\n TABS = 'tabs',\n TAB = 'tab',\n RULES_MANAGER = 'rulesManager',\n COMBINATION_RULES = 'combinationRules',\n CONFIGURATION_FORM = 'configurationForm',\n IMPORT_EXPORT_PROFILE_RUN = 'importExportProfileRun',\n LOGS = 'logs',\n JOBFILE = 'jobFile',\n JOBPROFILE = 'jobProfile',\n STATUS = 'status',\n UPLOAD_JOB_FILE_MODAL = 'uploadJobFileModal',\n UPLOAD_JOB_FILE_MODAL_TITLE = 'uploadJobFileModalTitle',\n FILE_UPLOAD_DROPZONE = 'fileUploadDropZone',\n FILE_UPLOAD_DROPZONE_SELECTED_FILE_NAME = 'selectedFileName',\n FILE_DOWNLOADER = 'fileDownloader',\n KPI = 'kpi',\n KPI_GROUP = 'kpiGroup',\n KPI_FILTERS = 'kpiFilters',\n}\n\nexport const TEST_ID_SEPARATOR = '|'\n\ntype ItemIds = (TestId | string)[]\ntype ItemId = `${typeof TEST_ID_SEPARATOR}${TestId | string}` | ''\nexport type FullTestId = `${TestId}${ItemId}`\n\nfunction normalizeItemId(itemIds: ItemIds): ItemId {\n const filtered = itemIds.filter((itemId) => itemId)\n return filtered.length > 0\n ? (`${TEST_ID_SEPARATOR}${filtered.join(TEST_ID_SEPARATOR)}` as ItemId)\n : ''\n}\n\nexport function generateTestId(\n testId: TestId,\n ...itemIds: ItemIds\n): FullTestId {\n return `${testId}${normalizeItemId(itemIds)}`\n}\n","import React from 'react'\nimport { Box } from '@mui/system'\n\nimport IonIcon from '../IonIcon/IonIcon'\nimport { TFunction, useTranslation } from 'next-i18next'\n\ninterface IProps {\n type: 'up' | 'down' | 'straight'\n boostNumber?: number\n boostMultiplicator?: number\n}\ninterface IBoostDetails {\n boostText: string\n boostColor: string\n boostTextColor: string\n boostIconName: string\n}\n\nfunction setBoostDetails(\n type: 'up' | 'down' | 'straight',\n boostNumber: number,\n boostMultiplicator: number,\n t: TFunction\n): IBoostDetails {\n const boostText = t('boost', { count: boostNumber })\n switch (type) {\n case 'up':\n return {\n boostText: ` x${boostMultiplicator} ( ${boostNumber} ${boostText}) `,\n boostColor: 'success.light',\n boostTextColor: 'success.main',\n boostIconName: 'trending-up-outline',\n }\n case 'down':\n return {\n boostText: ` x${boostMultiplicator} ( ${boostNumber} ${boostText}) `,\n boostColor: 'error.light',\n boostTextColor: 'error.main',\n boostIconName: 'trending-down-outline',\n }\n case 'straight':\n return {\n boostText: 'no boost',\n boostColor: 'neutral.light',\n boostTextColor: 'colors.neutral.600',\n boostIconName: 'arrow-forward-outline',\n }\n }\n}\n\nfunction Boost(props: IProps): JSX.Element {\n const { type, boostNumber, boostMultiplicator } = props\n\n const { t } = useTranslation('boost')\n const boostDetail = setBoostDetails(type, boostNumber, boostMultiplicator, t)\n\n return (\n <Box sx={{ display: 'flex', gap: '4px' }}>\n <Box\n sx={{\n backgroundColor: boostDetail.boostColor,\n color: boostDetail.boostTextColor,\n height: '20px',\n width: '20px',\n borderRadius: '50%',\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center',\n }}\n >\n <IonIcon\n name={boostDetail.boostIconName}\n style={\n type === 'straight'\n ? { width: '14px', height: '14px' }\n : { width: '14px', height: '8px' }\n }\n />\n </Box>\n {boostNumber !== undefined && boostMultiplicator !== undefined && (\n <Box\n sx={{\n color: boostDetail.boostTextColor,\n fontFamily: 'var(--gally-font)',\n fontWeight: '500',\n fontSize: '12px',\n lineHeight: '18px',\n }}\n >\n {boostDetail.boostText}\n </Box>\n )}\n </Box>\n )\n}\n\nexport default Boost\n","import React from 'react'\nimport { Typography } from '@mui/material'\nimport { styled } from '@mui/system'\n\nconst CustomBreadCrumb = styled('span')({\n fontWeight: '500',\n fontSize: '12px',\n lineHeight: '18px',\n})\n\nconst CustomBreadCrumbColorClassique = styled(CustomBreadCrumb)(\n ({ theme }) => ({\n color: theme.palette.colors.neutral['500'],\n })\n)\n\nconst CustomBreadCrumbColorLast = styled(CustomBreadCrumb)(({ theme }) => ({\n color: theme.palette.colors.neutral['800'],\n}))\n\ninterface IProps {\n label: string\n last: boolean\n}\n\nfunction Breadcrumb(props: IProps): JSX.Element {\n const { label, last } = props\n const Component = last\n ? CustomBreadCrumbColorLast\n : CustomBreadCrumbColorClassique\n return (\n <Typography>\n <Component>{label}</Component>\n </Typography>\n )\n}\n\nexport default Breadcrumb\n","import React from 'react'\nimport { Breadcrumbs as MuiBreadcrumbs } from '@mui/material'\n\nimport {\n IMenu,\n findBreadcrumbLabel,\n getSlugArray,\n} from '@elastic-suite/gally-admin-shared'\n\nimport Breadcrumb from './Breadcrumb'\nimport { TestId, generateTestId } from '../../../utils/testIds'\n\ninterface IProps {\n slug: string | string[]\n menu: IMenu\n}\n\nfunction Breadcrumbs(props: IProps): JSX.Element {\n const { menu, slug } = props\n\n if (!slug || slug.length === 0 || menu.hierarchy.length === 0) {\n return null\n }\n\n const breadcrumbProps = getSlugArray(slug)\n .map((item: string, index: number, slug: string[]) => ({\n key: item,\n label: findBreadcrumbLabel(index, slug, menu.hierarchy),\n last: slug.length <= index + 1,\n }))\n .filter(({ label }) => label)\n return (\n <MuiBreadcrumbs\n aria-label=\"breadcrumb\"\n sx={{ color: 'colors.neutral.500' }}\n data-testid={generateTestId(TestId.BREADCRUMBS)}\n >\n {breadcrumbProps.map(({ key, ...props }) => (\n <Breadcrumb key={key} {...props} />\n ))}\n </MuiBreadcrumbs>\n )\n}\n\nexport default Breadcrumbs\n","import React from 'react'\nimport { styled } from '@mui/system'\nimport { ChipProps, IconButton, Chip as MuiChip } from '@mui/material'\n\nimport IonIcon from '../IonIcon/IonIcon'\nimport { TestId, generateTestId } from '../../../utils/testIds'\n\nconst StyledChip = styled(MuiChip)(({ theme, disabled }) => ({\n display: 'inline-flex',\n height: '26px',\n color: theme.palette.colors.neutral['900'],\n fontFamily: 'var(--gally-font)',\n fontWeight: disabled ? 400 : 600,\n fontSize: disabled ? 14 : 12,\n lineHeight: '18px',\n textAlign: 'center',\n alignItems: 'center',\n padding: `2px ${theme.spacing(1)}`,\n background: theme.palette.colors.neutral['300'],\n borderRadius: '13px',\n cursor: 'default',\n '&.MuiChip-colorPrimary': {\n color: theme.palette.colors.secondary['600'],\n background: theme.palette.colors.secondary['100'],\n },\n '&.MuiChip-colorSuccess': {\n color: theme.palette.success.main,\n background: theme.palette.success.light,\n },\n '&.MuiChip-colorWarning': {\n color: theme.palette.warning.main,\n background: theme.palette.warning.light,\n },\n '&.MuiChip-colorError': {\n color: theme.palette.error.main,\n background: theme.palette.error.light,\n },\n '&.MuiChip-sizeSmall': {\n height: '20px',\n },\n '& .MuiChip-label': {\n padding: 0,\n },\n '& .MuiChip-deleteIcon': {\n color: 'inherit',\n margin: `0 0 0 ${theme.spacing(0.5)}`,\n padding: 0,\n fontSize: '14px',\n '&:hover': {\n color: 'inherit',\n opacity: 0.5,\n },\n },\n}))\ninterface IChipProps extends ChipProps {\n componentId?: string\n}\nfunction Chip(props: IChipProps): JSX.Element {\n const { disabled, componentId } = props\n\n const dataTestId = generateTestId(TestId.CHIP, componentId)\n return (\n <StyledChip\n disabled={disabled}\n data-testid={dataTestId}\n {...props}\n style={{ opacity: 1 }}\n deleteIcon={\n <IconButton>\n <IonIcon\n name=\"close\"\n componentId={generateTestId(TestId.CHIP_DELETE_ICON, dataTestId)}\n />\n </IconButton>\n }\n />\n )\n}\n\nexport default Chip\n","import React from 'react'\nimport { Box } from '@mui/material'\n\ninterface IProps {\n onClose: () => void\n}\n\nfunction CloseComponent({ onClose }: IProps): JSX.Element {\n return (\n <Box\n sx={{\n position: 'fixed',\n width: '100vw',\n height: '100vh',\n zIndex: 99,\n left: '0',\n top: '0',\n }}\n onClick={onClose}\n />\n )\n}\n\nexport default CloseComponent\n","import React, {\n ChangeEvent,\n ForwardedRef,\n ReactNode,\n SyntheticEvent,\n forwardRef,\n} from 'react'\nimport {\n CheckboxProps,\n FormControlLabel,\n FormHelperText,\n Checkbox as MuiCheckbox,\n} from '@mui/material'\nimport IonIcon from '../IonIcon/IonIcon'\nimport { TestId, generateTestId } from '../../../utils/testIds'\n\nexport interface ICheckboxWithoutErrorProps\n extends Omit<CheckboxProps, 'onChange' | 'onClick'> {\n label?: ReactNode\n list?: boolean\n onChange?: (checked: boolean, event: SyntheticEvent) => void\n onClick?: (event: SyntheticEvent) => void\n small?: boolean\n helperIcon?: string\n helperText?: ReactNode\n error?: boolean\n componentId?: string\n}\n\nfunction CheckboxWithoutError(\n props: ICheckboxWithoutErrorProps,\n ref?: ForwardedRef<HTMLInputElement>\n): JSX.Element {\n const {\n error,\n helperIcon,\n helperText,\n disabled,\n label,\n list,\n onChange,\n onClick,\n small,\n componentId,\n ...checkboxProps\n } = props\n\n function handleChange(event: ChangeEvent<HTMLInputElement>): void {\n if (onChange) {\n onChange(event.target.checked, event)\n }\n }\n\n return (\n <FormControlLabel\n componentsProps={{ typography: { variant: list ? 'caption' : 'body2' } }}\n control={\n <MuiCheckbox\n {...checkboxProps}\n inputRef={ref}\n onChange={handleChange}\n sx={{\n ...(Boolean(list) && {\n marginBottom: '-9px',\n marginTop: '-9px',\n fontSize: '12px',\n }),\n ...(Boolean(small) && {\n padding: 0,\n marginLeft: '6px',\n marginRight: '6px',\n }),\n }}\n />\n }\n onClick={onClick}\n disabled={disabled}\n label={\n <div>\n {label}\n {Boolean(helperText) && (\n <FormHelperText\n error={error}\n data-testid={generateTestId(TestId.HELPER_TEXT, componentId)}\n >\n {Boolean(helperIcon) && (\n <IonIcon\n name={helperIcon}\n style={{ fontSize: 18, marginRight: 2 }}\n />\n )}\n {helperText}\n </FormHelperText>\n )}\n </div>\n }\n sx={{\n ...(Boolean(small) && {\n marginLeft: '-6px',\n }),\n }}\n data-testid={generateTestId(TestId.CHECKBOX, componentId)}\n />\n )\n}\n\nexport default forwardRef(CheckboxWithoutError)\n","import React, { ForwardedRef, useCallback } from 'react'\n\nimport {\n IFieldErrorProps,\n IOnChange,\n IValidator,\n useFormError,\n} from '../../../hooks'\n\nimport CheckboxWithoutError, {\n ICheckboxWithoutErrorProps,\n} from './CheckboxWithoutError'\n\ninterface ICheckboxErrorProps\n extends IFieldErrorProps,\n ICheckboxWithoutErrorProps {}\n\nfunction Checkbox(props: ICheckboxErrorProps): JSX.Element {\n const { onChange, showError, additionalValidator, ...inputProps } = props\n\n const validator = useCallback<IValidator>(\n (value, event) => {\n if (additionalValidator) return additionalValidator(value, event)\n return ''\n },\n [additionalValidator]\n )\n\n const [{ ref, ...formErrorProps }] = useFormError(\n onChange as IOnChange,\n inputProps.checked,\n showError,\n validator,\n inputProps.disabled\n )\n\n return (\n <CheckboxWithoutError\n {...inputProps}\n {...formErrorProps}\n error={inputProps?.error || formErrorProps?.error}\n helperIcon={inputProps?.helperIcon || formErrorProps?.helperIcon}\n helperText={inputProps?.helperText || formErrorProps?.helperText}\n ref={ref as ForwardedRef<HTMLInputElement>}\n />\n )\n}\n\nexport default Checkbox\n","import {\n Dispatch,\n SetStateAction,\n useCallback,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport Router from 'next/router'\nimport { useTranslation } from 'next-i18next'\nimport debounce from 'lodash.debounce'\nimport {\n AuthError,\n IFetch,\n IFetchApi,\n IHydraMember,\n IHydraResponse,\n ILoadResource,\n IResource,\n IResourceEditableOperations,\n ISearchParameters,\n LoadStatus,\n defaultPageSize,\n fetchApi,\n fetchApiUsingPagination,\n getListApiParameters,\n isError,\n storageRemove,\n tokenStorageKey,\n} from '@elastic-suite/gally-admin-shared'\n\nimport { useLog } from './useLog'\nimport { useResourceOperations } from './useResource'\n\nconst debounceDelay = 1000\n\nexport function useApiFetch(secure = true): IFetchApi {\n const { i18n } = useTranslation('common')\n const log = useLog()\n\n const { t } = useTranslation('alert')\n\n return useCallback<IFetchApi>(\n async <T extends object>(\n resource: IResource | string,\n searchParameters?: ISearchParameters,\n options?: RequestInit,\n outputLog?: boolean\n ) => {\n try {\n const json = await fetchApi<T>(\n i18n.language,\n resource,\n searchParameters,\n options,\n secure\n )\n return json\n } catch (error) {\n if (outputLog !== false) {\n if (secure && error instanceof AuthError) {\n storageRemove(tokenStorageKey)\n Router.push('/login')\n log(t('Expired JWT Token'))\n } else {\n log(error)\n }\n }\n return { error }\n }\n },\n [i18n.language, log, secure, t]\n )\n}\n\nexport function useFetchApi<T extends object>(\n resource: IResource | string,\n searchParameters?: ISearchParameters,\n options?: RequestInit,\n secure = true,\n conditions = true,\n withDebounce = false,\n rowsPerPageWithPagination?: number\n): [IFetch<T>, Dispatch<SetStateAction<T>>, ILoadResource] {\n const fetchApi = useApiFetch(secure)\n const [response, setResponse] = useState<IFetch<T>>({\n status: LoadStatus.IDLE,\n })\n\n const updateResponse = useCallback((data: SetStateAction<T>): void => {\n setResponse((prevState) => ({\n ...prevState,\n data: data instanceof Function ? data(prevState.data) : data,\n }))\n }, [])\n\n const load = useCallback(() => {\n if (conditions) {\n setResponse((prevState) => ({\n data: prevState.data,\n status: LoadStatus.LOADING,\n }))\n if (!resource) {\n return setResponse({ data: null, status: LoadStatus.SUCCEEDED })\n }\n\n if (rowsPerPageWithPagination) {\n fetchApiUsingPagination<T>(\n fetchApi,\n resource,\n searchParameters,\n options,\n rowsPerPageWithPagination\n ).then((response) => {\n setResponse(response)\n })\n } else {\n fetchApi<T>(resource, searchParameters, options).then((json) => {\n if (isError(json)) {\n setResponse({ error: json.error, status: LoadStatus.FAILED })\n } else {\n setResponse({ data: json, status: LoadStatus.SUCCEEDED })\n }\n })\n }\n }\n }, [\n conditions,\n fetchApi,\n options,\n resource,\n searchParameters,\n rowsPerPageWithPagination,\n ])\n\n const customDebounce = useMemo(\n () => debounce((func: () => void) => func(), debounceDelay),\n []\n )\n\n useLayoutEffect(() => {\n if (withDebounce) {\n customDebounce(load)\n } else {\n load()\n }\n }, [load, customDebounce, withDebounce])\n\n return [response, updateResponse, load]\n}\n\nexport function useApiList<T extends object>(\n resource?: IResource | string,\n page: number | false = 0,\n rowsPerPage: number = defaultPageSize,\n searchParameters?: ISearchParameters,\n searchValue?: string,\n withDebounce = false,\n withPagination = false,\n conditions = true\n): [\n IFetch<IHydraResponse<T>> | null,\n Dispatch<SetStateAction<T[]>>?,\n ILoadResource?\n] {\n const parameters = useMemo(\n () =>\n getListApiParameters(page, rowsPerPage, searchParameters, searchValue),\n [page, rowsPerPage, searchParameters, searchValue]\n )\n // BEWARE: useFetchApi parameters order do no match useApiList parameters order\n const [response, updateResponse, load] = useFetchApi<IHydraResponse<T>>(\n resource,\n parameters,\n undefined,\n true,\n conditions,\n withDebounce,\n withPagination ? rowsPerPage : undefined\n )\n\n const updateList = useCallback(\n (data: SetStateAction<T[]>): void => {\n updateResponse((prevState) => ({\n ...prevState,\n 'hydra:member':\n data instanceof Function ? data(prevState['hydra:member']) : data,\n }))\n },\n [updateResponse]\n )\n\n return [response, updateList, load]\n}\n\nexport function useApiEditableList<T extends IHydraMember>(\n resource: IResource,\n page: number | false = 0,\n rowsPerPage: number = defaultPageSize,\n searchParameters?: ISearchParameters,\n searchValue?: string,\n url?: string,\n withDebounce = false\n): [\n IFetch<IHydraResponse<T>>,\n IResourceEditableOperations<T>,\n Dispatch<SetStateAction<T[]>>\n] {\n const [response, updateList, load] = useApiList<T>(\n url ? url : resource,\n page,\n rowsPerPage,\n searchParameters,\n searchValue,\n withDebounce\n )\n const { create, remove, replace, update } = useResourceOperations<T>(resource)\n const itemsToUpdate = useRef<Record<string, Partial<T>>>({})\n\n const debouncedUpdate = useMemo(\n () =>\n debounce(async (isValid = true): Promise<void> => {\n if (isValid) {\n const promises = Object.entries(itemsToUpdate.current).map(\n ([id, updatedItems]) => {\n delete itemsToUpdate.current[id]\n return update(id, updatedItems)\n }\n )\n\n await Promise.all(promises)\n load()\n }\n }, debounceDelay),\n [load, update]\n )\n\n const editableUpdate = useCallback(\n (id: string | number, updatedItem: Partial<T>, isValid = true) => {\n if (id in itemsToUpdate.current) {\n itemsToUpdate.current[id] = {\n ...itemsToUpdate.current[id],\n ...updatedItem,\n }\n } else {\n itemsToUpdate.current[id] = updatedItem\n }\n\n updateList((items) =>\n items.map((item) =>\n item.id === id ? { ...item, ...updatedItem } : item\n )\n )\n debouncedUpdate(isValid)\n },\n [debouncedUpdate, updateList]\n )\n\n const massEditableUpdate = useCallback(\n async (\n ids: (string | number)[],\n updatedItem: Partial<T>\n ): Promise<void> => {\n updateList((items) =>\n items.map((item) =>\n ids.includes(item.id) ? { ...item, ...updatedItem } : item\n )\n )\n const promises = ids.map((id) => update(id, updatedItem))\n await Promise.all(promises)\n load()\n },\n [load, update, updateList]\n )\n\n const massEditableReplace = useCallback(\n async (\n ids: (string | number)[],\n updatedItem: Omit<T, '@id' | '@type'>\n ): Promise<void> => {\n updateList((items) =>\n items.map((item) =>\n ids.includes(item.id) ? { ...item, ...updatedItem } : item\n )\n )\n const promises = ids.map((id) =>\n replace({ id, ...updatedItem } as unknown as T)\n )\n\n await Promise.all(promises)\n load()\n },\n [load, replace, updateList]\n )\n\n const editableCreate = useCallback(\n async (item: Omit<T, 'id' | '@id' | '@type'>): Promise<void> => {\n const createResponse = await create(item)\n if (\n !isError(createResponse) &&\n response.data['hydra:member'].length < rowsPerPage\n ) {\n // reload if item has been added and we are on the last page\n load()\n }\n },\n [create, load, response, rowsPerPage]\n )\n\n const debouncedReplace = useMemo(\n () =>\n debounce(async (isValid = true): Promise<void> => {\n if (isValid) {\n const promises = Object.entries(itemsToUpdate.current).map(\n ([id, replacedItem]) => {\n delete itemsToUpdate.current[id]\n return replace(replacedItem)\n }\n )\n\n await Promise.all(promises)\n load()\n }\n }, debounceDelay),\n [load, replace]\n )\n\n const editableReplace = useCallback(\n (replacedItem: Partial<T>, isValid = true) => {\n const id = replacedItem.id as string\n if (id in itemsToUpdate.current) {\n itemsToUpdate.current[id] = {\n ...itemsToUpdate.current[id],\n ...replacedItem,\n }\n } else {\n itemsToUpdate.current[id] = replacedItem\n }\n\n updateList((items) =>\n items.map((item) =>\n item.id === replacedItem.id ? { ...item, ...replacedItem } : item\n )\n )\n debouncedReplace(isValid)\n },\n [debouncedReplace, updateList]\n )\n\n const editableRemove = useCallback(\n async (id: string | number): Promise<void> => {\n updateList((items) => items.filter((item) => item.id !== id))\n const removeResponse = await remove(id)\n if (\n isError(removeResponse) ||\n response.data['hydra:member'].length === rowsPerPage\n ) {\n // reload if error\n // and reload if we are not on the last page to fill the space left by the deleted item\n load()\n }\n },\n [load, remove, response, rowsPerPage, updateList]\n )\n\n const operations = useMemo(() => {\n const operations: IResourceEditableOperations<T> = {}\n if (update) {\n operations.update = editableUpdate\n operations.massUpdate = massEditableUpdate\n }\n if (create) {\n operations.create = editableCreate\n }\n if (replace) {\n operations.replace = editableReplace\n operations.massReplace = massEditableReplace\n }\n if (remove) {\n operations.remove = editableRemove\n }\n return operations\n }, [\n create,\n editableCreate,\n editableRemove,\n editableReplace,\n editableUpdate,\n massEditableUpdate,\n massEditableReplace,\n remove,\n replace,\n update,\n ])\n\n return [response, operations, updateList]\n}\n","import { useCallback } from 'react'\nimport { closeSnackbar, enqueueSnackbar } from 'notistack'\nimport {\n MessageSeverity,\n NetworkError,\n log,\n} from '@elastic-suite/gally-admin-shared'\n\nexport function useLog(\n severity: MessageSeverity = 'error'\n): (error: NetworkError) => void {\n return useCallback(\n (error: NetworkError) =>\n log(error, (message: string) =>\n enqueueSnackbar(message, {\n onShut: closeSnackbar,\n variant: severity,\n preventDuplicate: true,\n })\n ),\n [severity]\n )\n}\n","import { useCallback, useMemo } from 'react'\nimport { useRouter } from 'next/router'\n\nimport {\n IError,\n IHydraMember,\n IMainContext,\n IResource,\n IResourceOperations,\n Method,\n contentTypeHeader,\n getApiUrl,\n getResource,\n updatePropertiesAccordingToPath,\n} from '@elastic-suite/gally-admin-shared'\n\nimport { selectApi, useAppSelector } from '../store'\n\nimport { useApiFetch } from './useApi'\n\nexport function useResource(\n resourceName: string,\n mainContext: IMainContext = IMainContext.GRID\n): IResource {\n const api = useAppSelector(selectApi)\n const { asPath } = useRouter()\n const pathWithoutQueryString = useMemo(() => asPath?.split('?')[0], [asPath])\n\n return useMemo(() => {\n const resource = getResource(api, resourceName)\n return {\n ...resource,\n supportedProperty:\n resource?.supportedProperty instanceof Array\n ? resource?.supportedProperty.map((field) =>\n updatePropertiesAccordingToPath(\n field,\n pathWithoutQueryString,\n mainContext\n )\n )\n : resource?.supportedProperty\n ? [\n updatePropertiesAccordingToPath(\n resource.supportedProperty,\n pathWithoutQueryString,\n mainContext\n ),\n ]\n : resource?.supportedProperty,\n }\n }, [api, pathWithoutQueryString, mainContext, resourceName])\n}\n\nexport function useResourceOperations<T extends IHydraMember>(\n resource: IResource\n): IResourceOperations<T> {\n const { supportedOperation } = resource\n const fetchApi = useApiFetch()\n const apiUrl = getApiUrl(resource.url)\n\n const update = useCallback(\n (id: string | number, item: Partial<T>): Promise<T | IError> =>\n fetchApi<T>(`${apiUrl}/${id}`, undefined, {\n body: JSON.stringify(item),\n method: Method.PATCH,\n headers: { [contentTypeHeader]: 'application/merge-patch+json' },\n }),\n [apiUrl, fetchApi]\n )\n\n const create = useCallback(\n (item: Omit<T, 'id' | '@id' | '@type'>): Promise<T | IError> => {\n // Check if item is FormData (file upload)\n const isFormData = item instanceof FormData\n\n const requestOptions: RequestInit = {\n body: isFormData ? item : JSON.stringify(item),\n method: Method.POST,\n headers: {\n [contentTypeHeader]: isFormData ? '' : 'application/ld+json',\n },\n }\n\n return fetchApi(apiUrl, undefined, requestOptions)\n },\n [apiUrl, fetchApi]\n )\n\n const replace = useCallback(\n (item: Partial<T>): Promise<T | IError> =>\n fetchApi(`${apiUrl}/${item.id}`, undefined, {\n body: JSON.stringify(item),\n method: Method.PUT,\n }),\n [apiUrl, fetchApi]\n )\n\n const remove = useCallback(\n (id: string | number): Promise<T | IError> =>\n fetchApi(`${apiUrl}/${id}`, undefined, {\n method: Method.DELETE,\n }),\n [apiUrl, fetchApi]\n )\n\n return useMemo(\n () =>\n supportedOperation.reduce<IResourceOperations<T>>((acc, operation) => {\n if (typeof operation['@type'] === 'string') {\n acc.update = upd