From 188aaf4b3d2e25eed95bed0c65c74fe38ac52a73 Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Fri, 22 Nov 2024 16:37:31 +0530 Subject: [PATCH 01/19] 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 02/19] 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 03/19] 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 04/19] 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 05/19] 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 06/19] 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 07/19] 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; From 4194842329dc0da6a32e40c42f36246d73dcd9f4 Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Mon, 25 Nov 2024 14:46:36 +0530 Subject: [PATCH 08/19] feat: add LogoutIcon and TeamsIcon components with customizable properties Signed-off-by: Amit Amrutiya --- src/icons/Delete/DeleteIcon.tsx | 4 +++ src/icons/Logout/LogOutIcon.tsx | 31 +++++++++++++++++++++++ src/icons/Logout/index.ts | 3 +++ src/icons/Teams/TeamsIcon.tsx | 44 +++++++++++++++++++++++++++++++++ src/icons/Teams/index.ts | 3 +++ src/icons/index.ts | 4 +++ 6 files changed, 89 insertions(+) create mode 100644 src/icons/Logout/LogOutIcon.tsx create mode 100644 src/icons/Logout/index.ts create mode 100644 src/icons/Teams/TeamsIcon.tsx create mode 100644 src/icons/Teams/index.ts diff --git a/src/icons/Delete/DeleteIcon.tsx b/src/icons/Delete/DeleteIcon.tsx index e3751e3b..31d3a550 100644 --- a/src/icons/Delete/DeleteIcon.tsx +++ b/src/icons/Delete/DeleteIcon.tsx @@ -4,6 +4,8 @@ import { IconProps } from '../types'; export const DeleteIcon = ({ width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT, + fill = '#455a64', + style, ...props }: IconProps): JSX.Element => { return ( @@ -12,6 +14,8 @@ export const DeleteIcon = ({ viewBox="0 0 24 24" width={width} height={height} + fill={fill} + style={style} {...props} > diff --git a/src/icons/Logout/LogOutIcon.tsx b/src/icons/Logout/LogOutIcon.tsx new file mode 100644 index 00000000..695e1c76 --- /dev/null +++ b/src/icons/Logout/LogOutIcon.tsx @@ -0,0 +1,31 @@ +import React from 'react'; + +interface LogoutIconProps { + width?: number; + height?: number; + fill?: string; + style?: React.CSSProperties; + secondaryFill?: string; +} + +const LogoutIcon: React.FC = ({ + width = 24, + height = 24, + fill, + style = {}, + secondaryFill = '#00B39F' +}) => ( + + + + +); + +export default LogoutIcon; diff --git a/src/icons/Logout/index.ts b/src/icons/Logout/index.ts new file mode 100644 index 00000000..e6831ee7 --- /dev/null +++ b/src/icons/Logout/index.ts @@ -0,0 +1,3 @@ +import LogoutIcon from './LogOutIcon'; + +export { LogoutIcon }; diff --git a/src/icons/Teams/TeamsIcon.tsx b/src/icons/Teams/TeamsIcon.tsx new file mode 100644 index 00000000..94fdb16f --- /dev/null +++ b/src/icons/Teams/TeamsIcon.tsx @@ -0,0 +1,44 @@ +import React from 'react'; + +interface TeamsIconProps { + width: string | number; + height: string | number; + fill: string; + primaryFill?: string; + secondaryFill?: string; + style?: React.CSSProperties; +} + +const TeamsIcon: React.FC = ({ + width, + height, + fill, + primaryFill = '#51636B', + secondaryFill = '#00B39F', + style = {} +}) => ( + + + + + +); + +export default TeamsIcon; diff --git a/src/icons/Teams/index.ts b/src/icons/Teams/index.ts new file mode 100644 index 00000000..9f0b15b6 --- /dev/null +++ b/src/icons/Teams/index.ts @@ -0,0 +1,3 @@ +import TeamsIcon from './TeamsIcon'; + +export { TeamsIcon }; diff --git a/src/icons/index.ts b/src/icons/index.ts index 1f0b66b8..73c79551 100644 --- a/src/icons/index.ts +++ b/src/icons/index.ts @@ -53,6 +53,8 @@ export * from './EmptyStyle'; export * from './Environment'; export * from './ExternalLink'; export * from './Feedback'; +export * from './Github'; +export * from './Google'; export * from './GridView'; export * from './HelpIcon'; export * from './Idea'; @@ -62,6 +64,7 @@ export * from './Kubernetes'; export * from './Learning'; export * from './LeftAngledArrow'; export * from './LeftArrow'; +export * from './Logout'; export * from './Menu'; export * from './MesheryFilter'; export * from './MesheryOperator'; @@ -90,6 +93,7 @@ export * from './SocialMedial'; export * from './Star'; export * from './Success'; export * from './TableView'; +export * from './Teams'; export * from './TerminalIcon'; export * from './Toolkit'; export * from './Touch'; From bfc74c30268a596f00aad853d316de1a717fae4d Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Sat, 23 Nov 2024 14:47:08 +0530 Subject: [PATCH 09/19] feat: add GithubIcon and GoogleIcon components with customizable properties Signed-off-by: Amit Amrutiya --- src/icons/Github/GithubIcon.tsx | 33 +++++++++++++++++ src/icons/Github/index.ts | 3 ++ src/icons/Google/GoogleIcon.tsx | 63 +++++++++++++++++++++++++++++++++ src/icons/Google/index.ts | 3 ++ 4 files changed, 102 insertions(+) create mode 100644 src/icons/Github/GithubIcon.tsx create mode 100644 src/icons/Github/index.ts create mode 100644 src/icons/Google/GoogleIcon.tsx create mode 100644 src/icons/Google/index.ts diff --git a/src/icons/Github/GithubIcon.tsx b/src/icons/Github/GithubIcon.tsx new file mode 100644 index 00000000..d5ada581 --- /dev/null +++ b/src/icons/Github/GithubIcon.tsx @@ -0,0 +1,33 @@ +interface GithubIconProps { + height?: string | number; + width?: string | number; + className?: string; +} + +export default function GithubIcon({ + height = '1.45rem', + width = '1.45rem', + className +}: GithubIconProps) { + return ( + + + + ); +} diff --git a/src/icons/Github/index.ts b/src/icons/Github/index.ts new file mode 100644 index 00000000..b9e50f27 --- /dev/null +++ b/src/icons/Github/index.ts @@ -0,0 +1,3 @@ +import GithubIcon from './GithubIcon'; + +export { GithubIcon }; diff --git a/src/icons/Google/GoogleIcon.tsx b/src/icons/Google/GoogleIcon.tsx new file mode 100644 index 00000000..cf669a8b --- /dev/null +++ b/src/icons/Google/GoogleIcon.tsx @@ -0,0 +1,63 @@ +import React from 'react'; + +interface GoogleIconProps { + height: number; + width: number; + style?: React.CSSProperties; +} + +const GoogleIcon: React.FC = ({ height, width, style }) => { + return ( + + + + + + + + + + + + ); +}; + +export default GoogleIcon; diff --git a/src/icons/Google/index.ts b/src/icons/Google/index.ts new file mode 100644 index 00000000..e3327269 --- /dev/null +++ b/src/icons/Google/index.ts @@ -0,0 +1,3 @@ +import GoogleIcon from './GoogleIcon'; + +export { GoogleIcon }; From 7d13fcfe023e85393d92e4e25338de6be8355d53 Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Sat, 23 Nov 2024 14:47:34 +0530 Subject: [PATCH 10/19] feat: enhance TooltipIcon component with additional props and improved styling Signed-off-by: Amit Amrutiya --- src/custom/TooltipIcon.tsx | 56 +++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/src/custom/TooltipIcon.tsx b/src/custom/TooltipIcon.tsx index 87477e8b..4f699196 100644 --- a/src/custom/TooltipIcon.tsx +++ b/src/custom/TooltipIcon.tsx @@ -1,40 +1,58 @@ -import { SxProps } from '@mui/material/styles'; +import { IconButton } from '@mui/material'; import React from 'react'; -import { IconButton } from '../base/IconButton'; +import { useTheme } from '../theme'; import { CustomTooltip } from './CustomTooltip'; +import { IconWrapper } from './ResponsiveDataTable'; interface TooltipIconProps { title: string; onClick?: (event: React.MouseEvent) => void; - icon: React.ReactNode; + icon?: React.ReactNode; arrow?: boolean; style?: React.CSSProperties; + iconType?: string; + id?: string; + placement?: 'bottom' | 'top' | 'left' | 'right'; + disabled?: boolean; + children?: React.ReactNode; } export function TooltipIcon({ + children, title, onClick, icon, style, - arrow = false + arrow = false, + disabled = false, + iconType, + id, + placement }: TooltipIconProps): JSX.Element { + const theme = useTheme(); + return ( - - + + - {icon} - + ...style + }} + disableRipple + > + {icon || children} + + ); } From 72326bc23eefc14217df91f009d4ba0a18aa5e0c Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Sat, 23 Nov 2024 14:48:48 +0530 Subject: [PATCH 11/19] feat: enhance CatalogDesignTable with search functionality and improved column permissions Signed-off-by: Amit Amrutiya --- .../CatalogDesignTable/CatalogDesignTable.tsx | 12 +++++- .../DesignTableColumnConfig.tsx | 37 +++++++++++-------- .../CustomColumnVisibilityControl.tsx | 2 +- src/custom/ResponsiveDataTable.tsx | 2 +- src/custom/SearchBar.tsx | 15 +++++--- src/custom/UniversalFilter.tsx | 2 +- 6 files changed, 44 insertions(+), 26 deletions(-) diff --git a/src/custom/CatalogDesignTable/CatalogDesignTable.tsx b/src/custom/CatalogDesignTable/CatalogDesignTable.tsx index bfe48242..9f5d11cc 100644 --- a/src/custom/CatalogDesignTable/CatalogDesignTable.tsx +++ b/src/custom/CatalogDesignTable/CatalogDesignTable.tsx @@ -25,6 +25,7 @@ interface CatalogDesignsTableProps { columnVisibility: Record; colViews: ColView[]; handleBulkDeleteModal: (patterns: Pattern[], modalRef: React.RefObject) => void; + setSearch?: (search: string) => void; handleBulkpatternsDataUnpublishModal: ( selected: any, patterns: Pattern[], @@ -46,6 +47,7 @@ export const CatalogDesignsTable: React.FC = ({ columnVisibility = {}, colViews = [], handleBulkDeleteModal, + setSearch, handleBulkpatternsDataUnpublishModal }) => { const theme = useTheme(); @@ -106,6 +108,9 @@ export const CatalogDesignsTable: React.FC = ({ case 'changeRowsPerPage': setPageSize(tableState.rowsPerPage); break; + case 'search': + setSearch && setSearch(tableState.searchText !== null ? tableState.searchText : ''); + break; case 'sort': if ( sortInfo.length === 2 && @@ -124,7 +129,7 @@ export const CatalogDesignsTable: React.FC = ({ break; } }, - [columns, setPage, setPageSize, setSortOrder, sortOrder] + [columns, setPage, setSearch, setPageSize, setSortOrder, sortOrder] ); const options = useMemo( @@ -137,6 +142,11 @@ export const CatalogDesignsTable: React.FC = ({ rowsPerPage: pageSize, page, elevation: 0, + sortOrder: { + name: 'updated_at At', + direction: 'desc' + }, + onTableChange: handleTableChange, customToolbarSelect: _.isNil(filter) ? (selected: any) => ( diff --git a/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx b/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx index af3ebe5a..c39da3df 100644 --- a/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx +++ b/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx @@ -25,11 +25,14 @@ interface ColumnConfigProps { 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; + isDownloadAllowed: boolean; + isCopyLinkAllowed: boolean; + isDeleteAllowed: boolean; + isPublishAllowed: boolean; + isUnpublishAllowed: boolean; + // for workspace designs table page only + isFromWorkspaceTable?: boolean; + isRemoveAllowed?: boolean; } export const colViews: ColView[] = [ @@ -49,11 +52,13 @@ export const createDesignsColumnsConfig = ({ handleCopyUrl, handleClone, handleShowDetails, - isUnpublishDisabled, - isCopyLinkDisabled, - isDeleteDisabled, - isPublishDisabled, - isDownloadDisabled + isUnpublishAllowed, + isCopyLinkAllowed, + isDeleteAllowed, + isPublishAllowed, + isDownloadAllowed, + isRemoveAllowed, + isFromWorkspaceTable = false }: ColumnConfigProps): MUIDataTableColumn[] => { return [ { @@ -153,12 +158,12 @@ export const createDesignsColumnsConfig = ({ { title: 'Download', onClick: () => downloadYaml(rowData?.pattern_file, rowData?.name), - disabled: isDownloadDisabled, + disabled: !isDownloadAllowed, icon: }, { title: 'Copy Link', - disabled: rowData.visibility === 'private' || isCopyLinkDisabled, + disabled: rowData.visibility === 'private' || !isCopyLinkAllowed, onClick: () => { handleCopyUrl(RESOURCE_TYPES.DESIGNS, rowData?.name, rowData?.id); }, @@ -179,8 +184,8 @@ export const createDesignsColumnsConfig = ({ icon: }, { - title: 'Delete', - disabled: isDeleteDisabled, + title: isFromWorkspaceTable ? 'Remove Design' : 'Delete', + disabled: isFromWorkspaceTable ? !isRemoveAllowed : !isDeleteAllowed, onClick: () => handleDeleteModal(rowData)(), icon: } @@ -188,7 +193,7 @@ export const createDesignsColumnsConfig = ({ const publishAction = { title: 'Publish', - disabled: isPublishDisabled, + disabled: !isPublishAllowed, onClick: () => handlePublishModal(rowData), icon: }; @@ -196,7 +201,7 @@ export const createDesignsColumnsConfig = ({ const unpublishAction = { title: 'Unpublish', onClick: () => handleUnpublishModal(rowData)(), - disabled: isUnpublishDisabled, + disabled: !isUnpublishAllowed, icon: }; diff --git a/src/custom/CustomColumnVisibilityControl/CustomColumnVisibilityControl.tsx b/src/custom/CustomColumnVisibilityControl/CustomColumnVisibilityControl.tsx index c50eb4bd..bb420e38 100644 --- a/src/custom/CustomColumnVisibilityControl/CustomColumnVisibilityControl.tsx +++ b/src/custom/CustomColumnVisibilityControl/CustomColumnVisibilityControl.tsx @@ -8,7 +8,7 @@ import { FormControlLabel } from '../../base/FormControlLabel'; import { ColumnIcon } from '../../icons'; import { useTheme } from '../../theme'; import PopperListener from '../PopperListener'; -import TooltipIcon from '../TooltipIcon'; +import { TooltipIcon } from '../TooltipIconButton'; export interface CustomColumnVisibilityControlProps { columns: MUIDataTableColumn[]; diff --git a/src/custom/ResponsiveDataTable.tsx b/src/custom/ResponsiveDataTable.tsx index a37a2a4e..d6fad434 100644 --- a/src/custom/ResponsiveDataTable.tsx +++ b/src/custom/ResponsiveDataTable.tsx @@ -5,7 +5,7 @@ import { Checkbox, Collapse, ListItemIcon, ListItemText, Menu, MenuItem } from ' import { ShareIcon } from '../icons'; import { EllipsisIcon } from '../icons/Ellipsis'; import { ColView } from './Helpers/ResponsiveColumns/responsive-coulmns.tsx'; -import TooltipIcon from './TooltipIcon'; +import { TooltipIcon } from './TooltipIconButton'; export const IconWrapper = styled('div')<{ disabled?: boolean }>(({ disabled = false }) => ({ cursor: disabled ? 'not-allowed' : 'pointer', diff --git a/src/custom/SearchBar.tsx b/src/custom/SearchBar.tsx index 8b7f2734..23411c2d 100644 --- a/src/custom/SearchBar.tsx +++ b/src/custom/SearchBar.tsx @@ -7,7 +7,7 @@ import { ClickAwayListener } from '../base/ClickAwayListener'; import { TextField } from '../base/TextField'; import { CloseIcon, SearchIcon } from '../icons'; import { useTheme } from '../theme'; -import TooltipIcon from './TooltipIcon'; +import { TooltipIcon } from './TooltipIconButton'; const customTheme = (theme: Theme) => createTheme({ @@ -92,7 +92,8 @@ function SearchBar({ debouncedOnSearch(value); }; - const handleClearIconClick = (): void => { + const handleClearIconClick = (e: React.MouseEvent): void => { + e.stopPropagation(); debouncedOnSearch(''); setSearchText(''); setExpanded(false); @@ -101,7 +102,8 @@ function SearchBar({ } }; - const handleSearchIconClick = (): void => { + const handleSearchIconClick = (e: React.MouseEvent): void => { + e.stopPropagation(); if (expanded) { debouncedOnSearch(''); setSearchText(''); @@ -119,17 +121,18 @@ function SearchBar({ return ( { + event.stopPropagation(); const isTable = (event.target as HTMLElement)?.closest('#ref'); if (searchText !== '') { return; } if (isTable) { - handleClearIconClick(); // Close the search bar as needed + handleClearIconClick(event as unknown as React.MouseEvent); } }} > -
+ <> )} -
+
); } diff --git a/src/custom/UniversalFilter.tsx b/src/custom/UniversalFilter.tsx index 02a8213f..a1347417 100644 --- a/src/custom/UniversalFilter.tsx +++ b/src/custom/UniversalFilter.tsx @@ -9,7 +9,7 @@ import { Select } from '../base/Select'; import { FilterIcon } from '../icons'; import { useTheme } from '../theme'; import PopperListener from './PopperListener'; -import TooltipIcon from './TooltipIcon'; +import { TooltipIcon } from './TooltipIconButton'; export interface FilterColumn { name: string; From f74e18d0d71ec6497e065a4f4dfeefa136407206 Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Sat, 23 Nov 2024 14:49:10 +0530 Subject: [PATCH 12/19] feat: add FormatId component for displaying and copying truncated IDs Signed-off-by: Amit Amrutiya --- src/custom/FormatId/FormatId.tsx | 50 ++++++++++++++++++++++++++++++++ src/custom/FormatId/index.ts | 3 ++ 2 files changed, 53 insertions(+) create mode 100644 src/custom/FormatId/FormatId.tsx create mode 100644 src/custom/FormatId/index.ts diff --git a/src/custom/FormatId/FormatId.tsx b/src/custom/FormatId/FormatId.tsx new file mode 100644 index 00000000..2999676f --- /dev/null +++ b/src/custom/FormatId/FormatId.tsx @@ -0,0 +1,50 @@ +import { Box, Tooltip, Typography } from '@mui/material'; +import _ from 'lodash'; +import React, { useState } from 'react'; +import { CopyIcon } from '../../icons'; +import { useTheme } from '../../theme'; + +interface FormatIdProps { + id: string; +} + +export const FormatId: React.FC = ({ id }) => { + const [copied, setCopied] = useState(false); + const theme = useTheme(); + + // Truncates the id to 15 characters and adds an ellipsis and adds a clipboard copy button + const copyToClipboard = (e: React.MouseEvent) => { + e.stopPropagation(); + navigator.clipboard.writeText(id); + setCopied(true); + + // Reset the copied status after a brief delay + setTimeout(() => { + setCopied(false); + }, 2000); + }; + + const truncatedId = _.truncate(id, { length: 15 }); + + return ( + + + + {truncatedId} + + + +
+ +
+
+
+ ); +}; diff --git a/src/custom/FormatId/index.ts b/src/custom/FormatId/index.ts new file mode 100644 index 00000000..693f1cc6 --- /dev/null +++ b/src/custom/FormatId/index.ts @@ -0,0 +1,3 @@ +import { FormatId } from './FormatId'; + +export { FormatId }; From 00225a97f6e8814869e6583a3cb23cb94453cfea Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Sun, 24 Nov 2024 14:49:39 +0530 Subject: [PATCH 13/19] feat: add TeamTable component for managing team members with expandable rows Signed-off-by: Amit Amrutiya --- src/custom/TeamTable/TeamTable.tsx | 88 ++++ .../TeamTable/TeamTableConfiguration.tsx | 406 ++++++++++++++++++ src/custom/TeamTable/index.ts | 3 + 3 files changed, 497 insertions(+) create mode 100644 src/custom/TeamTable/TeamTable.tsx create mode 100644 src/custom/TeamTable/TeamTableConfiguration.tsx create mode 100644 src/custom/TeamTable/index.ts diff --git a/src/custom/TeamTable/TeamTable.tsx b/src/custom/TeamTable/TeamTable.tsx new file mode 100644 index 00000000..47c1a753 --- /dev/null +++ b/src/custom/TeamTable/TeamTable.tsx @@ -0,0 +1,88 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Grid, TableCell } from '@mui/material'; +import { ErrorBoundary } from '../ErrorBoundary/ErrorBoundary.js'; +import { ColView } from '../Helpers/ResponsiveColumns/responsive-coulmns.tsx/index.js'; +import ResponsiveDataTable from '../ResponsiveDataTable.js'; +import UsersTable from '../UsersTable/UsersTable.js'; + +interface TeamTableProps { + teams: any; + tableOptions: object; + columnVisibility: Record; + colViews: ColView[]; + tableCols: any[]; + columns: any[]; + updateCols: (cols: any) => void; + isRemoveFromTeamAllowed: boolean; + org_id: string; + useGetUsersForOrgQuery: any; + useNotificationHandlers: any; + useRemoveUserFromTeamMutation: any; +} + +const TeamTable: React.FC = ({ + teams, + tableOptions, + columnVisibility, + colViews, + tableCols, + columns, + updateCols, + isRemoveFromTeamAllowed, + org_id, + useGetUsersForOrgQuery, + useNotificationHandlers, + useRemoveUserFromTeamMutation +}) => { + return ( + + { + const teamID = teams[rowMeta.dataIndex].id; + return ( + + + + + + ); + } + }} + colViews={colViews} + tableCols={tableCols} + updateCols={updateCols} + columnVisibility={columnVisibility} + /> + + ); +}; + +export default TeamTable; diff --git a/src/custom/TeamTable/TeamTableConfiguration.tsx b/src/custom/TeamTable/TeamTableConfiguration.tsx new file mode 100644 index 00000000..8606e994 --- /dev/null +++ b/src/custom/TeamTable/TeamTableConfiguration.tsx @@ -0,0 +1,406 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { useState } from 'react'; +import { DeleteIcon, EditIcon } from '../../icons'; +import LogoutIcon from '../../icons/Logout/LogOutIcon'; +import { CHARCOAL, useTheme } from '../../theme'; +import { CustomTooltip } from '../CustomTooltip'; +import { FormatId } from '../FormatId'; +import { ConditionalTooltip } from '../Helpers/CondtionalTooltip'; +import { useWindowDimensions } from '../Helpers/Dimension'; +import { ColView, updateVisibleColumns } from '../Helpers/ResponsiveColumns/responsive-coulmns.tsx'; +import { IconWrapper } from '../ResponsiveDataTable'; +import { TooltipIcon } from '../TooltipIconButton'; +import { + MesheryDeleteIcon, + TableIconsContainer, + TableIconsDisabledContainer, + TableTopIcon, + TableTopIconsWrapper +} from '../Workspaces/styles'; + +// currently team does not support bulk team delete +interface DeleteTeamsBtnProps { + selected: any; + teams: any[]; + deleteTeamsModalHandler: (deleteTeams: { team_ids: string[]; team_names: string[] }) => void; +} + +function DeleteTeamsBtn({ selected, teams, deleteTeamsModalHandler }: DeleteTeamsBtnProps) { + const deleteTeams = { + team_ids: [] as string[], + team_names: [] as string[] + }; + selected?.data.forEach((val: any) => { + const idx = val.index; + deleteTeams['team_ids'].push(teams[idx]?.team_id); + deleteTeams['team_names'].push(teams[idx]?.team_name); + }); + + return ( + + + + { + deleteTeamsModalHandler(deleteTeams); + }} + /> + + + + ); +} + +interface TeamTableConfigurationProps { + teams: any[]; + count: number; + page: number; + pageSize: number; + setPage: (page: number) => void; + setPageSize: (pageSize: number) => void; + sortOrder: string; + setSortOrder: (sortOrder: string) => void; + bulkSelect: boolean; + setBulkSelect: (bulkSelect: boolean) => void; + handleTeamView: (ev: any, rowData: any) => void; + handleDeleteTeam: (ev: any, rowData: any) => void; + handleleaveTeam: (ev: any, rowData: any) => void; + handleRemoveTeamFromWorkspace?: (rowData: any) => void; + teamId: string; + workspace?: boolean; + isEditTeamAllowed: boolean; + isLeaveTeamAllowed: boolean; + isRemoveTeamFromWorkspaceAllowed?: boolean; + isDeleteTeamAllowed: boolean; + setSearch: (search: string) => void; +} + +export default function TeamTableConfiguration({ + teams, + count, + page, + pageSize, + setPage, + setPageSize, + sortOrder, + setSortOrder, + bulkSelect, + setBulkSelect, + handleTeamView, + handleDeleteTeam, + handleRemoveTeamFromWorkspace, + handleleaveTeam, + teamId, + workspace = false, + isEditTeamAllowed, + isLeaveTeamAllowed, + isRemoveTeamFromWorkspaceAllowed, + isDeleteTeamAllowed, + setSearch +}: TeamTableConfigurationProps) { + const { width } = useWindowDimensions(); + const theme = useTheme(); + // Keys should be same as the value of 'name' key in columns array + const colViews: ColView[] = [ + ['id', 'na'], + ['name', 'xs'], + ['description', 'm'], + ['owner', 'l'], + ['created_at', 'xl'], + ['deleted_at', 'na'], + ['actions', 'xs'] + ]; + + const columns = [ + { + name: 'id', + label: 'ID', + options: { + filter: false, + sort: false, + searchable: false, + customBodyRender: (value: any) => + } + }, + { + name: 'name', + label: 'Name', + options: { + filter: false, + sort: true, + searchable: true + } + }, + { + name: 'description', + label: 'Description', + options: { + filter: false, + sort: true, + searchable: false, + customBodyRender: (value: any) => + } + }, + { + name: 'owner', + label: 'Owner', + options: { + filter: false, + sort: true, + searchable: true + } + }, + { + name: 'created_at', + label: 'Created At', + options: { + filter: false, + sort: true, + searchable: false, + sortDescFirst: true + } + }, + { + name: 'deleted_at', + label: 'Deleted At', + options: { + filter: true, + sort: true, + searchable: false, + sortDescFirst: true, + customBodyRender: (v: any) => JSON.stringify(v), + filterOptions: { + names: ['Deleted', 'Not Deleted'], + logic: (val: any, filters: any) => { + if (val != 'NA' && filters.indexOf('Deleted') >= 0) return true; + else if (val == 'NA' && filters.indexOf('Not Deleted') >= 0) return true; + return false; + }, + fullWidth: true + } + } + }, + { + name: 'actions', + label: 'Actions', + options: { + filter: false, + sort: false, + searchable: false, + customBodyRender: (_: any, tableMeta: any) => { + if (bulkSelect || tableMeta.rowData[4].Valid) { + return ( + + + + + ); + } + return ( + + {!workspace && ( + <> + + { + isEditTeamAllowed && handleTeamView(ev, tableMeta.rowData); + }} + iconType="edit" + > + + + + + isLeaveTeamAllowed && handleleaveTeam(e, tableMeta.rowData)} + iconType="delete" + > + + + + + )} + {workspace ? ( + + { + isRemoveTeamFromWorkspaceAllowed && + handleRemoveTeamFromWorkspace && + handleRemoveTeamFromWorkspace(tableMeta.rowData[0]); + }} + iconType="delete" + > + + + + ) : ( + + { + isDeleteTeamAllowed && handleDeleteTeam(ev, tableMeta.rowData); + }} + iconType="delete" + > + + + + )} + + ); + } + } + } + ]; + const ExpandedRowIdx = teams?.findIndex((team) => team.id === teamId); + + const options = { + filter: false, + selectableRows: 'none' as const, + filterType: 'dropdown' as const, + expandableRows: true, + expandableRowsHeader: false, + expandableRowsOnClick: true, + responsive: 'standard' as const, + count: count, + rowsPerPage: pageSize, + page, + print: false, + download: false, + elevation: 0, + serverSide: true, + tableBody: { + style: { + backgroundColor: '#f3f1f1' + } + }, + viewColumns: false, + search: false, + rowsExpanded: [ExpandedRowIdx], + customToolbarSelect: (selected: any) => ( + + {}} + /> + + ), + textLabels: { + selectedRows: { + text: 'teams(s) selected' + } + }, + 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 'search': + setSearch(tableState.searchText !== null ? tableState.searchText : ''); + 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; + case 'rowSelectionChange': + if (tableState.selectedRows.data.length) { + if (!bulkSelect) { + setBulkSelect(true); + } + } else { + setBulkSelect(false); + } + break; + default: + if (tableState.selectedRows.data.length == 0) { + if (bulkSelect) { + setBulkSelect(false); + } + } + } + }, + isRowSelectable: (dataIndx: number) => { + if (teams[dataIndx]['deleted_at'].Valid === true) return false; + return true; + }, + setRowProps: (row: any, rowIndex: number, tableState: any) => { + const selectedRows = + tableState && tableState.selectedRows ? tableState.selectedRows.data : []; + + if (selectedRows.includes(rowIndex)) { + return { + style: { + backgroundColor: theme.palette.background.hover + } + }; + } + + if (row[6].Valid) { + return { + style: { + backgroundColor: theme.palette.icon.disabled + } + }; + } + + return { + style: { + backgroundColor: theme.palette.background.paper + } + }; + } + }; + + const [tableCols, updateCols] = useState(columns); + const [columnVisibility, setColumnVisibility] = useState(() => { + const showCols = updateVisibleColumns(colViews, width); + const initialVisibility: Record = {}; + columns.forEach((col) => { + initialVisibility[col.name] = showCols[col.name]; + }); + return initialVisibility; + }); + + return { + columns, + tableOptions: options, + tableCols, + updateCols, + columnVisibility, + setColumnVisibility, + colViews + }; +} diff --git a/src/custom/TeamTable/index.ts b/src/custom/TeamTable/index.ts new file mode 100644 index 00000000..81a3ad87 --- /dev/null +++ b/src/custom/TeamTable/index.ts @@ -0,0 +1,3 @@ +import TeamTable from './TeamTable'; +import TeamTableConfiguration from './TeamTableConfiguration'; +export { TeamTable, TeamTableConfiguration }; From 88ca492bc9a1f751685d053fd96be98cbe1bdbd6 Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Sun, 24 Nov 2024 14:50:26 +0530 Subject: [PATCH 14/19] feat: add UsersTable and TooltipIconButton components with customizable properties Signed-off-by: Amit Amrutiya --- .../TooltipIconButton/TooltipIconButton.tsx | 60 +++ src/custom/TooltipIconButton/index.ts | 3 + .../UserSearchField/UserSearchFieldInput.tsx | 17 + src/custom/UsersTable/UsersTable.tsx | 474 ++++++++++++++++++ src/custom/UsersTable/index.ts | 3 + 5 files changed, 557 insertions(+) create mode 100644 src/custom/TooltipIconButton/TooltipIconButton.tsx create mode 100644 src/custom/TooltipIconButton/index.ts create mode 100644 src/custom/UsersTable/UsersTable.tsx create mode 100644 src/custom/UsersTable/index.ts diff --git a/src/custom/TooltipIconButton/TooltipIconButton.tsx b/src/custom/TooltipIconButton/TooltipIconButton.tsx new file mode 100644 index 00000000..5130a357 --- /dev/null +++ b/src/custom/TooltipIconButton/TooltipIconButton.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { IconButton } from '../../base/IconButton'; +import { useTheme } from '../../theme'; +import { CustomTooltip } from '../CustomTooltip'; +import { IconWrapper } from '../ResponsiveDataTable'; + +interface TooltipIconProps { + title: string; + onClick?: (event: React.MouseEvent) => void; + icon?: React.ReactNode; + arrow?: boolean; + style?: React.CSSProperties; + iconType?: string; + id?: string; + placement?: 'bottom' | 'top' | 'left' | 'right'; + disabled?: boolean; + children?: React.ReactNode; +} + +export function TooltipIcon({ + children, + title, + onClick, + icon, + style, + arrow = true, + disabled = false, + iconType, + id, + placement +}: TooltipIconProps): JSX.Element { + const theme = useTheme(); + + return ( + + + + {icon || children} + + + + ); +} + +export default TooltipIcon; diff --git a/src/custom/TooltipIconButton/index.ts b/src/custom/TooltipIconButton/index.ts new file mode 100644 index 00000000..5b7ba746 --- /dev/null +++ b/src/custom/TooltipIconButton/index.ts @@ -0,0 +1,3 @@ +import TooltipIcon from './TooltipIconButton'; + +export { TooltipIcon }; diff --git a/src/custom/UserSearchField/UserSearchFieldInput.tsx b/src/custom/UserSearchField/UserSearchFieldInput.tsx index 67e747e2..32464d9b 100644 --- a/src/custom/UserSearchField/UserSearchFieldInput.tsx +++ b/src/custom/UserSearchField/UserSearchFieldInput.tsx @@ -62,6 +62,23 @@ const UserSearchField: React.FC = ({ usersSearch, setUsersSearch }) => { + console.log('amit usersearchfield', { + usersData, + setUsersData, + label, + setDisableSave, + handleNotifyPref, + notifyUpdate, + isCreate, + searchType, + disabled, + currentUserData, + searchedUsers, + isUserSearchLoading, + fetchSearchedUsers, + usersSearch, + setUsersSearch + }); const [error, setError] = useState(''); const [open, setOpen] = useState(false); const [showAllUsers, setShowAllUsers] = useState(false); diff --git a/src/custom/UsersTable/UsersTable.tsx b/src/custom/UsersTable/UsersTable.tsx new file mode 100644 index 00000000..2d0e78ff --- /dev/null +++ b/src/custom/UsersTable/UsersTable.tsx @@ -0,0 +1,474 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { useRef, useState } from 'react'; +import { Avatar, Box, Grid, Tooltip, Typography } from '../../base'; +import { EditIcon, PersonIcon } from '../../icons'; +import Github from '../../icons/Github/GithubIcon'; +import Google from '../../icons/Google/GoogleIcon'; +import LogoutIcon from '../../icons/Logout/LogOutIcon'; +import { CHARCOAL, SistentThemeProvider } from '../../theme'; +import { useWindowDimensions } from '../Helpers/Dimension'; +import { + ColView, + updateVisibleColumns +} from '../Helpers/ResponsiveColumns/responsive-coulmns.tsx/responsive-column'; +import PromptComponent from '../Prompt'; +import ResponsiveDataTable from '../ResponsiveDataTable'; +import { TooltipIcon } from '../TooltipIconButton'; +import { parseDeletionTimestamp } from '../Workspaces/helper'; +import { TableIconsContainer, TableIconsDisabledContainer } from '../Workspaces/styles'; + +interface ActionButtonsProps { + tableMeta: any; + isRemoveFromTeamAllowed: boolean; + handleRemoveFromTeam: (data: any[]) => () => void; +} + +const ActionButtons: React.FC = ({ + tableMeta, + handleRemoveFromTeam, + isRemoveFromTeamAllowed +}) => { + return ( +
+ {isRemoveFromTeamAllowed ? ( + + + + + + ) : ( + + + + )} +
+ ); +}; + +interface UsersTableProps { + teamID: string; + useGetUsersForOrgQuery: any; + org_id: string; + useRemoveUserFromTeamMutation: any; + useNotificationHandlers: any; + isRemoveFromTeamAllowed: boolean; +} + +const UsersTable: React.FC = ({ + teamID, + useGetUsersForOrgQuery, + org_id, + useRemoveUserFromTeamMutation, + useNotificationHandlers, + isRemoveFromTeamAllowed +}) => { + const [page, setPage] = useState(0); + const [pageSize, setPageSize] = useState(10); + const [sortOrder, setSortOrder] = useState(''); + const [search, setSearch] = useState(''); + const availableRoles: string[] = []; + const { handleError, handleSuccess, handleInfo } = useNotificationHandlers(); + const ref: any = useRef(null); + + const { width } = useWindowDimensions(); + + const { data: userData } = useGetUsersForOrgQuery({ + page: page, + pagesize: pageSize, + search: search, + order: sortOrder, + teamId: teamID, + orgId: org_id + }); + + const [removeUserFromTeam] = useRemoveUserFromTeamMutation(); + + const users = userData?.data || []; + const count = userData?.total_count || 0; + + const handleRemoveFromTeam = (data: any[]) => async () => { + const user_id = data[0]; + + const response = await ref.current?.show({ + title: `Remove User From Team ?`, + subtitle: removeUserFromTeamModalContent(data[3], data[2]), + primaryOption: 'Proceed' + }); + if (response === 'Proceed') { + removeUserFromTeam({ + orgId: org_id, + teamId: teamID, + userId: user_id + }) + .unwrap() + .then(() => { + handleSuccess(`${data[4] ? data[4] : ''} ${data[5] ? data[5] : ''} removed from team`); + }) + .catch((err: any) => { + console.log('heya err', err); + const error = err.response?.data?.message || 'Failed to remove user from team'; + if (err.response.status === 404) { + handleInfo(error); + } else { + handleError(error); + } + }); + } + }; + + const getValidColumnValue = (rowData: any, columnName: string, columns: any) => { + const columnIndex = columns.findIndex((column: any) => column.name === columnName); + return rowData[columnIndex]; + }; + + // const fetchAvailableRoles = () => { + // axios + // .get(process.env.API_ENDPOINT_PREFIX + `/api/identity/orgs/${org_id}/roles?all=true`) + // .then((res) => { + // let roles = []; + // res?.data?.roles?.forEach((role) => roles.push(role?.role_name)); + // setAvailableRoles(roles); + // }) + // .catch((err) => { + // let error = err.response?.data?.message || 'Failed to fetch roles'; + // handleError(error); + // }); + // }; + + const removeUserFromTeamModalContent = (user: string, email: string) => ( + <> +

Are you sure you want to remove this user? (This action is irreversible)

+

+ User:{' '} + + {user} + +

+

+ Email:{' '} + + {email} + +

+ + ); + + let searchTimeout: NodeJS.Timeout; + + const options = { + search: false, + viewColumns: false, + filter: false, + rowsPerPageOptions: [10, 20, 25], + responsive: 'standard', + selectableRows: 'none', + count: count, + rowsPerPage: pageSize, + page, + print: false, + download: false, + 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 'search': + if (searchTimeout) { + clearTimeout(searchTimeout); + } + searchTimeout = setTimeout(() => { + if (search !== tableState.searchText) { + setSearch(tableState.searchText); + } + }, 500); + 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 colViews: ColView[] = [ + ['user_id', 'na'], + ['avatar_url', 'xs'], + ['email', 'na'], + ['username', 'na'], + ['first_name', 'na'], + ['last_name', 'na'], + ['role_names', 'xs'], + ['status', 'na'], + ['joined_at', 'l'], + ['last_login_time', 'l'], + ['deleted_at', 'na'] + // ["actions", "xs"] + ]; + + const columns: any[] = [ + { + name: 'user_id', + label: 'User ID', + options: { + filter: false, + sort: false, + searchable: false + } + }, + { + name: 'avatar_url', + label: 'Team Member', + options: { + filter: false, + sort: false, + searchable: false, + customBodyRender: (value: string, tableMeta: any) => ( + img': { mr: 2, flexShrink: 0 } }}> + + + + { + window.open( + `/user/${getValidColumnValue(tableMeta.rowData, 'user_id', columns)}` + ); + }} + alt={getValidColumnValue(tableMeta.rowData, 'first_name', columns)} + src={value} + > + {value ? '' : } + + + + + {tableMeta.rowData[4]} {tableMeta.rowData[5]} + + {tableMeta.rowData[2]} + + + + + ) + } + }, + { + name: 'email', + label: 'Email', + options: { + filter: false, + sort: true, + searchable: true + } + }, + { + name: 'username', + label: 'Username', + options: { + filter: false, + sort: true, + searchable: true, + customBodyRender: (value: string, tableMeta: any) => ( +
+ {value} + + {value?.includes('@') ? ( + + +
+ +
+
+
+ ) : ( + + +
+ +
+
+
+ )} +
+ ) + } + }, + { + name: 'first_name', + label: 'First', + options: { + filter: false, + sort: true, + searchable: true + } + }, + { + name: 'last_name', + label: 'Last', + options: { + filter: false, + sort: true, + searchable: true + } + }, + { + name: 'role_names', + label: 'Roles', + options: { + filter: true, + sort: false, + searchable: false, + // filterType: "multiselect", + filterOptions: { + names: availableRoles.concat(availableRoles?.map((role) => `!${role}`)) + }, + customBodyRender: (value: string[]) =>
{value?.join(', ')}
+ } + }, + { + name: 'status', + label: 'Status', + options: { + filter: true, + sort: true, + searchable: false + } + }, + { + name: 'joined_at', + label: 'Joined At', + options: { + filter: false, + sort: true, + searchable: false, + sortDescFirst: true + } + }, + { + name: 'last_login_time', + label: 'Last Active At', + options: { + filter: false, + sort: true, + searchable: false, + sortDescFirst: true + } + }, + { + name: 'deleted_at', + label: 'Deleted At', + options: { + filter: true, + sort: true, + searchable: false, + sortDescFirst: true, + filterOptions: { + names: ['Deleted', 'Not Deleted'], + logic: (val: string, filters: string[]) => { + if (val != 'NA' && filters.indexOf('Deleted') >= 0) return true; + else if (val == 'NA' && filters.indexOf('Not Deleted') >= 0) return true; + return false; + }, + fullWidth: true + }, + customBodyRender: (value: any, tableMeta: any) => { + const rowData = users[tableMeta.rowIndex]; + return parseDeletionTimestamp(rowData); + } + } + }, + { + name: 'actions', + label: 'Actions', + options: { + filter: false, + sort: false, + searchable: false, + customBodyRender: (_: any, tableMeta: any) => + getValidColumnValue(tableMeta.rowData, 'deleted_at', columns).Valid !== false ? ( + + + + + ) : ( + + ) + } + } + ]; + + const [tableCols, updateCols] = useState(columns); + + const [columnVisibility] = useState>(() => { + const showCols: Record = updateVisibleColumns(colViews, width); + // Initialize column visibility based on the original columns' visibility + const initialVisibility: Record = {}; + columns.forEach((col) => { + initialVisibility[col.name] = showCols[col.name]; + }); + return initialVisibility; + }); + + return ( + +
+ +
+ +
+ ); +}; + +export default UsersTable; diff --git a/src/custom/UsersTable/index.ts b/src/custom/UsersTable/index.ts new file mode 100644 index 00000000..ee80ef64 --- /dev/null +++ b/src/custom/UsersTable/index.ts @@ -0,0 +1,3 @@ +import UsersTable from './UsersTable'; + +export { UsersTable }; From 487e4fd9efabfa8ff1162b1c2cc3d4c283f922f0 Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Sun, 24 Nov 2024 14:51:10 +0530 Subject: [PATCH 15/19] feat: implement useTeamAssignment hook for managing team assignments in workspaces Signed-off-by: Amit Amrutiya --- src/custom/Workspaces/WorkspaceTeamsTable.tsx | 196 ++++++++++++++++++ .../Workspaces/hooks/useDesignAssignment.tsx | 18 +- .../hooks/useEnvironmentAssignment.tsx | 51 ++--- .../Workspaces/hooks/useTeamAssignment.tsx | 146 +++++++++++++ 4 files changed, 379 insertions(+), 32 deletions(-) create mode 100644 src/custom/Workspaces/WorkspaceTeamsTable.tsx create mode 100644 src/custom/Workspaces/hooks/useTeamAssignment.tsx diff --git a/src/custom/Workspaces/WorkspaceTeamsTable.tsx b/src/custom/Workspaces/WorkspaceTeamsTable.tsx new file mode 100644 index 00000000..74173c8c --- /dev/null +++ b/src/custom/Workspaces/WorkspaceTeamsTable.tsx @@ -0,0 +1,196 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { useState } from 'react'; +import { Accordion, AccordionDetails, AccordionSummary, Typography } from '../../base'; +import { TeamsIcon } from '../../icons'; +import { SistentThemeProvider } from '../../theme'; +import { CustomColumnVisibilityControl } from '../CustomColumnVisibilityControl'; +import SearchBar from '../SearchBar'; +import { TeamTableConfiguration } from '../TeamTable'; +import TeamTable from '../TeamTable/TeamTable'; +import AssignmentModal from './AssignmentModal'; +import EditButton from './EditButton'; +import useTeamAssignment from './hooks/useTeamAssignment'; +import { TableHeader, TableRightActionHeader } from './styles'; + +export interface TeamsTableProps { + workspaceId: string; + workspaceName: string; + useGetTeamsOfWorkspaceQuery: any; + useUnassignTeamFromWorkspaceMutation: any; + useAssignTeamToWorkspaceMutation: any; + isEditTeamAllowed: boolean; + isAssignTeamAllowed: boolean; + isRemoveTeamFromWorkspaceAllowed: boolean; + isDeleteTeamAllowed: boolean; + isLeaveTeamAllowed: boolean; + org_id: string; + fetchTeamUsers: any; + useGetUsersForOrgQuery: any; + useNotificationHandlers: any; + useRemoveUserFromTeamMutation: any; +} + +const TeamsTable: React.FC = ({ + workspaceId, + workspaceName, + useGetTeamsOfWorkspaceQuery, + useAssignTeamToWorkspaceMutation, + useUnassignTeamFromWorkspaceMutation, + isEditTeamAllowed, + isAssignTeamAllowed, + isRemoveTeamFromWorkspaceAllowed, + isLeaveTeamAllowed, + isDeleteTeamAllowed, + org_id, + useGetUsersForOrgQuery, + useNotificationHandlers, + useRemoveUserFromTeamMutation +}) => { + const [page, setPage] = useState(0); + const [pageSize, setPageSize] = useState(10); + const [sortOrder, setSortOrder] = useState(''); + const [bulkSelect, setBulkSelect] = useState(false); + const [expanded, setExpanded] = useState(true); + const handleAccordionChange = () => { + setExpanded(!expanded); + }; + const [search, setSearch] = useState(''); + const [isSearchExpanded, setIsSearchExpanded] = useState(false); + + const { data: teamsOfWorkspace } = useGetTeamsOfWorkspaceQuery({ + workspaceId, + page: page, + pageSize: pageSize, + order: sortOrder, + search: search + }); + const [unassignTeamFromWorkspace] = useUnassignTeamFromWorkspaceMutation(); + + const teamAssignment = useTeamAssignment({ + workspaceId, + useGetTeamsOfWorkspaceQuery, + useAssignTeamToWorkspaceMutation, + useUnassignTeamFromWorkspaceMutation + }); + + const handleRemoveTeamFromWorkspace = (teamId: string): void => { + unassignTeamFromWorkspace({ + workspaceId, + teamId + }).unwrap(); + }; + + const tableProps = TeamTableConfiguration({ + teams: teamsOfWorkspace?.teams, + count: teamsOfWorkspace?.total_count, + page, + pageSize, + setPage, + setPageSize, + sortOrder, + setSortOrder, + bulkSelect, + setBulkSelect, + handleRemoveTeamFromWorkspace, + handleTeamView: () => {}, + handleDeleteTeam: () => {}, + handleleaveTeam: () => {}, + teamId: '', + workspace: true, + isEditTeamAllowed, + isLeaveTeamAllowed, + isRemoveTeamFromWorkspaceAllowed, + isDeleteTeamAllowed: isDeleteTeamAllowed, + setSearch + }); + + return ( + + + } + sx={{ backgroundColor: 'background.paper' }} + > + + + Assigned Teams + + + { + setSearch(value); + }} + onClear={() => { + setSearch(''); + }} + expanded={isSearchExpanded} + setExpanded={setIsSearchExpanded} + placeholder="Search workspaces..." + /> + + + + + + + + + + + } + name="Teams" + assignableData={teamAssignment.data} + handleAssignedData={teamAssignment.handleAssignData} + originalAssignedData={teamAssignment.workspaceData} + emptyStateIcon={ + + } + handleAssignablePage={teamAssignment.handleAssignablePage} + handleAssignedPage={teamAssignment.handleAssignedPage} + originalLeftCount={teamAssignment.data?.length} + originalRightCount={teamsOfWorkspace?.total_count} + onAssign={teamAssignment.handleAssign} + disableTransfer={teamAssignment.disableTransferButton} + helpText={`Assign Teams to ${workspaceName}`} + isAssignAllowed={isAssignTeamAllowed} + isRemoveAllowed={isRemoveTeamFromWorkspaceAllowed} + /> + + ); +}; + +export default TeamsTable; diff --git a/src/custom/Workspaces/hooks/useDesignAssignment.tsx b/src/custom/Workspaces/hooks/useDesignAssignment.tsx index 9b9d4511..9931f581 100644 --- a/src/custom/Workspaces/hooks/useDesignAssignment.tsx +++ b/src/custom/Workspaces/hooks/useDesignAssignment.tsx @@ -107,22 +107,24 @@ const useDesignAssignment = ({ const handleAssignDesigns = async (): Promise => { const { addedDesignsIds, removedDesignsIds } = getAddedAndRemovedDesigns(assignedDesigns); - for (const id of addedDesignsIds) { - await assignDesignToWorkspace({ + addedDesignsIds.map((id) => + assignDesignToWorkspace({ workspaceId, designId: id - }).unwrap(); - } + }).unwrap() + ); - for (const id of removedDesignsIds) { - await unassignDesignFromWorkspace({ + removedDesignsIds.map((id) => + unassignDesignFromWorkspace({ workspaceId, designId: id - }).unwrap(); - } + }).unwrap() + ); setDesignsData([]); setWorkspaceDesignsData([]); + setDesignsPage(0); + setDesignsOfWorkspacePage(0); handleAssignDesignModalClose(); }; diff --git a/src/custom/Workspaces/hooks/useEnvironmentAssignment.tsx b/src/custom/Workspaces/hooks/useEnvironmentAssignment.tsx index fb346db7..155f28c3 100644 --- a/src/custom/Workspaces/hooks/useEnvironmentAssignment.tsx +++ b/src/custom/Workspaces/hooks/useEnvironmentAssignment.tsx @@ -23,6 +23,8 @@ const useEnvironmentAssignment = ({ const [workspaceEnvironmentsData, setWorkspaceEnvironmentsData] = useState([]); const [assignEnvironmentModal, setAssignEnvironmentModal] = useState(false); const [skipEnvironments, setSkipEnvironments] = useState(true); + const [disableTransferButton, setDisableTransferButton] = useState(true); + const [assignedEnvironments, setAssignedEnvironments] = useState([]); const { data: environments } = useGetEnvironmentsOfWorkspaceQuery( withDefaultPageArgs({ @@ -49,8 +51,6 @@ const useEnvironmentAssignment = ({ const [assignEnvironmentToWorkspace] = useAssignEnvironmentToWorkspaceMutation(); const [unassignEnvironmentFromWorkspace] = useUnassignEnvironmentFromWorkspaceMutation(); - const [disableTransferButton, setDisableTransferButton] = useState(true); - const [assignedEnvironments, setAssignedEnvironments] = useState([]); useEffect(() => { const environmentsDataRtk = environments?.environments ? environments.environments : []; @@ -92,49 +92,52 @@ const useEnvironmentAssignment = ({ } }; + 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 handleAssignEnvironments = async () => { const { addedEnvironmentsIds, removedEnvironmentsIds } = getAddedAndRemovedEnvironments(assignedEnvironments); - for (const id of addedEnvironmentsIds) { - await assignEnvironmentToWorkspace({ + addedEnvironmentsIds.map((id) => + assignEnvironmentToWorkspace({ workspaceId, environmentId: id - }).unwrap(); - } + }).unwrap() + ); - for (const id of removedEnvironmentsIds) { - await unassignEnvironmentFromWorkspace({ + removedEnvironmentsIds.map((id) => + unassignEnvironmentFromWorkspace({ workspaceId, environmentId: id - }).unwrap(); - } + }).unwrap() + ); setEnvironmentsData([]); setWorkspaceEnvironmentsData([]); + setEnvironmentsPage(0); + setEnvironmentsOfWorkspacePage(0); 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); }; diff --git a/src/custom/Workspaces/hooks/useTeamAssignment.tsx b/src/custom/Workspaces/hooks/useTeamAssignment.tsx new file mode 100644 index 00000000..0c27619e --- /dev/null +++ b/src/custom/Workspaces/hooks/useTeamAssignment.tsx @@ -0,0 +1,146 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { useEffect, useState } from 'react'; +import { withDefaultPageArgs } from '../../PerformersSection/PerformersSection'; +import { AssignmentHookResult, Team } from '../types'; + +interface UseTeamAssignmentProps { + workspaceId: string; + useGetTeamsOfWorkspaceQuery: any; + useAssignTeamToWorkspaceMutation: any; + useUnassignTeamFromWorkspaceMutation: any; +} + +const useTeamAssignment = ({ + workspaceId, + useGetTeamsOfWorkspaceQuery, + useAssignTeamToWorkspaceMutation, + useUnassignTeamFromWorkspaceMutation +}: UseTeamAssignmentProps): AssignmentHookResult => { + const [teamsPage, setTeamsPage] = useState(0); + const [teamsData, setTeamsData] = useState([]); + const teamsPageSize = 25; + const [teamsOfWorkspacePage, setTeamsOfWorkspacePage] = useState(0); + const [workspaceTeamsData, setWorkspaceTeamsData] = useState([]); + const [assignTeamModal, setAssignTeamModal] = useState(false); + const [skipTeams, setSkipTeams] = useState(true); + const [assignTeamToWorkspace] = useAssignTeamToWorkspaceMutation(); + const [unassignTeamFromWorkspace] = useUnassignTeamFromWorkspaceMutation(); + const [disableTransferButton, setDisableTransferButton] = useState(true); + const [assignedTeams, setAssignedTeams] = useState([]); + + const { data: teams } = useGetTeamsOfWorkspaceQuery( + withDefaultPageArgs({ + workspaceId, + page: teamsPage, + pagesize: teamsPageSize, + filter: '{"assigned":false}' + }), + { + skip: skipTeams + } + ); + + const { data: teamsOfWorkspace } = useGetTeamsOfWorkspaceQuery( + withDefaultPageArgs({ + workspaceId, + page: teamsOfWorkspacePage, + pagesize: teamsPageSize + }), + { + skip: skipTeams + } + ); + + useEffect(() => { + const teamsDataRtk = teams?.teams ? teams.teams : []; + setTeamsData((prevData) => [...prevData, ...teamsDataRtk]); + }, [teams]); + + useEffect(() => { + const teamsOfWorkspaceDataRtk = teamsOfWorkspace?.teams ? teamsOfWorkspace.teams : []; + setWorkspaceTeamsData((prevData) => [...prevData, ...teamsOfWorkspaceDataRtk]); + }, [teamsOfWorkspace]); + + const handleAssignTeamModal = (e?: React.MouseEvent) => { + e?.stopPropagation(); + setAssignTeamModal(true); + setSkipTeams(false); + }; + + const handleAssignTeamModalClose = (e?: React.MouseEvent) => { + e?.stopPropagation(); + setAssignTeamModal(false); + setSkipTeams(true); + }; + + const handleAssignablePageTeam = () => { + const pagesCount = Math.ceil(Number(teams?.total_count) / teamsPageSize); + if (teamsPage < pagesCount - 1) { + setTeamsPage((prevTeamsPage) => prevTeamsPage + 1); + } + }; + + const handleAssignedPageTeam = () => { + const pagesCount = Math.ceil(Number(teamsOfWorkspace?.total_count) / teamsPageSize); + + if (teamsOfWorkspacePage < pagesCount - 1) { + setTeamsOfWorkspacePage((prevPage) => prevPage + 1); + } + }; + + const handleAssignTeams = () => { + const { addedTeamsIds, removedTeamsIds } = getAddedAndRemovedTeams(assignedTeams); + + addedTeamsIds.map((id) => + assignTeamToWorkspace({ + workspaceId, + teamId: id + }).unwrap() + ); + + removedTeamsIds.map((id) => + unassignTeamFromWorkspace({ + workspaceId, + teamId: id + }).unwrap() + ); + + setTeamsData([]); + setWorkspaceTeamsData([]); + handleAssignTeamModalClose(); + }; + + const getAddedAndRemovedTeams = (allAssignedTeams: Team[]) => { + const originalTeamsIds = workspaceTeamsData.map((team) => team.id); + const updatedTeamsIds = allAssignedTeams.map((team) => team.id); + + const addedTeamsIds = updatedTeamsIds.filter((id) => !originalTeamsIds.includes(id)); + const removedTeamsIds = originalTeamsIds.filter((id) => !updatedTeamsIds.includes(id)); + + return { addedTeamsIds, removedTeamsIds }; + }; + + const handleAssignTeamsData = (updatedAssignedData: Team[]) => { + const { addedTeamsIds, removedTeamsIds } = getAddedAndRemovedTeams(updatedAssignedData); + addedTeamsIds.length > 0 || removedTeamsIds.length > 0 + ? setDisableTransferButton(false) + : setDisableTransferButton(true); + setAssignedTeams(updatedAssignedData); + }; + + return { + data: teamsData, + workspaceData: workspaceTeamsData, + assignModal: assignTeamModal, + handleAssignModal: handleAssignTeamModal, + handleAssignModalClose: handleAssignTeamModalClose, + handleAssignablePage: handleAssignablePageTeam, + handleAssignedPage: handleAssignedPageTeam, + handleAssign: handleAssignTeams, + handleAssignData: handleAssignTeamsData, + disableTransferButton, + assignedItems: assignedTeams + }; +}; + +export default useTeamAssignment; From bbb05882760c5928afcfea727f7431441a02f9ae Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Mon, 25 Nov 2024 14:52:05 +0530 Subject: [PATCH 16/19] feat: update AssignmentModal and EnvironmentTable to use isAssignAllowed Signed-off-by: Amit Amrutiya --- src/custom/Workspaces/AssignmentModal.tsx | 12 +-- src/custom/Workspaces/DesignTable.tsx | 105 +++++++++++---------- src/custom/Workspaces/EnvironmentTable.tsx | 51 ++++++---- src/custom/index.tsx | 10 ++ 4 files changed, 102 insertions(+), 76 deletions(-) diff --git a/src/custom/Workspaces/AssignmentModal.tsx b/src/custom/Workspaces/AssignmentModal.tsx index e9933125..4fc4d865 100644 --- a/src/custom/Workspaces/AssignmentModal.tsx +++ b/src/custom/Workspaces/AssignmentModal.tsx @@ -19,8 +19,8 @@ interface AssignmentModalProps { originalRightCount: number; onAssign: () => void; disableTransfer: boolean; - isAssignDisabled: boolean; - isRemoveDisabled: boolean; + isAssignAllowed: boolean; + isRemoveAllowed: boolean; helpText: string; } @@ -40,8 +40,8 @@ const AssignmentModal: React.FC = ({ originalRightCount, onAssign, disableTransfer, - isAssignDisabled, - isRemoveDisabled, + isAssignAllowed, + isRemoveAllowed, helpText }) => { return ( @@ -67,8 +67,8 @@ const AssignmentModal: React.FC = ({ assignedPage={handleAssignedPage} originalLeftCount={originalLeftCount} originalRightCount={originalRightCount} - leftPermission={isAssignDisabled} - rightPermission={isRemoveDisabled} + leftPermission={isAssignAllowed} + rightPermission={isRemoveAllowed} transferComponentType={''} /> diff --git a/src/custom/Workspaces/DesignTable.tsx b/src/custom/Workspaces/DesignTable.tsx index 7d60636b..d0d1f3e2 100644 --- a/src/custom/Workspaces/DesignTable.tsx +++ b/src/custom/Workspaces/DesignTable.tsx @@ -16,11 +16,11 @@ import { CustomColumnVisibilityControl } from '../CustomColumnVisibilityControl' import { useWindowDimensions } from '../Helpers/Dimension'; import { updateVisibleColumns } from '../Helpers/ResponsiveColumns/responsive-coulmns.tsx/responsive-column'; import PromptComponent from '../Prompt'; +import SearchBar from '../SearchBar'; 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; @@ -48,14 +48,15 @@ export interface DesignTableProps { 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; + isDownloadAllowed: boolean; + isCopyLinkAllowed: boolean; + isDeleteAllowed: boolean; + isPublishAllowed: boolean; + isUnpublishAllowed: boolean; + isAssignAllowed: boolean; + isRemoveAllowed: boolean; + setDesignSearch: (value: string) => void; } export interface PublishModalState { @@ -83,17 +84,18 @@ const DesignTable: React.FC = ({ handleUnpublishModal, handleWorkspaceDesignDeleteModal, publishModalHandler, - isCopyLinkDisabled, - isDeleteDisabled, - isDownloadDisabled, - isPublishDisabled, - isUnpublishDisabled, + isCopyLinkAllowed, + isDeleteAllowed, + isDownloadAllowed, + isPublishAllowed, + isUnpublishAllowed, useAssignDesignToWorkspaceMutation, useUnassignDesignFromWorkspaceMutation, GenericRJSFModal, - isAssignDisabled, - isRemoveDisabled, - useGetWorkspaceDesignsQuery + isAssignAllowed, + isRemoveAllowed, + useGetWorkspaceDesignsQuery, + setDesignSearch }) => { const [publishModal, setPublishModal] = useState({ open: false, @@ -103,6 +105,7 @@ const DesignTable: React.FC = ({ const [page, setPage] = useState(0); const [pageSize, setPageSize] = useState(10); const [sortOrder, setSortOrder] = useState(''); + const [isSearchExpanded, setIsSearchExpanded] = useState(false); const handlePublishModal = (pattern: Pattern): void => { const result = publishModalHandler(pattern); @@ -119,11 +122,13 @@ const DesignTable: React.FC = ({ handleCopyUrl, handleClone, handleShowDetails, - isDownloadDisabled, - isCopyLinkDisabled, - isDeleteDisabled, - isPublishDisabled, - isUnpublishDisabled + isCopyLinkAllowed, + isDeleteAllowed, + isDownloadAllowed, + isPublishAllowed, + isUnpublishAllowed, + isFromWorkspaceTable: true, + isRemoveAllowed }); const [publishSchema, setPublishSchema] = useState<{ @@ -135,9 +140,9 @@ const DesignTable: React.FC = ({ }); const { width } = useWindowDimensions(); - const [columnVisibility, setColumnVisibility] = useState(() => { + const [columnVisibility, setColumnVisibility] = useState>(() => { const showCols = updateVisibleColumns(designColumnsColViews, width); - const initialVisibility: ColumnVisibility = {}; + const initialVisibility: Record = {}; columns.forEach((col) => { initialVisibility[col.name] = showCols[col.name]; }); @@ -167,19 +172,7 @@ const DesignTable: React.FC = ({ fetchSchema(); }, [meshModelModelsData]); - const { - disableTransferButton, - assignModal, - handleAssignModalClose, - handleAssignModal, - assignedItems, - data, - workspaceData, - handleAssignablePage, - handleAssignedPage, - handleAssign, - handleAssignData - } = useDesignAssignment({ + const designAssignment = useDesignAssignment({ workspaceId, useAssignDesignToWorkspaceMutation, useUnassignDesignFromWorkspaceMutation, @@ -192,6 +185,17 @@ const DesignTable: React.FC = ({ Assigned Designs + { + setDesignSearch(value); + }} + onClear={() => { + setDesignSearch(''); + }} + expanded={isSearchExpanded} + setExpanded={setIsSearchExpanded} + placeholder="Search designs..." + /> = ({ }} id={'catalog-table'} /> - + ); @@ -234,28 +238,29 @@ const DesignTable: React.FC = ({ handleBulkWorkspaceDesignDeleteModal(designs, modalRef, workspaceName, workspaceId) } filter={'my-designs'} + setSearch={setDesignSearch} /> } name="Designs" - assignableData={data} - handleAssignedData={handleAssignData} - originalAssignedData={workspaceData} + assignableData={designAssignment.data} + handleAssignedData={designAssignment.handleAssignData} + originalAssignedData={designAssignment.workspaceData} emptyStateIcon={} - handleAssignablePage={handleAssignablePage} - handleAssignedPage={handleAssignedPage} - originalLeftCount={data?.length} - originalRightCount={assignedItems?.length} - onAssign={handleAssign} - disableTransfer={disableTransferButton} + handleAssignablePage={designAssignment.handleAssignablePage} + handleAssignedPage={designAssignment.handleAssignedPage} + originalLeftCount={designAssignment.data?.length} + originalRightCount={designAssignment.assignedItems?.length} + onAssign={designAssignment.handleAssign} + disableTransfer={designAssignment.disableTransferButton} helpText={`Assign Designs to ${workspaceName}`} - isAssignDisabled={isAssignDisabled} - isRemoveDisabled={isRemoveDisabled} + isAssignAllowed={isAssignAllowed} + isRemoveAllowed={isRemoveAllowed} /> ( const EnvironmentTable: React.FC = ({ workspaceId, workspaceName, - isRemoveDisabled, + isRemoveAllowed, useGetEnvironmentsOfWorkspaceQuery, useUnassignEnvironmentFromWorkspaceMutation, useAssignEnvironmentToWorkspaceMutation, - isAssignDisabled + isAssignAllowed }) => { const [expanded, setExpanded] = useState(true); const handleAccordionChange = () => { setExpanded(!expanded); }; + const [search, setSearch] = useState(''); + const [isSearchExpanded, setIsSearchExpanded] = useState(false); const [page, setPage] = useState(0); const [pageSize, setPageSize] = useState(10); const [sortOrder, setSortOrder] = useState(''); @@ -73,6 +75,7 @@ const EnvironmentTable: React.FC = ({ workspaceId, page: page, pageSize: pageSize, + search: search, order: sortOrder }); const { width } = useWindowDimensions(); @@ -148,12 +151,12 @@ const EnvironmentTable: React.FC = ({ sort: false, searchable: false, customBodyRender: (_: any, tableMeta: MUIDataTableMeta) => ( - + { - !isRemoveDisabled && + isRemoveAllowed && unassignEnvironmentFromWorkspace({ workspaceId, environmentId: tableMeta.rowData[0] @@ -161,13 +164,7 @@ const EnvironmentTable: React.FC = ({ }} iconType="delete" > - + ) @@ -182,9 +179,9 @@ const EnvironmentTable: React.FC = ({ useAssignEnvironmentToWorkspaceMutation }); - const [columnVisibility, setColumnVisibility] = useState(() => { + const [columnVisibility, setColumnVisibility] = useState>(() => { const showCols = updateVisibleColumns(colViews, width); - const initialVisibility: ColumnVisibility = {}; + const initialVisibility: Record = {}; columns.forEach((col) => { initialVisibility[col.name] = showCols[col.name]; }); @@ -214,6 +211,9 @@ const EnvironmentTable: React.FC = ({ case 'changeRowsPerPage': setPageSize(tableState.rowsPerPage); break; + case 'search': + setSearch(tableState.searchText !== null ? tableState.searchText : ''); + break; case 'sort': if (sortInfo.length == 2) { if (sortInfo[1] === 'ascending') { @@ -245,6 +245,17 @@ const EnvironmentTable: React.FC = ({ Assigned Environments + { + setSearch(value); + }} + onClear={() => { + setSearch(''); + }} + expanded={isSearchExpanded} + setExpanded={setIsSearchExpanded} + placeholder="Search workspaces..." + /> = ({ /> @@ -290,8 +301,8 @@ const EnvironmentTable: React.FC = ({ onAssign={environmentAssignment.handleAssign} disableTransfer={environmentAssignment.disableTransferButton} helpText={`Assign Environments to ${workspaceName}`} - isAssignDisabled={!isAssignDisabled} - isRemoveDisabled={!isRemoveDisabled} + isAssignAllowed={isAssignAllowed} + isRemoveAllowed={isRemoveAllowed} /> ); diff --git a/src/custom/index.tsx b/src/custom/index.tsx index a2202b9a..70bef8d5 100644 --- a/src/custom/index.tsx +++ b/src/custom/index.tsx @@ -27,6 +27,7 @@ import { } from './ErrorBoundary'; import { FeedbackButton } from './Feedback'; import { FlipCard, FlipCardProps } from './FlipCard'; +import { FormatId } from './FormatId'; import { useWindowDimensions } from './Helpers/Dimension'; import { useNotificationHandler } from './Helpers/Notification'; import { ColView, updateVisibleColumns } from './Helpers/ResponsiveColumns/responsive-coulmns.tsx'; @@ -40,9 +41,12 @@ import ResponsiveDataTable, { ResponsiveDataTableProps } from './ResponsiveDataTable'; import SearchBar, { SearchBarProps } from './SearchBar'; +import { TeamTable, TeamTableConfiguration } from './TeamTable'; +import { TooltipIcon } from './TooltipIconButton'; import { TransferList } from './TransferModal/TransferList'; import { TransferListProps } from './TransferModal/TransferList/TransferList'; import UniversalFilter, { UniversalFilterProps } from './UniversalFilter'; +import { UsersTable } from './UsersTable'; export { CatalogCard } from './CatalogCard'; export { CatalogFilterSidebar } from './CatalogFilterSection'; export type { FilterListType } from './CatalogFilterSection'; @@ -59,6 +63,7 @@ export { TOC } from './TOCChapter'; export { TOCLearning } from './TOCLearning'; export { Terminal } from './Terminal'; export { UserSearchField } from './UserSearchField'; + export { ActionButton, BookmarkNotification, @@ -78,6 +83,7 @@ export { Fallback, FeedbackButton, FlipCard, + FormatId, InfoTooltip, LearningCard, ModalCard, @@ -88,8 +94,12 @@ export { StyledDialogActions, StyledDialogContent, StyledDialogTitle, + TeamTable, + TeamTableConfiguration, + TooltipIcon, TransferList, UniversalFilter, + UsersTable, updateVisibleColumns, useNotificationHandler, useWindowDimensions, From 6765d63674ceb39cafe491240ee0185c3e2ca592 Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Mon, 25 Nov 2024 14:52:20 +0530 Subject: [PATCH 17/19] feat: remove TooltipIcon component and add helper functions for timestamp parsing Signed-off-by: Amit Amrutiya --- .../UserSearchField/UserSearchFieldInput.tsx | 17 ------ src/custom/Workspaces/TooltipIcon.tsx | 54 ------------------- src/custom/Workspaces/helper.ts | 19 +++++++ src/custom/Workspaces/index.ts | 15 +++++- src/custom/Workspaces/styles.ts | 47 ++++++++++++++++ src/custom/Workspaces/types.ts | 4 -- 6 files changed, 80 insertions(+), 76 deletions(-) delete mode 100644 src/custom/Workspaces/TooltipIcon.tsx create mode 100644 src/custom/Workspaces/helper.ts diff --git a/src/custom/UserSearchField/UserSearchFieldInput.tsx b/src/custom/UserSearchField/UserSearchFieldInput.tsx index 32464d9b..67e747e2 100644 --- a/src/custom/UserSearchField/UserSearchFieldInput.tsx +++ b/src/custom/UserSearchField/UserSearchFieldInput.tsx @@ -62,23 +62,6 @@ const UserSearchField: React.FC = ({ usersSearch, setUsersSearch }) => { - console.log('amit usersearchfield', { - usersData, - setUsersData, - label, - setDisableSave, - handleNotifyPref, - notifyUpdate, - isCreate, - searchType, - disabled, - currentUserData, - searchedUsers, - isUserSearchLoading, - fetchSearchedUsers, - usersSearch, - setUsersSearch - }); const [error, setError] = useState(''); const [open, setOpen] = useState(false); const [showAllUsers, setShowAllUsers] = useState(false); diff --git a/src/custom/Workspaces/TooltipIcon.tsx b/src/custom/Workspaces/TooltipIcon.tsx deleted file mode 100644 index 6b387b9a..00000000 --- a/src/custom/Workspaces/TooltipIcon.tsx +++ /dev/null @@ -1,54 +0,0 @@ -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/helper.ts b/src/custom/Workspaces/helper.ts new file mode 100644 index 00000000..55344279 --- /dev/null +++ b/src/custom/Workspaces/helper.ts @@ -0,0 +1,19 @@ +import { formatDate } from '../CatalogDetail/helper'; + +/** + * Helper function to parse and format the value of a field representing a deletion timestamp in the provided data object. + * @param {Object} data - The data object containing the field representing the deletion timestamp. + * @returns {string} - The formatted date string if the deletion timestamp is valid; otherwise, "N/A". + */ + +export const DEFAULT_DATE = 'N/A'; // a constant to represent the default date value +export const parseDeletionTimestamp = (data: { + deleted_at: { Valid: boolean; Time: string | number | Date }; +}) => { + if (data && data.deleted_at && data.deleted_at.Valid === true) { + const date = new Date(data.deleted_at.Time); + return formatDate(date); + } else { + return DEFAULT_DATE; + } +}; diff --git a/src/custom/Workspaces/index.ts b/src/custom/Workspaces/index.ts index 4294ab26..6a0dfa9d 100644 --- a/src/custom/Workspaces/index.ts +++ b/src/custom/Workspaces/index.ts @@ -1,4 +1,17 @@ import AssignmentModal from './AssignmentModal'; import DesignTable from './DesignTable'; import EnvironmentTable from './EnvironmentTable'; -export { AssignmentModal, DesignTable, EnvironmentTable }; +import WorkspaceTeamsTable from './WorkspaceTeamsTable'; +import useDesignAssignment from './hooks/useDesignAssignment'; +import useEnvironmentAssignment from './hooks/useEnvironmentAssignment'; +import useTeamAssignment from './hooks/useTeamAssignment'; + +export { + AssignmentModal, + DesignTable, + EnvironmentTable, + WorkspaceTeamsTable, + useDesignAssignment, + useEnvironmentAssignment, + useTeamAssignment +}; diff --git a/src/custom/Workspaces/styles.ts b/src/custom/Workspaces/styles.ts index 92ac5238..c26b225a 100644 --- a/src/custom/Workspaces/styles.ts +++ b/src/custom/Workspaces/styles.ts @@ -63,3 +63,50 @@ export const CustomBodyRenderStyle = styled('div')({ display: 'block', width: '100%' }); + +export const TableTopIcon = styled('span')(() => ({ + '& svg': { + cursor: 'pointer', + width: '2rem', + height: '2rem' + } +})); + +export const DisabledTableTopIcon = styled('span')(() => ({ + '& svg': { + width: '2rem', + height: '2rem' + } +})); + +export const MesheryDeleteIcon = styled('span')(({ theme }) => ({ + '& svg': { + color: '#3C494F', + '&:hover': { + color: theme.palette.error.error + } + } +})); + +export const TableIconsDisabledContainer = styled('span')(() => ({ + color: '#455a64', + opacity: '0.5', + '& svg': { + cursor: 'not-allowed' + } +})); + +export const TableTopIconsWrapper = styled('div')(() => ({ + display: 'flex', + justifyContent: 'space-between', + paddingRight: '26px' +})); + +export const TableIconsContainer = styled('div')(() => ({ + color: '#455a64', + display: 'flex', + cursor: 'not-allowed', + '& svg': { + cursor: 'pointer' + } +})); diff --git a/src/custom/Workspaces/types.ts b/src/custom/Workspaces/types.ts index ad391f76..a6f5b6af 100644 --- a/src/custom/Workspaces/types.ts +++ b/src/custom/Workspaces/types.ts @@ -32,10 +32,6 @@ export interface Environment { updated_at: string; } -export interface ColumnVisibility { - [key: string]: boolean; -} - export interface Team { id: string; name: string; From be19b858245dd759b4d8e36948dfb68d48c2f9e0 Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Mon, 25 Nov 2024 16:29:00 +0530 Subject: [PATCH 18/19] feat: update sorting configurations and add user_id column in design tables Signed-off-by: Amit Amrutiya --- src/custom/CatalogDesignTable/CatalogDesignTable.tsx | 2 +- .../CatalogDesignTable/DesignTableColumnConfig.tsx | 10 ++++++++++ src/custom/TeamTable/TeamTableConfiguration.tsx | 4 ++++ src/custom/UsersTable/UsersTable.tsx | 4 ++++ src/custom/Workspaces/EnvironmentTable.tsx | 4 ++++ 5 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/custom/CatalogDesignTable/CatalogDesignTable.tsx b/src/custom/CatalogDesignTable/CatalogDesignTable.tsx index 9f5d11cc..b6e63a17 100644 --- a/src/custom/CatalogDesignTable/CatalogDesignTable.tsx +++ b/src/custom/CatalogDesignTable/CatalogDesignTable.tsx @@ -143,7 +143,7 @@ export const CatalogDesignsTable: React.FC = ({ page, elevation: 0, sortOrder: { - name: 'updated_at At', + name: 'updated_at', direction: 'desc' }, diff --git a/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx b/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx index c39da3df..06c092e9 100644 --- a/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx +++ b/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx @@ -42,6 +42,7 @@ export const colViews: ColView[] = [ ['created_at', 'na'], ['updated_at', 'l'], ['visibility', 'l'], + ['user_id', 'na'], ['actions', 'xs'] ]; @@ -141,6 +142,15 @@ export const createDesignsColumnsConfig = ({ searchable: true } }, + { + name: 'user_id', + label: 'User ID', + options: { + filter: false, + sort: false, + searchable: false + } + }, { name: 'actions', label: 'Actions', diff --git a/src/custom/TeamTable/TeamTableConfiguration.tsx b/src/custom/TeamTable/TeamTableConfiguration.tsx index 8606e994..0b8db7c9 100644 --- a/src/custom/TeamTable/TeamTableConfiguration.tsx +++ b/src/custom/TeamTable/TeamTableConfiguration.tsx @@ -288,6 +288,10 @@ export default function TeamTableConfiguration({ backgroundColor: '#f3f1f1' } }, + sortOrder: { + name: 'created_at', + direction: 'desc' + }, viewColumns: false, search: false, rowsExpanded: [ExpandedRowIdx], diff --git a/src/custom/UsersTable/UsersTable.tsx b/src/custom/UsersTable/UsersTable.tsx index 2d0e78ff..2a03fafe 100644 --- a/src/custom/UsersTable/UsersTable.tsx +++ b/src/custom/UsersTable/UsersTable.tsx @@ -174,6 +174,10 @@ const UsersTable: React.FC = ({ download: false, elevation: 0, serverSide: true, + sortOrder: { + name: 'last_login_time', + direction: 'desc' + }, onTableChange: (action: string, tableState: any) => { const sortInfo = tableState.announceText ? tableState.announceText.split(' : ') : []; let order = ''; diff --git a/src/custom/Workspaces/EnvironmentTable.tsx b/src/custom/Workspaces/EnvironmentTable.tsx index 765c0f47..b09bf023 100644 --- a/src/custom/Workspaces/EnvironmentTable.tsx +++ b/src/custom/Workspaces/EnvironmentTable.tsx @@ -196,6 +196,10 @@ const EnvironmentTable: React.FC = ({ rowsPerPage: pageSize, page, elevation: 0, + sortOrder: { + name: 'updated_at', + direction: 'desc' + }, serverSide: true, onTableChange: (action: string, tableState: any) => { const sortInfo = tableState.announceText ? tableState.announceText.split(' : ') : []; From fc59b7fb791b0beb386fde0b3fb3e62723e4f7fa Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Tue, 26 Nov 2024 12:03:53 +0530 Subject: [PATCH 19/19] fix: types Signed-off-by: Amit Amrutiya --- src/constants/constants.ts | 2 +- .../CatalogDesignTable/CatalogDesignTable.tsx | 5 ++-- src/custom/CatalogDesignTable/helper.ts | 6 +++++ src/custom/CatalogDesignTable/helper.tsx | 9 ------- src/custom/ResponsiveDataTable.tsx | 8 +++--- src/custom/TeamTable/TeamTable.tsx | 3 ++- .../TeamTable/TeamTableConfiguration.tsx | 26 ++++++++++--------- src/custom/UsersTable/UsersTable.tsx | 17 ++++++------ src/custom/Workspaces/EnvironmentTable.tsx | 6 ++--- src/custom/Workspaces/types.ts | 6 +++++ 10 files changed, 48 insertions(+), 40 deletions(-) create mode 100644 src/custom/CatalogDesignTable/helper.ts delete mode 100644 src/custom/CatalogDesignTable/helper.tsx diff --git a/src/constants/constants.ts b/src/constants/constants.ts index 148e8aaf..acd42608 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -9,5 +9,5 @@ export const DEFAULT_STROKE_WIDTH = '2'; export const CLOUD_URL = 'https://cloud.layer5.io'; export const PLAYGROUND_MODES = { DESIGNER: 'design', - VISUALIZER: 'visualize' + OPERATOR: 'operator' } as const; diff --git a/src/custom/CatalogDesignTable/CatalogDesignTable.tsx b/src/custom/CatalogDesignTable/CatalogDesignTable.tsx index b6e63a17..064015e4 100644 --- a/src/custom/CatalogDesignTable/CatalogDesignTable.tsx +++ b/src/custom/CatalogDesignTable/CatalogDesignTable.tsx @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import _ from 'lodash'; +import { MUIDataTableColumn } from 'mui-datatables'; import { useCallback, useMemo, useRef } from 'react'; import { PublishIcon } from '../../icons'; import { CHARCOAL, useTheme } from '../../theme'; @@ -14,7 +15,7 @@ import UnpublishTooltipIcon from './UnpublishTooltipIcon'; interface CatalogDesignsTableProps { patterns: Pattern[]; filter: any; - columns: Array; + columns: MUIDataTableColumn[]; totalCount: number; sortOrder: string; setSortOrder: (order: string) => void; @@ -63,7 +64,7 @@ export const CatalogDesignsTable: React.FC = ({ return new Date(date).toLocaleDateString('en-US', dateOptions); }, []); - const processedColumns = useMemo(() => { + const processedColumns: MUIDataTableColumn[] = useMemo(() => { return columns.map((col) => { const newCol = { ...col }; if (!newCol.options) newCol.options = {}; diff --git a/src/custom/CatalogDesignTable/helper.ts b/src/custom/CatalogDesignTable/helper.ts new file mode 100644 index 00000000..827fd086 --- /dev/null +++ b/src/custom/CatalogDesignTable/helper.ts @@ -0,0 +1,6 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export const getColumnValue = (tableMeta: any, targetColumn: string): any => { + const rowData = tableMeta.tableData[tableMeta.rowIndex]; + return (rowData as any)[targetColumn] || ''; +}; diff --git a/src/custom/CatalogDesignTable/helper.tsx b/src/custom/CatalogDesignTable/helper.tsx deleted file mode 100644 index a80b784b..00000000 --- a/src/custom/CatalogDesignTable/helper.tsx +++ /dev/null @@ -1,9 +0,0 @@ -/* 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/ResponsiveDataTable.tsx b/src/custom/ResponsiveDataTable.tsx index d6fad434..489abb38 100644 --- a/src/custom/ResponsiveDataTable.tsx +++ b/src/custom/ResponsiveDataTable.tsx @@ -1,5 +1,5 @@ import { Theme, ThemeProvider, createTheme, styled } from '@mui/material'; -import MUIDataTable from 'mui-datatables'; +import MUIDataTable, { MUIDataTableColumn } from 'mui-datatables'; import React, { useCallback } from 'react'; import { Checkbox, Collapse, ListItemIcon, ListItemText, Menu, MenuItem } from '../base'; import { ShareIcon } from '../icons'; @@ -278,10 +278,10 @@ export interface Column { export interface ResponsiveDataTableProps { data: string[][]; - columns: Column[]; + columns: MUIDataTableColumn[]; options?: object; - tableCols?: Column[]; - updateCols?: ((columns: Column[]) => void) | undefined; + tableCols?: MUIDataTableColumn[]; + updateCols?: ((columns: MUIDataTableColumn[]) => void) | undefined; columnVisibility: Record | undefined; theme?: object; colViews?: ColView[]; diff --git a/src/custom/TeamTable/TeamTable.tsx b/src/custom/TeamTable/TeamTable.tsx index 47c1a753..cfa5de4a 100644 --- a/src/custom/TeamTable/TeamTable.tsx +++ b/src/custom/TeamTable/TeamTable.tsx @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Grid, TableCell } from '@mui/material'; +import { MUIDataTableColumn } from 'mui-datatables'; import { ErrorBoundary } from '../ErrorBoundary/ErrorBoundary.js'; import { ColView } from '../Helpers/ResponsiveColumns/responsive-coulmns.tsx/index.js'; import ResponsiveDataTable from '../ResponsiveDataTable.js'; @@ -11,7 +12,7 @@ interface TeamTableProps { columnVisibility: Record; colViews: ColView[]; tableCols: any[]; - columns: any[]; + columns: MUIDataTableColumn[]; updateCols: (cols: any) => void; isRemoveFromTeamAllowed: boolean; org_id: string; diff --git a/src/custom/TeamTable/TeamTableConfiguration.tsx b/src/custom/TeamTable/TeamTableConfiguration.tsx index 0b8db7c9..8ebd6321 100644 --- a/src/custom/TeamTable/TeamTableConfiguration.tsx +++ b/src/custom/TeamTable/TeamTableConfiguration.tsx @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import { MUIDataTableColumn, MUIDataTableMeta } from 'mui-datatables'; import { useState } from 'react'; import { DeleteIcon, EditIcon } from '../../icons'; import LogoutIcon from '../../icons/Logout/LogOutIcon'; @@ -17,11 +18,12 @@ import { TableTopIcon, TableTopIconsWrapper } from '../Workspaces/styles'; +import { Team } from '../Workspaces/types'; // currently team does not support bulk team delete interface DeleteTeamsBtnProps { selected: any; - teams: any[]; + teams: Team[]; deleteTeamsModalHandler: (deleteTeams: { team_ids: string[]; team_names: string[] }) => void; } @@ -55,7 +57,7 @@ function DeleteTeamsBtn({ selected, teams, deleteTeamsModalHandler }: DeleteTeam } interface TeamTableConfigurationProps { - teams: any[]; + teams: Team[]; count: number; page: number; pageSize: number; @@ -65,9 +67,9 @@ interface TeamTableConfigurationProps { setSortOrder: (sortOrder: string) => void; bulkSelect: boolean; setBulkSelect: (bulkSelect: boolean) => void; - handleTeamView: (ev: any, rowData: any) => void; - handleDeleteTeam: (ev: any, rowData: any) => void; - handleleaveTeam: (ev: any, rowData: any) => void; + handleTeamView: (ev: React.MouseEvent, rowData: any) => void; + handleDeleteTeam: (ev: React.MouseEvent, rowData: any) => void; + handleleaveTeam: (ev: React.MouseEvent, rowData: any) => void; handleRemoveTeamFromWorkspace?: (rowData: any) => void; teamId: string; workspace?: boolean; @@ -114,7 +116,7 @@ export default function TeamTableConfiguration({ ['actions', 'xs'] ]; - const columns = [ + const columns: MUIDataTableColumn[] = [ { name: 'id', label: 'ID', @@ -122,7 +124,7 @@ export default function TeamTableConfiguration({ filter: false, sort: false, searchable: false, - customBodyRender: (value: any) => + customBodyRender: (value: string) => } }, { @@ -141,7 +143,7 @@ export default function TeamTableConfiguration({ filter: false, sort: true, searchable: false, - customBodyRender: (value: any) => + customBodyRender: (value: string) => } }, { @@ -171,10 +173,10 @@ export default function TeamTableConfiguration({ sort: true, searchable: false, sortDescFirst: true, - customBodyRender: (v: any) => JSON.stringify(v), + customBodyRender: (v: string) => JSON.stringify(v), filterOptions: { names: ['Deleted', 'Not Deleted'], - logic: (val: any, filters: any) => { + logic: (val: string, filters: any) => { if (val != 'NA' && filters.indexOf('Deleted') >= 0) return true; else if (val == 'NA' && filters.indexOf('Not Deleted') >= 0) return true; return false; @@ -190,7 +192,7 @@ export default function TeamTableConfiguration({ filter: false, sort: false, searchable: false, - customBodyRender: (_: any, tableMeta: any) => { + customBodyRender: (_: string, tableMeta: MUIDataTableMeta) => { if (bulkSelect || tableMeta.rowData[4].Valid) { return ( @@ -251,7 +253,7 @@ export default function TeamTableConfiguration({ { + onClick={(ev: React.MouseEvent) => { isDeleteTeamAllowed && handleDeleteTeam(ev, tableMeta.rowData); }} iconType="delete" diff --git a/src/custom/UsersTable/UsersTable.tsx b/src/custom/UsersTable/UsersTable.tsx index 2a03fafe..16d991b8 100644 --- a/src/custom/UsersTable/UsersTable.tsx +++ b/src/custom/UsersTable/UsersTable.tsx @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import { MUIDataTableColumn, MUIDataTableMeta } from 'mui-datatables'; import { useRef, useState } from 'react'; import { Avatar, Box, Grid, Tooltip, Typography } from '../../base'; import { EditIcon, PersonIcon } from '../../icons'; @@ -18,7 +19,7 @@ import { parseDeletionTimestamp } from '../Workspaces/helper'; import { TableIconsContainer, TableIconsDisabledContainer } from '../Workspaces/styles'; interface ActionButtonsProps { - tableMeta: any; + tableMeta: MUIDataTableMeta; isRemoveFromTeamAllowed: boolean; handleRemoveFromTeam: (data: any[]) => () => void; } @@ -121,7 +122,7 @@ const UsersTable: React.FC = ({ } }; - const getValidColumnValue = (rowData: any, columnName: string, columns: any) => { + const getValidColumnValue = (rowData: any, columnName: string, columns: MUIDataTableColumn[]) => { const columnIndex = columns.findIndex((column: any) => column.name === columnName); return rowData[columnIndex]; }; @@ -233,7 +234,7 @@ const UsersTable: React.FC = ({ // ["actions", "xs"] ]; - const columns: any[] = [ + const columns: MUIDataTableColumn[] = [ { name: 'user_id', label: 'User ID', @@ -250,7 +251,7 @@ const UsersTable: React.FC = ({ filter: false, sort: false, searchable: false, - customBodyRender: (value: string, tableMeta: any) => ( + customBodyRender: (value: string, tableMeta: MUIDataTableMeta) => ( img': { mr: 2, flexShrink: 0 } }}> @@ -295,7 +296,7 @@ const UsersTable: React.FC = ({ filter: false, sort: true, searchable: true, - customBodyRender: (value: string, tableMeta: any) => ( + customBodyRender: (value: string, tableMeta: MUIDataTableMeta) => (
{value} @@ -408,7 +409,7 @@ const UsersTable: React.FC = ({ }, fullWidth: true }, - customBodyRender: (value: any, tableMeta: any) => { + customBodyRender: (_: string, tableMeta: MUIDataTableMeta) => { const rowData = users[tableMeta.rowIndex]; return parseDeletionTimestamp(rowData); } @@ -421,7 +422,7 @@ const UsersTable: React.FC = ({ filter: false, sort: false, searchable: false, - customBodyRender: (_: any, tableMeta: any) => + customBodyRender: (_: string, tableMeta: MUIDataTableMeta) => getValidColumnValue(tableMeta.rowData, 'deleted_at', columns).Valid !== false ? ( = ({ } ]; - const [tableCols, updateCols] = useState(columns); + const [tableCols, updateCols] = useState(columns); const [columnVisibility] = useState>(() => { const showCols: Record = updateVisibleColumns(colViews, width); diff --git a/src/custom/Workspaces/EnvironmentTable.tsx b/src/custom/Workspaces/EnvironmentTable.tsx index b09bf023..127a1339 100644 --- a/src/custom/Workspaces/EnvironmentTable.tsx +++ b/src/custom/Workspaces/EnvironmentTable.tsx @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; -import { MUIDataTableMeta } from 'mui-datatables'; +import { MUIDataTableColumn, MUIDataTableMeta } from 'mui-datatables'; import React, { useState } from 'react'; import { Accordion, AccordionDetails, AccordionSummary, Typography } from '../../base'; import { DeleteIcon, EnvironmentIcon } from '../../icons'; @@ -80,7 +80,7 @@ const EnvironmentTable: React.FC = ({ }); const { width } = useWindowDimensions(); const [unassignEnvironmentFromWorkspace] = useUnassignEnvironmentFromWorkspaceMutation(); - const columns: any[] = [ + const columns: MUIDataTableColumn[] = [ { name: 'id', label: 'ID', @@ -150,7 +150,7 @@ const EnvironmentTable: React.FC = ({ filter: false, sort: false, searchable: false, - customBodyRender: (_: any, tableMeta: MUIDataTableMeta) => ( + customBodyRender: (_: string, tableMeta: MUIDataTableMeta) => (