From 188aaf4b3d2e25eed95bed0c65c74fe38ac52a73 Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Fri, 22 Nov 2024 16:37:31 +0530 Subject: [PATCH 1/7] feat: convert design column config into sistent component Signed-off-by: Amit Amrutiya --- src/constants/constants.ts | 4 + .../DesignTableColumnConfig.tsx | 220 ++++++++++++++++++ .../CatalogDesignTable/columnConfig.tsx | 7 +- src/custom/CatalogDesignTable/helper.tsx | 9 + src/custom/CatalogDesignTable/index.ts | 13 +- src/custom/CatalogDesignTable/style.tsx | 25 ++ src/theme/colors/colors.ts | 2 +- 7 files changed, 272 insertions(+), 8 deletions(-) create mode 100644 src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx create mode 100644 src/custom/CatalogDesignTable/helper.tsx diff --git a/src/constants/constants.ts b/src/constants/constants.ts index e65be201..148e8aaf 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -7,3 +7,7 @@ export const CARIBBEAN_GREEN_FILL = '#00D3A9'; export const DEFAULT_STROKE = '#000'; export const DEFAULT_STROKE_WIDTH = '2'; export const CLOUD_URL = 'https://cloud.layer5.io'; +export const PLAYGROUND_MODES = { + DESIGNER: 'design', + VISUALIZER: 'visualize' +} as const; diff --git a/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx b/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx new file mode 100644 index 00000000..1ec263fe --- /dev/null +++ b/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx @@ -0,0 +1,220 @@ +import { MUIDataTableColumn, MUIDataTableMeta } from 'mui-datatables'; +import { PLAYGROUND_MODES } from '../../constants/constants'; +import { ChainIcon, CopyIcon, KanvasIcon, PublishIcon } from '../../icons'; +import Download from '../../icons/Download/Download'; +import { CHARCOAL } from '../../theme'; +import { downloadYaml, slugify } from '../CatalogDetail/helper'; +import { RESOURCE_TYPES } from '../CatalogDetail/types'; +import { Pattern } from '../CustomCatalog/CustomCard'; +import { ConditionalTooltip } from '../Helpers/CondtionalTooltip'; +import { DataTableEllipsisMenu } from '../ResponsiveDataTable'; +import AuthorCell from './AuthorCell'; +import { getColumnValue } from './helper'; +import { L5DeleteIcon, NameDiv } from './style'; + +interface TableMeta extends MUIDataTableMeta { + rowIndex: number; + tableData: Pattern[]; +} + +interface ColumnConfigProps { + handleDeleteModal: (data: Pattern) => () => void; + handlePublishModal: (data: Pattern) => void; + handleUnpublishModal: (data: Pattern) => () => void; + handleCopyUrl: (type: string, name: string, id: string) => void; + handleClone: (name: string, id: string) => void; + handleShowDetails: (designId: string, designName: string) => void; + isDownloadDisabled: boolean; + isCopyLinkDisabled: boolean; + isDeleteDisabled: boolean; + isPublishDisabled: boolean; + isUnpublishDisabled: boolean; +} + +export const colViews: [string, string][] = [ + ['id', 'na'], + ['name', 'xs'], + ['first_name', 'xs'], + ['created_at', 'na'], + ['updated_at', 'l'], + ['visibility', 'l'], + ['actions', 'xs'] +]; + +export const createDesignsColumnsConfig = ({ + handleDeleteModal, + handlePublishModal, + handleUnpublishModal, + handleCopyUrl, + handleClone, + handleShowDetails, + isUnpublishDisabled, + isCopyLinkDisabled, + isDeleteDisabled, + isPublishDisabled, + isDownloadDisabled +}: ColumnConfigProps): MUIDataTableColumn[] => { + return [ + { + name: 'id', + label: 'ID', + options: { + filter: false, + customBodyRender: (value: string) => + } + }, + { + name: 'name', + label: 'Pattern Name', + options: { + filter: false, + sort: true, + searchable: true, + customBodyRender: (value: string, tableMeta: MUIDataTableMeta) => { + const designId = (tableMeta as TableMeta).tableData[tableMeta.rowIndex]?.id ?? ''; + const designName = (tableMeta as TableMeta).tableData[tableMeta.rowIndex]?.name ?? ''; + + return handleShowDetails(designId, designName)}>{value}; + } + } + }, + { + name: 'first_name', + label: 'Author', + options: { + filter: false, + sort: true, + searchable: true, + customBodyRender: (_, tableMeta: MUIDataTableMeta) => { + const firstName = getColumnValue(tableMeta as TableMeta, 'first_name'); + const lastName = getColumnValue(tableMeta as TableMeta, 'last_name'); + const avatar_url = getColumnValue(tableMeta as TableMeta, 'avatar_url'); + const user_id = getColumnValue(tableMeta as TableMeta, 'user_id'); + + return ( + + ); + } + } + }, + { + name: 'created_at', + label: 'Created At', + options: { + filter: false, + sort: true, + searchable: true, + setCellHeaderProps: () => { + return { align: 'center' }; + } + } + }, + { + name: 'updated_at', + label: 'Updated At', + options: { + filter: false, + sort: true, + searchable: true, + setCellHeaderProps: () => { + return { align: 'center' }; + } + } + }, + { + name: 'visibility', + label: 'Visibility', + options: { + filter: false, + sort: false, + searchable: true + } + }, + { + name: 'actions', + label: 'Actions', + options: { + filter: false, + sort: false, + searchable: false, + setCellHeaderProps: () => ({ align: 'center' as const }), + setCellProps: () => ({ align: 'center' as const }), + customBodyRender: function CustomBody(_, tableMeta: MUIDataTableMeta) { + const rowIndex = (tableMeta as TableMeta).rowIndex; + const rowData = (tableMeta as TableMeta).tableData[rowIndex]; + + const actionsList = [ + { + title: 'Download', + onClick: () => downloadYaml(rowData?.pattern_file, rowData?.name), + disabled: isDownloadDisabled, + icon: + }, + { + title: 'Copy Link', + disabled: rowData.visibility === 'private' || isCopyLinkDisabled, + onClick: () => { + handleCopyUrl(RESOURCE_TYPES.DESIGNS, rowData?.name, rowData?.id); + }, + icon: + }, + { + title: 'Open in playground', + onClick: () => { + window.open( + `https://playground.meshery.io/extension/meshmap?mode=${ + PLAYGROUND_MODES.DESIGNER + }&type=${RESOURCE_TYPES.DESIGNS}&id=${rowData?.id}&name=${slugify( + rowData?.name + )}`, + '_blank' + ); + }, + icon: + }, + { + title: 'Delete', + disabled: isDeleteDisabled, + onClick: () => handleDeleteModal(rowData)(), + icon: + } + ]; + + const publishAction = { + title: 'Publish', + disabled: isPublishDisabled, + onClick: () => handlePublishModal(rowData), + icon: + }; + + const unpublishAction = { + title: 'Unpublish', + onClick: () => handleUnpublishModal(rowData)(), + disabled: isUnpublishDisabled, + icon: + }; + + const cloneAction = { + title: 'Clone', + onClick: () => handleClone(rowData?.name, rowData?.id), + icon: + }; + + if (rowData.visibility === 'published') { + actionsList.splice(0, 0, cloneAction); + actionsList.splice(2, 0, unpublishAction); + } else { + actionsList.splice(1, 0, publishAction); + } + + return ; + } + } + } + ]; +}; diff --git a/src/custom/CatalogDesignTable/columnConfig.tsx b/src/custom/CatalogDesignTable/columnConfig.tsx index 549fd081..870895a0 100644 --- a/src/custom/CatalogDesignTable/columnConfig.tsx +++ b/src/custom/CatalogDesignTable/columnConfig.tsx @@ -19,6 +19,7 @@ import { Pattern } from '../CustomCatalog/CustomCard'; import { ConditionalTooltip } from '../Helpers/CondtionalTooltip'; import { DataTableEllipsisMenu } from '../ResponsiveDataTable'; import AuthorCell from './AuthorCell'; +import { getColumnValue } from './helper'; import { NameDiv } from './style'; export type ColView = [string, 'na' | 'xs' | 'l']; @@ -83,12 +84,6 @@ export const createDesignColumns = ({ showOpenPlayground }: ColumnConfigProps): MUIDataTableColumn[] => { const cleanedType = type?.replace('my-', '').replace(/s$/, ''); - const getColumnValue = (tableMeta: MUIDataTableMeta, targetColumn: string): any => { - //@ts-ignore - const rowData = tableMeta.tableData[tableMeta.rowIndex] as Pattern; - return (rowData as any)[targetColumn] || ''; - }; - return [ { name: 'id', diff --git a/src/custom/CatalogDesignTable/helper.tsx b/src/custom/CatalogDesignTable/helper.tsx new file mode 100644 index 00000000..a80b784b --- /dev/null +++ b/src/custom/CatalogDesignTable/helper.tsx @@ -0,0 +1,9 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { MUIDataTableMeta } from 'mui-datatables'; + +export const getColumnValue = (tableMeta: MUIDataTableMeta, targetColumn: string): any => { + //@ts-ignore + const rowData = tableMeta.tableData[tableMeta.rowIndex] as Pattern; + return (rowData as any)[targetColumn] || ''; +}; diff --git a/src/custom/CatalogDesignTable/index.ts b/src/custom/CatalogDesignTable/index.ts index 98c1393f..d56128bb 100644 --- a/src/custom/CatalogDesignTable/index.ts +++ b/src/custom/CatalogDesignTable/index.ts @@ -1,6 +1,17 @@ import AuthorCell from './AuthorCell'; import CatalogDesignsTable from './CatalogDesignTable'; import { colViews, createDesignColumns } from './columnConfig'; +import { + createDesignsColumnsConfig, + colViews as designColumnsColViews +} from './DesignColumnConfig'; export { TableVisibilityControl } from './TableVisibilityControl'; export { ViewSwitch } from './ViewSwitch'; -export { AuthorCell, CatalogDesignsTable, colViews, createDesignColumns }; +export { + AuthorCell, + CatalogDesignsTable, + colViews, + createDesignColumns, + createDesignsColumnsConfig, + designColumnsColViews +}; diff --git a/src/custom/CatalogDesignTable/style.tsx b/src/custom/CatalogDesignTable/style.tsx index 502129fe..2e01e64b 100644 --- a/src/custom/CatalogDesignTable/style.tsx +++ b/src/custom/CatalogDesignTable/style.tsx @@ -1,4 +1,7 @@ +import DeleteIcon from '@mui/icons-material/Delete'; import { styled } from '@mui/material'; +import { buttonDisabled } from '../../theme'; +import { HOVER_DELETE } from '../../theme/colors/colors'; export const NameDiv = styled('div')({ cursor: 'pointer', @@ -9,3 +12,25 @@ export const NameDiv = styled('div')({ textDecoration: 'underline' } }); + +interface DeleteIconProps { + disabled?: boolean; + bulk?: boolean; +} + +export const L5DeleteIcon = styled(DeleteIcon)(({ disabled, bulk, theme }) => ({ + color: disabled ? theme.palette.icon.disabled : theme.palette.text.secondary, + cursor: disabled ? 'not-allowed' : 'pointer', + width: bulk ? '32' : '28.8', + height: bulk ? '32' : '28.8', + '&:hover': { + color: disabled ? buttonDisabled : HOVER_DELETE, + '& svg': { + color: disabled ? buttonDisabled : HOVER_DELETE + } + }, + '& svg': { + color: theme.palette.error.main, + cursor: disabled ? 'not-allowed' : 'pointer' + } +})); diff --git a/src/theme/colors/colors.ts b/src/theme/colors/colors.ts index 107bc4f3..8823dc58 100644 --- a/src/theme/colors/colors.ts +++ b/src/theme/colors/colors.ts @@ -95,7 +95,7 @@ export const charcoal = { 70: '#B1B9BC', 60: '#8C999E', 50: '#647176', - 40: '#3C494E', + 40: '#3C494F', 30: '#28353A', 20: '#142126', 10: '#000D12' From 99e9cf8b90fd14ed2248d123c93b5ae39ea01845 Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Fri, 22 Nov 2024 17:03:53 +0530 Subject: [PATCH 2/7] feat: convert workspaces assignment modal into sistent component Signed-off-by: Amit Amrutiya --- src/custom/CatalogDesignTable/index.ts | 2 +- .../TransferList/TransferList.tsx | 2 +- src/custom/Workspaces/AssignmentModal.tsx | 87 +++++++++++++++++++ src/custom/Workspaces/index.ts | 3 + src/custom/Workspaces/styles.ts | 6 ++ src/custom/Workspaces/types.ts | 0 src/custom/index.tsx | 1 + 7 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 src/custom/Workspaces/AssignmentModal.tsx create mode 100644 src/custom/Workspaces/index.ts create mode 100644 src/custom/Workspaces/styles.ts create mode 100644 src/custom/Workspaces/types.ts diff --git a/src/custom/CatalogDesignTable/index.ts b/src/custom/CatalogDesignTable/index.ts index d56128bb..b1f9a4aa 100644 --- a/src/custom/CatalogDesignTable/index.ts +++ b/src/custom/CatalogDesignTable/index.ts @@ -4,7 +4,7 @@ import { colViews, createDesignColumns } from './columnConfig'; import { createDesignsColumnsConfig, colViews as designColumnsColViews -} from './DesignColumnConfig'; +} from './DesignTableColumnConfig'; export { TableVisibilityControl } from './TableVisibilityControl'; export { ViewSwitch } from './ViewSwitch'; export { diff --git a/src/custom/TransferModal/TransferList/TransferList.tsx b/src/custom/TransferModal/TransferList/TransferList.tsx index 8fe1d302..40098fee 100644 --- a/src/custom/TransferModal/TransferList/TransferList.tsx +++ b/src/custom/TransferModal/TransferList/TransferList.tsx @@ -46,7 +46,7 @@ export interface TransferListProps { leftPermission: boolean; } -interface ListItemType { +export interface ListItemType { id: number; name: string; kind: string | undefined; diff --git a/src/custom/Workspaces/AssignmentModal.tsx b/src/custom/Workspaces/AssignmentModal.tsx new file mode 100644 index 00000000..c9aa05c3 --- /dev/null +++ b/src/custom/Workspaces/AssignmentModal.tsx @@ -0,0 +1,87 @@ +import { Modal, ModalBody, ModalButtonPrimary, ModalButtonSecondary, ModalFooter } from '../Modal'; +import { TransferList } from '../TransferModal/TransferList'; +import { ListItemType } from '../TransferModal/TransferList/TransferList'; +import { ModalActionDiv } from './styles'; + +interface AssignmentModalProps { + open: boolean; + onClose: (e?: React.MouseEvent) => void; + title: string; + headerIcon: JSX.Element; + name: string; + assignableData: ListItemType[]; + handleAssignedData: (data: ListItemType[]) => void; + originalAssignedData: ListItemType[]; + emptyStateIcon: JSX.Element; + handleAssignablePage: () => void; + handleAssignedPage: () => void; + originalLeftCount: number; + originalRightCount: number; + onAssign: () => void; + disableTransfer: boolean; + isAssignDisabled: boolean; + isRemoveDisabled: boolean; + helpText: string; +} + +const AssignmentModal: React.FC = ({ + open, + onClose, + title, + headerIcon, + name, + assignableData, + handleAssignedData, + originalAssignedData, + emptyStateIcon, + handleAssignablePage, + handleAssignedPage, + originalLeftCount, + originalRightCount, + onAssign, + disableTransfer, + isAssignDisabled, + isRemoveDisabled, + helpText +}) => { + return ( + + + + + + + Cancel + + Save + + + + + ); +}; + +export default AssignmentModal; diff --git a/src/custom/Workspaces/index.ts b/src/custom/Workspaces/index.ts new file mode 100644 index 00000000..58a7423a --- /dev/null +++ b/src/custom/Workspaces/index.ts @@ -0,0 +1,3 @@ +import AssignmentModal from './AssignmentModal'; + +export { AssignmentModal }; diff --git a/src/custom/Workspaces/styles.ts b/src/custom/Workspaces/styles.ts new file mode 100644 index 00000000..d73baebb --- /dev/null +++ b/src/custom/Workspaces/styles.ts @@ -0,0 +1,6 @@ +import { styled } from '../../theme'; + +export const ModalActionDiv = styled('div')({ + display: 'flex', + gap: '1rem' +}); diff --git a/src/custom/Workspaces/types.ts b/src/custom/Workspaces/types.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/custom/index.tsx b/src/custom/index.tsx index ded1e4f5..a2202b9a 100644 --- a/src/custom/index.tsx +++ b/src/custom/index.tsx @@ -136,4 +136,5 @@ export type { export * from './CatalogDesignTable'; export * from './CatalogDetail'; export * from './Dialog'; +export * from './Workspaces'; export * from './permissions'; From 37933af1aa126e37f8966a362e64482149df93d2 Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Fri, 22 Nov 2024 17:41:44 +0530 Subject: [PATCH 3/7] feat: add EditButton component with custom tooltip and styled icon Signed-off-by: Amit Amrutiya --- src/custom/Workspaces/EditButton.tsx | 24 ++++++++++++++++++++++ src/custom/Workspaces/styles.ts | 30 +++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 src/custom/Workspaces/EditButton.tsx diff --git a/src/custom/Workspaces/EditButton.tsx b/src/custom/Workspaces/EditButton.tsx new file mode 100644 index 00000000..7486ae15 --- /dev/null +++ b/src/custom/Workspaces/EditButton.tsx @@ -0,0 +1,24 @@ +import { IconButton } from '@mui/material'; +import React from 'react'; +import { CustomTooltip } from '../CustomTooltip'; +import { L5EditIcon } from './styles'; + +interface EditButtonProps { + onClick: (e: React.MouseEvent) => void; + disabled?: boolean; + title?: string; +} + +const EditButton: React.FC = ({ onClick, disabled, title = 'Edit' }) => { + return ( + +
+ + + +
+
+ ); +}; + +export default EditButton; diff --git a/src/custom/Workspaces/styles.ts b/src/custom/Workspaces/styles.ts index d73baebb..e6917e2a 100644 --- a/src/custom/Workspaces/styles.ts +++ b/src/custom/Workspaces/styles.ts @@ -1,6 +1,34 @@ -import { styled } from '../../theme'; +import EditIcon from '@mui/icons-material/Edit'; +import { buttonDisabled, styled } from '../../theme'; +import { HOVER_DELETE } from '../../theme/colors/colors'; export const ModalActionDiv = styled('div')({ display: 'flex', gap: '1rem' }); + +interface ExtendedEditIconProps { + disabled?: boolean; + bulk?: boolean; + style?: React.CSSProperties; +} + +export const L5EditIcon = styled(EditIcon)( + ({ disabled, bulk, style, theme }) => ({ + color: disabled ? theme.palette.icon.disabled : theme.palette.text.secondary, + cursor: disabled ? 'not-allowed' : 'pointer', + width: bulk ? '32' : '28.8', + height: bulk ? '32' : '28.8', + '&:hover': { + color: disabled ? buttonDisabled : HOVER_DELETE, + '& svg': { + color: disabled ? buttonDisabled : HOVER_DELETE + } + }, + '& svg': { + color: theme.palette.error.main, + cursor: disabled ? 'not-allowed' : 'pointer' + }, + ...style + }) +); From a2c111ef08f331ff6a53aec7490dae9c0e074499 Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Fri, 22 Nov 2024 19:01:56 +0530 Subject: [PATCH 4/7] feat: add useDesignAssignment hook for managing design assignments in workspaces Signed-off-by: Amit Amrutiya --- .../Workspaces/hooks/useDesignAssignment.tsx | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 src/custom/Workspaces/hooks/useDesignAssignment.tsx diff --git a/src/custom/Workspaces/hooks/useDesignAssignment.tsx b/src/custom/Workspaces/hooks/useDesignAssignment.tsx new file mode 100644 index 00000000..9b9d4511 --- /dev/null +++ b/src/custom/Workspaces/hooks/useDesignAssignment.tsx @@ -0,0 +1,150 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { useEffect, useState } from 'react'; +import { Pattern } from '../../CustomCatalog/CustomCard'; +import { withDefaultPageArgs } from '../../PerformersSection/PerformersSection'; +import { AssignmentHookResult } from '../types'; + +interface AddedAndRemovedDesigns { + addedDesignsIds: string[]; + removedDesignsIds: string[]; +} + +interface useDesignAssignmentProps { + workspaceId: string; + useGetDesignsOfWorkspaceQuery: any; + useAssignDesignToWorkspaceMutation: any; + useUnassignDesignFromWorkspaceMutation: any; +} + +const useDesignAssignment = ({ + workspaceId, + useGetDesignsOfWorkspaceQuery, + useAssignDesignToWorkspaceMutation, + useUnassignDesignFromWorkspaceMutation +}: useDesignAssignmentProps): AssignmentHookResult => { + const [designsPage, setDesignsPage] = useState(0); + const [designsData, setDesignsData] = useState([]); + const designsPageSize = 25; + const [designsOfWorkspacePage, setDesignsOfWorkspacePage] = useState(0); + const [workspaceDesignsData, setWorkspaceDesignsData] = useState([]); + const [assignDesignModal, setAssignDesignModal] = useState(false); + const [skipDesigns, setSkipDesigns] = useState(true); + const [disableTransferButton, setDisableTransferButton] = useState(true); + const [assignedDesigns, setAssignedDesigns] = useState([]); + + const { data: designs } = useGetDesignsOfWorkspaceQuery( + withDefaultPageArgs({ + workspaceId, + page: designsPage, + pagesize: designsPageSize, + filter: '{"assigned":false}' + }), + { + skip: skipDesigns + } + ); + + const { data: designsOfWorkspace } = useGetDesignsOfWorkspaceQuery( + withDefaultPageArgs({ + workspaceId, + page: designsOfWorkspacePage, + pagesize: designsPageSize + }), + { + skip: skipDesigns + } + ); + + const [assignDesignToWorkspace] = useAssignDesignToWorkspaceMutation(); + const [unassignDesignFromWorkspace] = useUnassignDesignFromWorkspaceMutation(); + + useEffect(() => { + const designsDataRtk = designs?.designs ? designs.designs : []; + setDesignsData((prevData) => [...prevData, ...designsDataRtk]); + }, [designs]); + + useEffect(() => { + const designsOfWorkspaceDataRtk = designsOfWorkspace?.designs ? designsOfWorkspace.designs : []; + setWorkspaceDesignsData((prevData) => [...prevData, ...designsOfWorkspaceDataRtk]); + }, [designsOfWorkspace]); + + const handleAssignDesignModal = (e?: React.MouseEvent): void => { + e?.stopPropagation(); + setAssignDesignModal(true); + setSkipDesigns(false); + }; + + const handleAssignDesignModalClose = (e?: React.MouseEvent): void => { + e?.stopPropagation(); + setAssignDesignModal(false); + setSkipDesigns(true); + }; + + const handleAssignablePageDesign = (): void => { + const pagesCount = Math.ceil(Number(designs?.total_count) / designsPageSize); + if (designsPage < pagesCount - 1) { + setDesignsPage((prevDesignsPage) => prevDesignsPage + 1); + } + }; + + const handleAssignedPageDesign = (): void => { + const pagesCount = Math.ceil(Number(designsOfWorkspace?.total_count) / designsPageSize); + if (designsOfWorkspacePage < pagesCount - 1) { + setDesignsOfWorkspacePage((prevPage) => prevPage + 1); + } + }; + + const getAddedAndRemovedDesigns = (allAssignedDesigns: Pattern[]): AddedAndRemovedDesigns => { + const originalDesignsIds = workspaceDesignsData.map((design) => design.id); + const updatedDesignsIds = allAssignedDesigns.map((design) => design.id); + + const addedDesignsIds = updatedDesignsIds.filter((id) => !originalDesignsIds.includes(id)); + const removedDesignsIds = originalDesignsIds.filter((id) => !updatedDesignsIds.includes(id)); + + return { addedDesignsIds, removedDesignsIds }; + }; + + const handleAssignDesigns = async (): Promise => { + const { addedDesignsIds, removedDesignsIds } = getAddedAndRemovedDesigns(assignedDesigns); + + for (const id of addedDesignsIds) { + await assignDesignToWorkspace({ + workspaceId, + designId: id + }).unwrap(); + } + + for (const id of removedDesignsIds) { + await unassignDesignFromWorkspace({ + workspaceId, + designId: id + }).unwrap(); + } + + setDesignsData([]); + setWorkspaceDesignsData([]); + handleAssignDesignModalClose(); + }; + + const handleAssignDesignsData = (updatedAssignedData: Pattern[]): void => { + const { addedDesignsIds, removedDesignsIds } = getAddedAndRemovedDesigns(updatedAssignedData); + setDisableTransferButton(!(addedDesignsIds.length > 0 || removedDesignsIds.length > 0)); + setAssignedDesigns(updatedAssignedData); + }; + + return { + data: designsData, + workspaceData: workspaceDesignsData, + assignModal: assignDesignModal, + handleAssignModal: handleAssignDesignModal, + handleAssignModalClose: handleAssignDesignModalClose, + handleAssignablePage: handleAssignablePageDesign, + handleAssignedPage: handleAssignedPageDesign, + handleAssign: handleAssignDesigns, + handleAssignData: handleAssignDesignsData, + disableTransferButton, + assignedItems: assignedDesigns + }; +}; + +export default useDesignAssignment; From eb5108b37f7e050f11dedf3e0a29eca0a9aaa05e Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Fri, 22 Nov 2024 19:03:01 +0530 Subject: [PATCH 5/7] feat: update column view types to use ColView interface across CatalogDesignTable components Signed-off-by: Amit Amrutiya --- src/custom/CatalogDesignTable/CatalogDesignTable.tsx | 5 +++-- src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx | 3 ++- src/custom/CatalogDesignTable/columnConfig.tsx | 3 +-- .../CustomColumnVisibilityControl.tsx | 3 ++- src/custom/PerformersSection/PerformersSection.tsx | 2 +- src/custom/ResponsiveDataTable.tsx | 3 ++- src/custom/Workspaces/AssignmentModal.tsx | 8 ++++---- 7 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/custom/CatalogDesignTable/CatalogDesignTable.tsx b/src/custom/CatalogDesignTable/CatalogDesignTable.tsx index 42d9c111..bfe48242 100644 --- a/src/custom/CatalogDesignTable/CatalogDesignTable.tsx +++ b/src/custom/CatalogDesignTable/CatalogDesignTable.tsx @@ -5,6 +5,7 @@ import { PublishIcon } from '../../icons'; import { CHARCOAL, useTheme } from '../../theme'; import { Pattern } from '../CustomCatalog/CustomCard'; import { ErrorBoundary } from '../ErrorBoundary'; +import { ColView } from '../Helpers/ResponsiveColumns/responsive-coulmns.tsx/responsive-column'; import PromptComponent from '../Prompt'; import { PromptRef } from '../Prompt/promt-component'; import ResponsiveDataTable from '../ResponsiveDataTable'; @@ -22,7 +23,7 @@ interface CatalogDesignsTableProps { page: number; setPage: (page: number) => void; columnVisibility: Record; - colViews: Record | undefined; + colViews: ColView[]; handleBulkDeleteModal: (patterns: Pattern[], modalRef: React.RefObject) => void; handleBulkpatternsDataUnpublishModal: ( selected: any, @@ -43,7 +44,7 @@ export const CatalogDesignsTable: React.FC = ({ page = 0, setPage, columnVisibility = {}, - colViews = {}, + colViews = [], handleBulkDeleteModal, handleBulkpatternsDataUnpublishModal }) => { diff --git a/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx b/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx index 1ec263fe..af3ebe5a 100644 --- a/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx +++ b/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx @@ -7,6 +7,7 @@ import { downloadYaml, slugify } from '../CatalogDetail/helper'; import { RESOURCE_TYPES } from '../CatalogDetail/types'; import { Pattern } from '../CustomCatalog/CustomCard'; import { ConditionalTooltip } from '../Helpers/CondtionalTooltip'; +import { ColView } from '../Helpers/ResponsiveColumns/responsive-coulmns.tsx'; import { DataTableEllipsisMenu } from '../ResponsiveDataTable'; import AuthorCell from './AuthorCell'; import { getColumnValue } from './helper'; @@ -31,7 +32,7 @@ interface ColumnConfigProps { isUnpublishDisabled: boolean; } -export const colViews: [string, string][] = [ +export const colViews: ColView[] = [ ['id', 'na'], ['name', 'xs'], ['first_name', 'xs'], diff --git a/src/custom/CatalogDesignTable/columnConfig.tsx b/src/custom/CatalogDesignTable/columnConfig.tsx index 870895a0..bcdf1c45 100644 --- a/src/custom/CatalogDesignTable/columnConfig.tsx +++ b/src/custom/CatalogDesignTable/columnConfig.tsx @@ -17,13 +17,12 @@ import { downloadFilter, downloadYaml } from '../CatalogDetail/helper'; import { RESOURCE_TYPES } from '../CatalogDetail/types'; import { Pattern } from '../CustomCatalog/CustomCard'; import { ConditionalTooltip } from '../Helpers/CondtionalTooltip'; +import { ColView } from '../Helpers/ResponsiveColumns/responsive-coulmns.tsx/responsive-column'; import { DataTableEllipsisMenu } from '../ResponsiveDataTable'; import AuthorCell from './AuthorCell'; import { getColumnValue } from './helper'; import { NameDiv } from './style'; -export type ColView = [string, 'na' | 'xs' | 'l']; - export const colViews: ColView[] = [ ['id', 'na'], ['name', 'xs'], diff --git a/src/custom/CustomColumnVisibilityControl/CustomColumnVisibilityControl.tsx b/src/custom/CustomColumnVisibilityControl/CustomColumnVisibilityControl.tsx index 7541891d..c50eb4bd 100644 --- a/src/custom/CustomColumnVisibilityControl/CustomColumnVisibilityControl.tsx +++ b/src/custom/CustomColumnVisibilityControl/CustomColumnVisibilityControl.tsx @@ -1,3 +1,4 @@ +import { MUIDataTableColumn } from 'mui-datatables'; import React from 'react'; import { Box } from '../../base/Box'; import { Card } from '../../base/Card'; @@ -10,7 +11,7 @@ import PopperListener from '../PopperListener'; import TooltipIcon from '../TooltipIcon'; export interface CustomColumnVisibilityControlProps { - columns: CustomColumn[]; + columns: MUIDataTableColumn[]; customToolsProps: { columnVisibility: Record; setColumnVisibility: React.Dispatch>>; diff --git a/src/custom/PerformersSection/PerformersSection.tsx b/src/custom/PerformersSection/PerformersSection.tsx index 2fdcc1d8..53b21f83 100644 --- a/src/custom/PerformersSection/PerformersSection.tsx +++ b/src/custom/PerformersSection/PerformersSection.tsx @@ -184,7 +184,7 @@ interface PageArgs { [key: string]: any; } -const withDefaultPageArgs = (args: PageArgs = {}): PageArgs => ({ +export const withDefaultPageArgs = (args: PageArgs = {}): PageArgs => ({ search: args.search ?? '', order: args.order ?? '', pagesize: args.pagesize ?? 0, diff --git a/src/custom/ResponsiveDataTable.tsx b/src/custom/ResponsiveDataTable.tsx index de09a30b..a37a2a4e 100644 --- a/src/custom/ResponsiveDataTable.tsx +++ b/src/custom/ResponsiveDataTable.tsx @@ -4,6 +4,7 @@ import React, { useCallback } from 'react'; import { Checkbox, Collapse, ListItemIcon, ListItemText, Menu, MenuItem } from '../base'; import { ShareIcon } from '../icons'; import { EllipsisIcon } from '../icons/Ellipsis'; +import { ColView } from './Helpers/ResponsiveColumns/responsive-coulmns.tsx'; import TooltipIcon from './TooltipIcon'; export const IconWrapper = styled('div')<{ disabled?: boolean }>(({ disabled = false }) => ({ @@ -283,7 +284,7 @@ export interface ResponsiveDataTableProps { updateCols?: ((columns: Column[]) => void) | undefined; columnVisibility: Record | undefined; theme?: object; - colViews?: Record | undefined; + colViews?: ColView[]; rowsPerPageOptions?: number[] | undefined; backgroundColor?: string; } diff --git a/src/custom/Workspaces/AssignmentModal.tsx b/src/custom/Workspaces/AssignmentModal.tsx index c9aa05c3..e9933125 100644 --- a/src/custom/Workspaces/AssignmentModal.tsx +++ b/src/custom/Workspaces/AssignmentModal.tsx @@ -1,6 +1,6 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { Modal, ModalBody, ModalButtonPrimary, ModalButtonSecondary, ModalFooter } from '../Modal'; import { TransferList } from '../TransferModal/TransferList'; -import { ListItemType } from '../TransferModal/TransferList/TransferList'; import { ModalActionDiv } from './styles'; interface AssignmentModalProps { @@ -9,9 +9,9 @@ interface AssignmentModalProps { title: string; headerIcon: JSX.Element; name: string; - assignableData: ListItemType[]; - handleAssignedData: (data: ListItemType[]) => void; - originalAssignedData: ListItemType[]; + assignableData: any[]; + handleAssignedData: (data: any) => void; + originalAssignedData: any[]; emptyStateIcon: JSX.Element; handleAssignablePage: () => void; handleAssignedPage: () => void; From 3e5764ef5561ab172fb49dc79d97083242953f64 Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Fri, 22 Nov 2024 19:03:51 +0530 Subject: [PATCH 6/7] feat: add DesignTable component for managing workspace designs Signed-off-by: Amit Amrutiya --- src/custom/Workspaces/designs-table.tsx | 276 ++++++++++++++++++++++++ src/custom/Workspaces/index.ts | 3 +- src/custom/Workspaces/styles.ts | 13 ++ src/custom/Workspaces/types.ts | 38 ++++ 4 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 src/custom/Workspaces/designs-table.tsx diff --git a/src/custom/Workspaces/designs-table.tsx b/src/custom/Workspaces/designs-table.tsx new file mode 100644 index 00000000..c719c8cd --- /dev/null +++ b/src/custom/Workspaces/designs-table.tsx @@ -0,0 +1,276 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import _ from 'lodash'; +import React, { useEffect, useRef, useState } from 'react'; +import { Accordion, AccordionDetails, AccordionSummary, Typography } from '../../base'; +import { DesignIcon } from '../../icons'; +import { publishCatalogItemSchema } from '../../schemas'; +import { SistentThemeProvider } from '../../theme'; +import { + CatalogDesignsTable, + createDesignsColumnsConfig, + designColumnsColViews +} from '../CatalogDesignTable'; +import { Pattern } from '../CustomCatalog/CustomCard'; +import { CustomColumnVisibilityControl } from '../CustomColumnVisibilityControl'; +import { useWindowDimensions } from '../Helpers/Dimension'; +import { updateVisibleColumns } from '../Helpers/ResponsiveColumns/responsive-coulmns.tsx/responsive-column'; +import PromptComponent from '../Prompt'; +import AssignmentModal from './AssignmentModal'; +import EditButton from './EditButton'; +import useDesignAssignment from './hooks/useDesignAssignment'; +import { TableHeader, TableRightActionHeader } from './styles'; + +export interface DesignTableProps { + workspaceId: string; + workspaceName: string; + designsOfWorkspace: any; + meshModelModelsData: any; + useGetWorkspaceDesignsQuery: any; + useAssignDesignToWorkspaceMutation: any; + useUnassignDesignFromWorkspaceMutation: any; + handleCopyUrl: (type: string, name: string, id: string) => void; + handleClone: (name: string, id: string) => void; + handleWorkspaceDesignDeleteModal: (designId: string, workspaceId: string) => void; + handleBulkWorkspaceDesignDeleteModal: ( + designs: Pattern[], + modalRef: React.RefObject, + workspaceName: string, + workspaceId: string + ) => void; + handlePublish: (publishModal: PublishModalState, data: any) => void; + publishModalHandler: any; + handleUnpublishModal: (design: Pattern, modalRef: React.RefObject) => void; + handleBulkUnpublishModal: ( + selected: any, + designs: Pattern[], + modalRef: React.RefObject + ) => void; + handleShowDetails: (designId: string, designName: string) => void; + isDownloadDisabled: boolean; + isCopyLinkDisabled: boolean; + isDeleteDisabled: boolean; + isPublishDisabled: boolean; + isUnpublishDisabled: boolean; + GenericRJSFModal: any; + isAssignDisabled: boolean; + isRemoveDisabled: boolean; +} + +export interface PublishModalState { + open: boolean; + pattern: Partial; +} + +export interface ColumnVisibility { + [key: string]: boolean; +} +export interface TableColumn { + name: string; + label: string; + [key: string]: any; +} + +const DesignTable: React.FC = ({ + workspaceId, + workspaceName, + designsOfWorkspace, + meshModelModelsData, + handleBulkUnpublishModal, + handleBulkWorkspaceDesignDeleteModal, + handleClone, + handleCopyUrl, + handlePublish, + handleShowDetails, + handleUnpublishModal, + handleWorkspaceDesignDeleteModal, + publishModalHandler, + isCopyLinkDisabled, + isDeleteDisabled, + isDownloadDisabled, + isPublishDisabled, + isUnpublishDisabled, + useAssignDesignToWorkspaceMutation, + useUnassignDesignFromWorkspaceMutation, + GenericRJSFModal, + isAssignDisabled, + isRemoveDisabled, + useGetWorkspaceDesignsQuery +}) => { + const [publishModal, setPublishModal] = useState({ + open: false, + pattern: {} + }); + const modalRef = useRef(null); + const [page, setPage] = useState(0); + const [pageSize, setPageSize] = useState(10); + const [sortOrder, setSortOrder] = useState(''); + + const handlePublishModal = (pattern: Pattern): void => { + const result = publishModalHandler(pattern); + setPublishModal({ + open: true, + pattern: result + }); + }; + + const columns = createDesignsColumnsConfig({ + handleDeleteModal: (design) => () => handleWorkspaceDesignDeleteModal(design.id, workspaceId), + handlePublishModal, + handleUnpublishModal: (design) => () => handleUnpublishModal(design, modalRef), + handleCopyUrl, + handleClone, + handleShowDetails, + isDownloadDisabled, + isCopyLinkDisabled, + isDeleteDisabled, + isPublishDisabled, + isUnpublishDisabled + }); + + const [publishSchema, setPublishSchema] = useState<{ + rjsfSchema: any; + uiSchema: any; + }>({ + rjsfSchema: {}, + uiSchema: {} + }); + + const { width } = useWindowDimensions(); + const [columnVisibility, setColumnVisibility] = useState(() => { + const showCols = updateVisibleColumns(designColumnsColViews, width); + const initialVisibility: ColumnVisibility = {}; + columns.forEach((col) => { + initialVisibility[col.name] = showCols[col.name]; + }); + return initialVisibility; + }); + + const [expanded, setExpanded] = useState(true); + const handleAccordionChange = () => { + setExpanded(!expanded); + }; + + useEffect(() => { + const fetchSchema = async () => { + const modelNames = _.uniq( + meshModelModelsData?.models?.map((model: any) => model.display_name) + ); + const modifiedSchema = _.set( + _.cloneDeep(publishCatalogItemSchema), + 'properties.compatibility.items.enum', + modelNames + ); + setPublishSchema({ + rjsfSchema: modifiedSchema, + uiSchema: publishCatalogItemSchema + }); + }; + fetchSchema(); + }, [meshModelModelsData]); + + const { + disableTransferButton, + assignModal, + handleAssignModalClose, + handleAssignModal, + assignedItems, + data, + workspaceData, + handleAssignablePage, + handleAssignedPage, + handleAssign, + handleAssignData + } = useDesignAssignment({ + workspaceId, + useAssignDesignToWorkspaceMutation, + useUnassignDesignFromWorkspaceMutation, + useGetDesignsOfWorkspaceQuery: useGetWorkspaceDesignsQuery + }); + + const tableHeaderContent = ( + + + Assigned Designs + + + + + + + ); + + return ( + + + } + sx={{ + backgroundColor: 'background.paper' + }} + > + {tableHeaderContent} + + + + handleBulkWorkspaceDesignDeleteModal(designs, modalRef, workspaceName, workspaceId) + } + filter={'my-designs'} + /> + + + } + name="Designs" + assignableData={data} + handleAssignedData={handleAssignData} + originalAssignedData={workspaceData} + emptyStateIcon={} + handleAssignablePage={handleAssignablePage} + handleAssignedPage={handleAssignedPage} + originalLeftCount={data?.length} + originalRightCount={assignedItems?.length} + onAssign={handleAssign} + disableTransfer={disableTransferButton} + helpText={`Assign Designs to ${workspaceName}`} + isAssignDisabled={isAssignDisabled} + isRemoveDisabled={isRemoveDisabled} + /> + setPublishModal({ open: false, pattern: {} })} + schema={publishSchema?.rjsfSchema} + uiSchema={publishSchema?.uiSchema} + handleSubmit={(data: any) => handlePublish(publishModal, data)} + title={`Publish ${publishModal?.pattern?.name}`} + buttonTitle="Publish" + /> + + + ); +}; + +export default DesignTable; diff --git a/src/custom/Workspaces/index.ts b/src/custom/Workspaces/index.ts index 58a7423a..c079ab4f 100644 --- a/src/custom/Workspaces/index.ts +++ b/src/custom/Workspaces/index.ts @@ -1,3 +1,4 @@ import AssignmentModal from './AssignmentModal'; +import DesignTable from './designs-table'; -export { AssignmentModal }; +export { AssignmentModal, DesignTable }; diff --git a/src/custom/Workspaces/styles.ts b/src/custom/Workspaces/styles.ts index e6917e2a..ab6f0393 100644 --- a/src/custom/Workspaces/styles.ts +++ b/src/custom/Workspaces/styles.ts @@ -32,3 +32,16 @@ export const L5EditIcon = styled(EditIcon)( ...style }) ); + +export const TableHeader = styled('div')({ + display: 'flex', + justifyContent: 'space-between', + width: '100%', + alignItems: 'center' +}); + +export const TableRightActionHeader = styled('div')({ + display: 'flex', + alignItems: 'center', + marginRight: '1rem' +}); diff --git a/src/custom/Workspaces/types.ts b/src/custom/Workspaces/types.ts index e69de29b..a6f5b6af 100644 --- a/src/custom/Workspaces/types.ts +++ b/src/custom/Workspaces/types.ts @@ -0,0 +1,38 @@ +export interface AssignmentHookResult { + data: T[]; + workspaceData: T[]; + assignModal: boolean; + handleAssignModal: (e?: React.MouseEvent) => void; + handleAssignModalClose: (e?: React.MouseEvent) => void; + handleAssignablePage: () => void; + handleAssignedPage: () => void; + handleAssign: () => void; + handleAssignData: (data: T[]) => void; + disableTransferButton: boolean; + assignedItems: T[]; +} + +export interface Workspace { + id: string; + name: string; + description?: string; + created_at: string; + updated_at: string; + deleted_at: { + Valid: boolean; + }; +} + +export interface Environment { + id: string; + name: string; + description?: string; + organization_id: string; + created_at: string; + updated_at: string; +} + +export interface Team { + id: string; + name: string; +} From e797e5e89d2494af0b0a1f8fca1a97fa26bb2200 Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Fri, 22 Nov 2024 19:58:30 +0530 Subject: [PATCH 7/7] feat: introduce EnvironmentTable component and enhance design table functionality Signed-off-by: Amit Amrutiya --- .../{designs-table.tsx => DesignTable.tsx} | 4 +- src/custom/Workspaces/EnvironmentTable.tsx | 300 ++++++++++++++++++ src/custom/Workspaces/TooltipIcon.tsx | 54 ++++ .../hooks/useEnvironmentAssignment.tsx | 156 +++++++++ src/custom/Workspaces/index.ts | 6 +- src/custom/Workspaces/styles.ts | 24 +- src/custom/Workspaces/types.ts | 4 + 7 files changed, 539 insertions(+), 9 deletions(-) rename src/custom/Workspaces/{designs-table.tsx => DesignTable.tsx} (99%) create mode 100644 src/custom/Workspaces/EnvironmentTable.tsx create mode 100644 src/custom/Workspaces/TooltipIcon.tsx create mode 100644 src/custom/Workspaces/hooks/useEnvironmentAssignment.tsx diff --git a/src/custom/Workspaces/designs-table.tsx b/src/custom/Workspaces/DesignTable.tsx similarity index 99% rename from src/custom/Workspaces/designs-table.tsx rename to src/custom/Workspaces/DesignTable.tsx index c719c8cd..7d60636b 100644 --- a/src/custom/Workspaces/designs-table.tsx +++ b/src/custom/Workspaces/DesignTable.tsx @@ -20,6 +20,7 @@ import AssignmentModal from './AssignmentModal'; import EditButton from './EditButton'; import useDesignAssignment from './hooks/useDesignAssignment'; import { TableHeader, TableRightActionHeader } from './styles'; +import { ColumnVisibility } from './types'; export interface DesignTableProps { workspaceId: string; @@ -62,9 +63,6 @@ export interface PublishModalState { pattern: Partial; } -export interface ColumnVisibility { - [key: string]: boolean; -} export interface TableColumn { name: string; label: string; diff --git a/src/custom/Workspaces/EnvironmentTable.tsx b/src/custom/Workspaces/EnvironmentTable.tsx new file mode 100644 index 00000000..08279576 --- /dev/null +++ b/src/custom/Workspaces/EnvironmentTable.tsx @@ -0,0 +1,300 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { MUIDataTableMeta } from 'mui-datatables'; +import React, { useState } from 'react'; +import { Accordion, AccordionDetails, AccordionSummary, Typography } from '../../base'; +import { DeleteIcon, EnvironmentIcon } from '../../icons'; +import { CHARCOAL, SistentThemeProvider } from '../../theme'; +import { CustomColumnVisibilityControl } from '../CustomColumnVisibilityControl'; +import { CustomTooltip } from '../CustomTooltip'; +import { ConditionalTooltip } from '../Helpers/CondtionalTooltip'; +import { useWindowDimensions } from '../Helpers/Dimension'; +import { + ColView, + updateVisibleColumns +} from '../Helpers/ResponsiveColumns/responsive-coulmns.tsx/responsive-column'; +import ResponsiveDataTable, { IconWrapper } from '../ResponsiveDataTable'; +import AssignmentModal from './AssignmentModal'; +import EditButton from './EditButton'; +import TooltipIcon from './TooltipIcon'; +import useEnvironmentAssignment from './hooks/useEnvironmentAssignment'; +import { CellStyle, CustomBodyRenderStyle, TableHeader, TableRightActionHeader } from './styles'; +import { ColumnVisibility } from './types'; + +interface EnvironmentTableProps { + workspaceId: string; + workspaceName: string; + useGetEnvironmentsOfWorkspaceQuery: any; + useUnassignEnvironmentFromWorkspaceMutation: any; + useAssignEnvironmentToWorkspaceMutation: any; + isRemoveDisabled: boolean; + isAssignDisabled: boolean; +} + +const colViews: ColView[] = [ + ['id', 'na'], + ['name', 'xs'], + ['description', 'm'], + ['organization_id', 'l'], + ['created_at', 'na'], + ['updated_at', 'xl'], + ['actions', 'xs'] +]; + +export const ResizableDescriptionCell = ({ value }: { value: string }) => ( +
+ + + + {value} + + + +
+); + +const EnvironmentTable: React.FC = ({ + workspaceId, + workspaceName, + isRemoveDisabled, + useGetEnvironmentsOfWorkspaceQuery, + useUnassignEnvironmentFromWorkspaceMutation, + useAssignEnvironmentToWorkspaceMutation, + isAssignDisabled +}) => { + const [expanded, setExpanded] = useState(true); + const handleAccordionChange = () => { + setExpanded(!expanded); + }; + const [page, setPage] = useState(0); + const [pageSize, setPageSize] = useState(10); + const [sortOrder, setSortOrder] = useState(''); + const { data: environmentsOfWorkspace } = useGetEnvironmentsOfWorkspaceQuery({ + workspaceId, + page: page, + pageSize: pageSize, + order: sortOrder + }); + const { width } = useWindowDimensions(); + const [unassignEnvironmentFromWorkspace] = useUnassignEnvironmentFromWorkspaceMutation(); + const columns: any[] = [ + { + name: 'id', + label: 'ID', + options: { + filter: false, + customBodyRender: (value: string) => + } + }, + { + name: 'name', + label: 'Name', + options: { + filter: false, + sort: true, + searchable: true, + customBodyRender: (value: string) => + } + }, + { + name: 'organization_id', + label: 'Organization ID', + options: { + filter: false, + sort: false, + searchable: false + } + }, + + { + name: 'description', + label: 'Description', + options: { + filter: false, + sort: true, + searchable: true, + customBodyRender: (value: string) => + } + }, + { + name: 'created_at', + label: 'Created At', + options: { + filter: false, + sort: true, + searchable: true, + setCellHeaderProps: () => { + return { align: 'center' }; + } + } + }, + { + name: 'updated_at', + label: 'Updated At', + options: { + filter: false, + sort: true, + searchable: true, + setCellHeaderProps: () => { + return { align: 'center' }; + } + } + }, + { + name: 'actions', + label: 'Actions', + options: { + filter: false, + sort: false, + searchable: false, + customBodyRender: (_: any, tableMeta: MUIDataTableMeta) => ( + + { + !isRemoveDisabled && + unassignEnvironmentFromWorkspace({ + workspaceId, + environmentId: tableMeta.rowData[0] + }); + }} + iconType="delete" + > + + + + ) + } + } + ]; + + const environmentAssignment = useEnvironmentAssignment({ + workspaceId, + useGetEnvironmentsOfWorkspaceQuery, + useUnassignEnvironmentFromWorkspaceMutation, + useAssignEnvironmentToWorkspaceMutation + }); + + const [columnVisibility, setColumnVisibility] = useState(() => { + const showCols = updateVisibleColumns(colViews, width); + const initialVisibility: ColumnVisibility = {}; + columns.forEach((col) => { + initialVisibility[col.name] = showCols[col.name]; + }); + return initialVisibility; + }); + + const options = { + filter: false, + responsive: 'standard', + selectableRows: 'none', + count: environmentsOfWorkspace?.total_count, + rowsPerPage: pageSize, + page, + elevation: 0, + serverSide: true, + onTableChange: (action: string, tableState: any) => { + const sortInfo = tableState.announceText ? tableState.announceText.split(' : ') : []; + let order = ''; + if (tableState.activeColumn) { + order = `${columns[tableState.activeColumn].name} desc`; + } + + switch (action) { + case 'changePage': + setPage(tableState.page); + break; + case 'changeRowsPerPage': + setPageSize(tableState.rowsPerPage); + break; + case 'sort': + if (sortInfo.length == 2) { + if (sortInfo[1] === 'ascending') { + order = `${columns[tableState.activeColumn].name} asc`; + } else { + order = `${columns[tableState.activeColumn].name} desc`; + } + } + if (order !== sortOrder) { + setSortOrder(order); + } + break; + } + } + }; + const [tableCols, updateCols] = useState(columns); + + return ( + + + } + sx={{ + backgroundColor: 'background.paper' + }} + > + + + Assigned Environments + + + + + + + + + + + + + } + name="Environments" + assignableData={environmentAssignment.data} + handleAssignedData={environmentAssignment.handleAssignData} + originalAssignedData={environmentAssignment.workspaceData} + emptyStateIcon={} + handleAssignablePage={environmentAssignment.handleAssignablePage} + handleAssignedPage={environmentAssignment.handleAssignedPage} + originalLeftCount={environmentAssignment.data?.length || 0} + originalRightCount={environmentsOfWorkspace?.total_count || 0} + onAssign={environmentAssignment.handleAssign} + disableTransfer={environmentAssignment.disableTransferButton} + helpText={`Assign Environments to ${workspaceName}`} + isAssignDisabled={!isAssignDisabled} + isRemoveDisabled={!isRemoveDisabled} + /> + + ); +}; + +export default EnvironmentTable; diff --git a/src/custom/Workspaces/TooltipIcon.tsx b/src/custom/Workspaces/TooltipIcon.tsx new file mode 100644 index 00000000..6b387b9a --- /dev/null +++ b/src/custom/Workspaces/TooltipIcon.tsx @@ -0,0 +1,54 @@ +import { IconButton } from '@mui/material'; +import { useTheme } from '../../theme'; +import { CustomTooltip } from '../CustomTooltip'; +import { IconWrapper } from '../ResponsiveDataTable'; + +interface TooltipIconProps { + children: React.ReactNode; + onClick: (event: React.MouseEvent) => void; + title: string; + iconType: string; + id: string; + style?: React.CSSProperties; + placement?: 'bottom' | 'top' | 'left' | 'right'; + disabled?: boolean; +} + +const TooltipIcon: React.FC = ({ + children, + onClick, + title, + iconType, + id, + style, + placement, + disabled = false +}) => { + const theme = useTheme(); + return ( + + + + {children} + + + + ); +}; + +export default TooltipIcon; diff --git a/src/custom/Workspaces/hooks/useEnvironmentAssignment.tsx b/src/custom/Workspaces/hooks/useEnvironmentAssignment.tsx new file mode 100644 index 00000000..fb346db7 --- /dev/null +++ b/src/custom/Workspaces/hooks/useEnvironmentAssignment.tsx @@ -0,0 +1,156 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { useEffect, useState } from 'react'; +import { withDefaultPageArgs } from '../../PerformersSection/PerformersSection'; +import { AssignmentHookResult, Environment } from '../types'; + +interface UseEnvironmentAssignmentProps { + workspaceId: string; + useGetEnvironmentsOfWorkspaceQuery: any; + useAssignEnvironmentToWorkspaceMutation: any; + useUnassignEnvironmentFromWorkspaceMutation: any; +} + +const useEnvironmentAssignment = ({ + workspaceId, + useGetEnvironmentsOfWorkspaceQuery, + useAssignEnvironmentToWorkspaceMutation, + useUnassignEnvironmentFromWorkspaceMutation +}: UseEnvironmentAssignmentProps): AssignmentHookResult => { + const [environmentsPage, setEnvironmentsPage] = useState(0); + const [environmentsData, setEnvironmentsData] = useState([]); + const environmentsPageSize = 25; + const [environmentsOfWorkspacePage, setEnvironmentsOfWorkspacePage] = useState(0); + const [workspaceEnvironmentsData, setWorkspaceEnvironmentsData] = useState([]); + const [assignEnvironmentModal, setAssignEnvironmentModal] = useState(false); + const [skipEnvironments, setSkipEnvironments] = useState(true); + + const { data: environments } = useGetEnvironmentsOfWorkspaceQuery( + withDefaultPageArgs({ + workspaceId, + page: environmentsPage, + pagesize: environmentsPageSize, + filter: '{"assigned":false}' + }), + { + skip: skipEnvironments + } + ); + + const { data: environmentsOfWorkspace } = useGetEnvironmentsOfWorkspaceQuery( + withDefaultPageArgs({ + workspaceId, + page: environmentsOfWorkspacePage, + pagesize: environmentsPageSize + }), + { + skip: skipEnvironments + } + ); + + const [assignEnvironmentToWorkspace] = useAssignEnvironmentToWorkspaceMutation(); + const [unassignEnvironmentFromWorkspace] = useUnassignEnvironmentFromWorkspaceMutation(); + const [disableTransferButton, setDisableTransferButton] = useState(true); + const [assignedEnvironments, setAssignedEnvironments] = useState([]); + + useEffect(() => { + const environmentsDataRtk = environments?.environments ? environments.environments : []; + setEnvironmentsData((prevData) => [...prevData, ...environmentsDataRtk]); + }, [environments]); + + useEffect(() => { + const environmentsOfWorkspaceDataRtk = environmentsOfWorkspace?.environments + ? environmentsOfWorkspace.environments + : []; + setWorkspaceEnvironmentsData((prevData) => [...prevData, ...environmentsOfWorkspaceDataRtk]); + }, [environmentsOfWorkspace]); + + const handleAssignEnvironmentModal = (e?: React.MouseEvent) => { + e?.stopPropagation(); + setAssignEnvironmentModal(true); + setSkipEnvironments(false); + }; + + const handleAssignEnvironmentModalClose = (e?: React.MouseEvent) => { + e?.stopPropagation(); + setAssignEnvironmentModal(false); + setSkipEnvironments(true); + }; + + const handleAssignablePageEnvironment = () => { + const pagesCount = Math.ceil(Number(environments?.total_count) / environmentsPageSize); + if (environmentsPage < pagesCount - 1) { + setEnvironmentsPage((prevEnvironmentsPage) => prevEnvironmentsPage + 1); + } + }; + + const handleAssignedPageEnvironment = () => { + const pagesCount = Math.ceil( + Number(environmentsOfWorkspace?.total_count) / environmentsPageSize + ); + if (environmentsOfWorkspacePage < pagesCount - 1) { + setEnvironmentsOfWorkspacePage((prevPage) => prevPage + 1); + } + }; + + const handleAssignEnvironments = async () => { + const { addedEnvironmentsIds, removedEnvironmentsIds } = + getAddedAndRemovedEnvironments(assignedEnvironments); + + for (const id of addedEnvironmentsIds) { + await assignEnvironmentToWorkspace({ + workspaceId, + environmentId: id + }).unwrap(); + } + + for (const id of removedEnvironmentsIds) { + await unassignEnvironmentFromWorkspace({ + workspaceId, + environmentId: id + }).unwrap(); + } + + setEnvironmentsData([]); + setWorkspaceEnvironmentsData([]); + handleAssignEnvironmentModalClose(); + }; + + const getAddedAndRemovedEnvironments = (allAssignedEnvironments: Environment[]) => { + const originalEnvironmentsIds = workspaceEnvironmentsData.map((env) => env.id); + const updatedEnvironmentsIds = allAssignedEnvironments.map((env) => env.id); + + const addedEnvironmentsIds = updatedEnvironmentsIds.filter( + (id) => !originalEnvironmentsIds.includes(id) + ); + const removedEnvironmentsIds = originalEnvironmentsIds.filter( + (id) => !updatedEnvironmentsIds.includes(id) + ); + + return { addedEnvironmentsIds, removedEnvironmentsIds }; + }; + + const handleAssignEnvironmentsData = (updatedAssignedData: Environment[]) => { + const { addedEnvironmentsIds, removedEnvironmentsIds } = + getAddedAndRemovedEnvironments(updatedAssignedData); + addedEnvironmentsIds.length > 0 || removedEnvironmentsIds.length > 0 + ? setDisableTransferButton(false) + : setDisableTransferButton(true); + setAssignedEnvironments(updatedAssignedData); + }; + + return { + data: environmentsData, + workspaceData: workspaceEnvironmentsData, + assignModal: assignEnvironmentModal, + handleAssignModal: handleAssignEnvironmentModal, + handleAssignModalClose: handleAssignEnvironmentModalClose, + handleAssignablePage: handleAssignablePageEnvironment, + handleAssignedPage: handleAssignedPageEnvironment, + handleAssign: handleAssignEnvironments, + handleAssignData: handleAssignEnvironmentsData, + disableTransferButton, + assignedItems: assignedEnvironments + }; +}; + +export default useEnvironmentAssignment; diff --git a/src/custom/Workspaces/index.ts b/src/custom/Workspaces/index.ts index c079ab4f..4294ab26 100644 --- a/src/custom/Workspaces/index.ts +++ b/src/custom/Workspaces/index.ts @@ -1,4 +1,4 @@ import AssignmentModal from './AssignmentModal'; -import DesignTable from './designs-table'; - -export { AssignmentModal, DesignTable }; +import DesignTable from './DesignTable'; +import EnvironmentTable from './EnvironmentTable'; +export { AssignmentModal, DesignTable, EnvironmentTable }; diff --git a/src/custom/Workspaces/styles.ts b/src/custom/Workspaces/styles.ts index ab6f0393..92ac5238 100644 --- a/src/custom/Workspaces/styles.ts +++ b/src/custom/Workspaces/styles.ts @@ -1,6 +1,6 @@ import EditIcon from '@mui/icons-material/Edit'; import { buttonDisabled, styled } from '../../theme'; -import { HOVER_DELETE } from '../../theme/colors/colors'; +import { KEPPEL } from '../../theme/colors/colors'; export const ModalActionDiv = styled('div')({ display: 'flex', @@ -20,9 +20,9 @@ export const L5EditIcon = styled(EditIcon)( width: bulk ? '32' : '28.8', height: bulk ? '32' : '28.8', '&:hover': { - color: disabled ? buttonDisabled : HOVER_DELETE, + color: disabled ? buttonDisabled : KEPPEL, '& svg': { - color: disabled ? buttonDisabled : HOVER_DELETE + color: disabled ? buttonDisabled : KEPPEL } }, '& svg': { @@ -45,3 +45,21 @@ export const TableRightActionHeader = styled('div')({ alignItems: 'center', marginRight: '1rem' }); + +export const CellStyle = styled('div')({ + boxSizing: 'border-box', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap' +}); + +export const CustomBodyRenderStyle = styled('div')({ + position: 'absolute', + top: 0, + right: 0, + bottom: 0, + left: 0, + boxSizing: 'border-box', + display: 'block', + width: '100%' +}); diff --git a/src/custom/Workspaces/types.ts b/src/custom/Workspaces/types.ts index a6f5b6af..ad391f76 100644 --- a/src/custom/Workspaces/types.ts +++ b/src/custom/Workspaces/types.ts @@ -32,6 +32,10 @@ export interface Environment { updated_at: string; } +export interface ColumnVisibility { + [key: string]: boolean; +} + export interface Team { id: string; name: string;