From ccc230b5a3494deb05fe2e44ca628d18dbc6c328 Mon Sep 17 00:00:00 2001 From: Pranita Date: Thu, 26 Jun 2025 12:17:35 +0530 Subject: [PATCH] add selection widgets to modal --- .../AllWidgetsContent/AllWidgetsContent.tsx | 48 +++++++ .../all-widgets-content.less | 17 +++ .../CustomiseHomeModal/CustomiseHomeModal.tsx | 118 +++++++++++++++--- .../customise-home-modal.less | 11 ++ .../WidgetCard/WidgetCard.tsx | 42 +++++++ .../WidgetCard/widget-card.less | 39 ++++++ 6 files changed, 258 insertions(+), 17 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/MyData/CustomizableComponents/AllWidgetsContent/AllWidgetsContent.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/MyData/CustomizableComponents/AllWidgetsContent/all-widgets-content.less create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/MyData/CustomizableComponents/WidgetCard/WidgetCard.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/MyData/CustomizableComponents/WidgetCard/widget-card.less diff --git a/openmetadata-ui/src/main/resources/ui/src/components/MyData/CustomizableComponents/AllWidgetsContent/AllWidgetsContent.tsx b/openmetadata-ui/src/main/resources/ui/src/components/MyData/CustomizableComponents/AllWidgetsContent/AllWidgetsContent.tsx new file mode 100644 index 00000000000..5f8ab918a98 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/MyData/CustomizableComponents/AllWidgetsContent/AllWidgetsContent.tsx @@ -0,0 +1,48 @@ +/* + * Copyright 2025 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Col, Row } from 'antd'; +import React, { forwardRef } from 'react'; +import { Document } from '../../../../generated/entity/docStore/document'; +import WidgetCard from '../WidgetCard/WidgetCard'; +import './all-widgets-content.less'; + +interface AllWidgetsContentProps { + widgets: Document[]; + selectedWidgets: string[]; + onSelectWidget: (id: string) => void; +} + +const AllWidgetsContent = forwardRef( + ({ widgets, selectedWidgets, onSelectWidget }, ref) => { + return ( + + {widgets.map((widget) => ( + + onSelectWidget(widget.id ?? '')} + /> + + ))} + + ); + } +); + +export default AllWidgetsContent; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/MyData/CustomizableComponents/AllWidgetsContent/all-widgets-content.less b/openmetadata-ui/src/main/resources/ui/src/components/MyData/CustomizableComponents/AllWidgetsContent/all-widgets-content.less new file mode 100644 index 00000000000..62577b944cf --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/MyData/CustomizableComponents/AllWidgetsContent/all-widgets-content.less @@ -0,0 +1,17 @@ +/* + * Copyright 2025 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +.all-widgets-grid { + max-height: calc(100vh - 220px); // Adjust as needed + overflow-y: auto; + padding-right: 8px; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/MyData/CustomizableComponents/CustomiseHomeModal/CustomiseHomeModal.tsx b/openmetadata-ui/src/main/resources/ui/src/components/MyData/CustomizableComponents/CustomiseHomeModal/CustomiseHomeModal.tsx index 17ec6cfa074..18e6e1d274f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/MyData/CustomizableComponents/CustomiseHomeModal/CustomiseHomeModal.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/MyData/CustomizableComponents/CustomiseHomeModal/CustomiseHomeModal.tsx @@ -12,11 +12,18 @@ */ import Icon from '@ant-design/icons'; import { Button, Col, Divider, Modal, Row, Typography } from 'antd'; -import React, { useState } from 'react'; +import { AxiosError } from 'axios'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ReactComponent as AddIcon } from '../../../../assets/svg/add-square.svg'; +import { PAGE_SIZE_MEDIUM } from '../../../../constants/constants'; import { DEFAULT_HEADER_BG_COLOR } from '../../../../constants/Mydata.constants'; +import { LandingPageWidgetKeys } from '../../../../enums/CustomizablePage.enum'; +import { Document } from '../../../../generated/entity/docStore/document'; +import { getAllKnowledgePanels } from '../../../../rest/DocStoreAPI'; +import { showErrorToast } from '../../../../utils/ToastUtils'; import HeaderTheme from '../../HeaderTheme/HeaderTheme'; +import AllWidgetsContent from '../AllWidgetsContent/AllWidgetsContent'; import './customise-home-modal.less'; interface CustomiseHomeModalProps { @@ -37,6 +44,51 @@ const CustomiseHomeModal = ({ currentBackgroundColor ?? DEFAULT_HEADER_BG_COLOR ); + const [widgets, setWidgets] = useState([]); + const [selectedWidgets, setSelectedWidgets] = useState([]); + const [selectedKey, setSelectedKey] = useState('header-theme'); + const contentRef = useRef(null); + + const fetchWidgets = async () => { + try { + const { data } = await getAllKnowledgePanels({ + fqnPrefix: 'KnowledgePanel', + limit: PAGE_SIZE_MEDIUM, + }); + setWidgets( + data.filter( + (widget) => + widget.fullyQualifiedName !== LandingPageWidgetKeys.ANNOUNCEMENTS + ) + ); + } catch (error) { + showErrorToast(error as AxiosError); + } + }; + + useEffect(() => { + fetchWidgets(); + }, []); + + const handleSelectWidget = (id: string) => { + setSelectedWidgets((prev) => + prev.includes(id) ? prev.filter((w) => w !== id) : [...prev, id] + ); + }; + + const handleSidebarClick = (key: string) => { + if (key === 'header-theme' || key === 'all-widgets') { + setSelectedKey(key); + } else { + const target = contentRef.current?.querySelector( + `[data-widget-key="${key}"]` + ); + if (target) { + target.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + } + }; + const customiseOptions = [ { key: 'header-theme', @@ -51,15 +103,57 @@ const CustomiseHomeModal = ({ { key: 'all-widgets', label: t('label.all-widgets'), - component:
{t('label.all-widgets')}
, + component: ( + + ), }, ]; - const [selectedKey, setSelectedKey] = useState(customiseOptions[0].key); + const sidebarItems = [ + ...customiseOptions.map(({ key, label }) => ({ key, label })), + ...widgets.map((widget) => ({ + key: widget.fullyQualifiedName, + label: widget.name, + })), + ]; - const selectedComponent = customiseOptions.find( - (item) => item.key === selectedKey - )?.component; + const selectedComponent = useMemo(() => { + return customiseOptions.find((item) => item.key === selectedKey)?.component; + }, [customiseOptions, selectedKey]); + + const sidebarOptions = useMemo(() => { + return ( + <> + {sidebarItems.map((item) => { + const isWidgetItem = + item.key !== 'header-theme' && item.key !== 'all-widgets'; + + const isAllWidgets = item.key === 'all-widgets'; + + return ( +
handleSidebarClick(item.key)}> + {item.label} + {isAllWidgets && ( + + {widgets.length} + + )} +
+ ); + })} + + ); + }, [sidebarItems, selectedKey, handleSidebarClick]); const handleApply = () => { if (onBackgroundColorUpdate) { @@ -88,17 +182,7 @@ const CustomiseHomeModal = ({ onCancel={onClose}> - {customiseOptions.map((item) => ( -
setSelectedKey(item.key)}> - {item.label} -
- ))} + {sidebarOptions} void; +} + +const WidgetCard = ({ widget, isSelected, onSelect }: WidgetCardProps) => { + return ( + +
+ {widget.name} + {isSelected && } +
+ + {widget.description || 'No description available.'} + +
+ ); +}; + +export default WidgetCard; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/MyData/CustomizableComponents/WidgetCard/widget-card.less b/openmetadata-ui/src/main/resources/ui/src/components/MyData/CustomizableComponents/WidgetCard/widget-card.less new file mode 100644 index 00000000000..a36a8435fbd --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/MyData/CustomizableComponents/WidgetCard/widget-card.less @@ -0,0 +1,39 @@ +/* + * Copyright 2025 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +.widget-card { + border: 1px solid #e5e5e5; + border-radius: 12px; + cursor: pointer; + transition: border 0.3s ease; + position: relative; + + &.selected { + border: 2px solid #1890ff; + } + + .widget-card-content { + display: flex; + justify-content: space-between; + align-items: center; + } + + .check-icon { + color: #1890ff; + font-size: 16px; + } + + .widget-desc { + margin-top: 8px; + color: #888; + } +}