From 01248f4ee6e03d6c6e21c93b4272a9d7b494244f Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Thu, 23 Jan 2025 09:24:39 +0530 Subject: [PATCH 1/2] feat: add support for views to workspace Signed-off-by: Amit Amrutiya --- .../TransferList/TransferList.tsx | 2 +- src/custom/Workspaces/AssignmentModal.tsx | 59 ++++++- src/custom/Workspaces/DesignTable.tsx | 1 + .../Workspaces/hooks/useDesignAssignment.tsx | 6 + .../Workspaces/hooks/useViewsAssignment.tsx | 158 ++++++++++++++++++ src/custom/Workspaces/index.ts | 6 +- src/custom/Workspaces/types.ts | 1 + 7 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 src/custom/Workspaces/hooks/useViewsAssignment.tsx diff --git a/src/custom/TransferModal/TransferList/TransferList.tsx b/src/custom/TransferModal/TransferList/TransferList.tsx index 40098feec..3999ecb06 100644 --- a/src/custom/TransferModal/TransferList/TransferList.tsx +++ b/src/custom/TransferModal/TransferList/TransferList.tsx @@ -29,7 +29,7 @@ export function getFallbackImageBasedOnKind(kind: string | undefined): JSX.Eleme } export interface TransferListProps { - name: string; + name?: string; assignableData: ListItemType[]; assignedData: (data: ListItemType[]) => void; originalAssignedData: ListItemType[]; diff --git a/src/custom/Workspaces/AssignmentModal.tsx b/src/custom/Workspaces/AssignmentModal.tsx index 4fc4d8652..0274426b0 100644 --- a/src/custom/Workspaces/AssignmentModal.tsx +++ b/src/custom/Workspaces/AssignmentModal.tsx @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import { Divider } from '../../base'; import { Modal, ModalBody, ModalButtonPrimary, ModalButtonSecondary, ModalFooter } from '../Modal'; import { TransferList } from '../TransferModal/TransferList'; import { ModalActionDiv } from './styles'; @@ -22,6 +23,21 @@ interface AssignmentModalProps { isAssignAllowed: boolean; isRemoveAllowed: boolean; helpText: string; + showViews?: boolean; + nameViews?: string; + assignableViewsData?: any[]; + handleAssignedViewsData?: (data: any) => void; + originalAssignedViewsData?: any[]; + + emptyStateViewsIcon?: JSX.Element; + handleAssignableViewsPage?: () => void; + handleAssignedViewsPage?: () => void; + originalLeftViewsCount?: number; + originalRightViewsCount?: number; + onAssignViews?: () => void; + disableTransferViews?: boolean; + isAssignAllowedViews?: boolean; + isRemoveAllowedViews?: boolean; } const AssignmentModal: React.FC = ({ @@ -42,7 +58,19 @@ const AssignmentModal: React.FC = ({ disableTransfer, isAssignAllowed, isRemoveAllowed, - helpText + helpText, + showViews, + nameViews, + assignableViewsData, + handleAssignedViewsData, + originalAssignedViewsData, + emptyStateViewsIcon, + handleAssignableViewsPage, + handleAssignedViewsPage, + originalLeftViewsCount, + originalRightViewsCount, + isAssignAllowedViews = false, + isRemoveAllowedViews = false }) => { return ( = ({ rightPermission={isRemoveAllowed} transferComponentType={''} /> + + {showViews && ( + {})} + originalAssignedData={originalAssignedViewsData || []} + emptyStateIconLeft={emptyStateViewsIcon || <>} + emtyStateMessageLeft={`No views available`} + emptyStateIconRight={emptyStateViewsIcon || <>} + emtyStateMessageRight={`No views assigned`} + assignablePage={handleAssignableViewsPage || (() => {})} + assignedPage={handleAssignedViewsPage || (() => {})} + originalLeftCount={originalLeftViewsCount ?? 0} + originalRightCount={originalRightViewsCount ?? 0} + leftPermission={isAssignAllowedViews} + rightPermission={isRemoveAllowedViews} + transferComponentType={''} + /> + )} Cancel - + Save diff --git a/src/custom/Workspaces/DesignTable.tsx b/src/custom/Workspaces/DesignTable.tsx index e099cc572..46c3d9cf1 100644 --- a/src/custom/Workspaces/DesignTable.tsx +++ b/src/custom/Workspaces/DesignTable.tsx @@ -264,6 +264,7 @@ const DesignTable: React.FC = ({ helpText={`Assign Designs to ${workspaceName}`} isAssignAllowed={isAssignAllowed} isRemoveAllowed={isRemoveAllowed} + showViews={false} /> { + const { addedDesignsIds, removedDesignsIds } = getAddedAndRemovedDesigns(allAssignedDesigns); + return addedDesignsIds.length > 0 || removedDesignsIds.length > 0; + }; + const handleAssignDesigns = async (): Promise => { const { addedDesignsIds, removedDesignsIds } = getAddedAndRemovedDesigns(assignedDesigns); @@ -144,6 +149,7 @@ const useDesignAssignment = ({ handleAssignedPage: handleAssignedPageDesign, handleAssign: handleAssignDesigns, handleAssignData: handleAssignDesignsData, + isActivityOccurred: isDesignsActivityOccurred, disableTransferButton, assignedItems: assignedDesigns }; diff --git a/src/custom/Workspaces/hooks/useViewsAssignment.tsx b/src/custom/Workspaces/hooks/useViewsAssignment.tsx new file mode 100644 index 000000000..6f1294b9f --- /dev/null +++ b/src/custom/Workspaces/hooks/useViewsAssignment.tsx @@ -0,0 +1,158 @@ +/* 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 AddedAndRemovedViews { + addedviewsIds: string[]; + removedviewsIds: string[]; +} + +interface useViewAssignmentProps { + workspaceId: string; + useGetViewsOfWorkspaceQuery: any; + useAssignViewToWorkspaceMutation: any; + useUnassignViewFromWorkspaceMutation: any; +} + +const useViewAssignment = ({ + workspaceId, + useGetViewsOfWorkspaceQuery, + useAssignViewToWorkspaceMutation, + useUnassignViewFromWorkspaceMutation +}: useViewAssignmentProps): AssignmentHookResult => { + const [viewsPage, setviewsPage] = useState(0); + const [viewsData, setviewsData] = useState([]); + const viewsPageSize = 25; + const [viewsOfWorkspacePage, setviewsOfWorkspacePage] = useState(0); + const [workspaceviewsData, setWorkspaceviewsData] = useState([]); + const [assignviewModal, setAssignviewModal] = useState(false); + const [skipviews, setSkipviews] = useState(true); + const [disableTransferButton, setDisableTransferButton] = useState(true); + const [assignedviews, setAssignedviews] = useState([]); + + const { data: views } = useGetViewsOfWorkspaceQuery( + withDefaultPageArgs({ + workspaceId, + page: viewsPage, + pagesize: viewsPageSize, + filter: '{"assigned":false}' + }), + { + skip: skipviews + } + ); + + const { data: viewsOfWorkspace } = useGetViewsOfWorkspaceQuery( + withDefaultPageArgs({ + workspaceId, + page: viewsOfWorkspacePage, + pagesize: viewsPageSize + }), + { + skip: skipviews + } + ); + + const [assignviewToWorkspace] = useAssignViewToWorkspaceMutation(); + const [unassignviewFromWorkspace] = useUnassignViewFromWorkspaceMutation(); + + useEffect(() => { + const viewsDataRtk = views?.views ? views.views : []; + setviewsData((prevData) => [...prevData, ...viewsDataRtk]); + }, [views]); + + useEffect(() => { + const viewsOfWorkspaceDataRtk = viewsOfWorkspace?.views ? viewsOfWorkspace.views : []; + setWorkspaceviewsData((prevData) => [...prevData, ...viewsOfWorkspaceDataRtk]); + }, [viewsOfWorkspace]); + + const handleAssignviewModal = (e?: React.MouseEvent): void => { + e?.stopPropagation(); + setAssignviewModal(true); + setSkipviews(false); + }; + + const handleAssignviewModalClose = (e?: React.MouseEvent): void => { + e?.stopPropagation(); + setAssignviewModal(false); + setSkipviews(true); + }; + + const handleAssignablePageview = (): void => { + const pagesCount = Math.ceil(Number(views?.total_count) / viewsPageSize); + if (viewsPage < pagesCount - 1) { + setviewsPage((prevviewsPage) => prevviewsPage + 1); + } + }; + + const handleAssignedPageview = (): void => { + const pagesCount = Math.ceil(Number(viewsOfWorkspace?.total_count) / viewsPageSize); + if (viewsOfWorkspacePage < pagesCount - 1) { + setviewsOfWorkspacePage((prevPage) => prevPage + 1); + } + }; + + const getAddedAndRemovedviews = (allAssignedviews: Pattern[]): AddedAndRemovedViews => { + const originalviewsIds = workspaceviewsData.map((view) => view.id); + const updatedviewsIds = allAssignedviews.map((view) => view.id); + + const addedviewsIds = updatedviewsIds.filter((id) => !originalviewsIds.includes(id)); + const removedviewsIds = originalviewsIds.filter((id) => !updatedviewsIds.includes(id)); + + return { addedviewsIds, removedviewsIds }; + }; + + const isViewsActivityOccurred = (allViews: Pattern[]): boolean => { + const { addedviewsIds, removedviewsIds } = getAddedAndRemovedviews(allViews); + return addedviewsIds.length > 0 || removedviewsIds.length > 0; + }; + + const handleAssignviews = async (): Promise => { + const { addedviewsIds, removedviewsIds } = getAddedAndRemovedviews(assignedviews); + + addedviewsIds.map((id) => + assignviewToWorkspace({ + workspaceId, + viewId: id + }).unwrap() + ); + + removedviewsIds.map((id) => + unassignviewFromWorkspace({ + workspaceId, + viewId: id + }).unwrap() + ); + + setviewsData([]); + setWorkspaceviewsData([]); + setviewsPage(0); + setviewsOfWorkspacePage(0); + handleAssignviewModalClose(); + }; + + const handleAssignviewsData = (updatedAssignedData: Pattern[]): void => { + const { addedviewsIds, removedviewsIds } = getAddedAndRemovedviews(updatedAssignedData); + setDisableTransferButton(!(addedviewsIds.length > 0 || removedviewsIds.length > 0)); + setAssignedviews(updatedAssignedData); + }; + + return { + data: viewsData, + workspaceData: workspaceviewsData, + assignModal: assignviewModal, + handleAssignModal: handleAssignviewModal, + handleAssignModalClose: handleAssignviewModalClose, + handleAssignablePage: handleAssignablePageview, + handleAssignedPage: handleAssignedPageview, + handleAssign: handleAssignviews, + isActivityOccurred: isViewsActivityOccurred, + handleAssignData: handleAssignviewsData, + disableTransferButton, + assignedItems: assignedviews + }; +}; + +export default useViewAssignment; diff --git a/src/custom/Workspaces/index.ts b/src/custom/Workspaces/index.ts index 6a0dfa9d4..82cd1eacd 100644 --- a/src/custom/Workspaces/index.ts +++ b/src/custom/Workspaces/index.ts @@ -2,16 +2,20 @@ import AssignmentModal from './AssignmentModal'; import DesignTable from './DesignTable'; import EnvironmentTable from './EnvironmentTable'; import WorkspaceTeamsTable from './WorkspaceTeamsTable'; +import WorkspaceViewsTable from './WorkspaceViewsTable'; import useDesignAssignment from './hooks/useDesignAssignment'; import useEnvironmentAssignment from './hooks/useEnvironmentAssignment'; import useTeamAssignment from './hooks/useTeamAssignment'; +import useViewAssignment from './hooks/useViewsAssignment'; export { AssignmentModal, DesignTable, EnvironmentTable, WorkspaceTeamsTable, + WorkspaceViewsTable, useDesignAssignment, useEnvironmentAssignment, - useTeamAssignment + useTeamAssignment, + useViewAssignment }; diff --git a/src/custom/Workspaces/types.ts b/src/custom/Workspaces/types.ts index 5ad04b8cd..280c4ec97 100644 --- a/src/custom/Workspaces/types.ts +++ b/src/custom/Workspaces/types.ts @@ -8,6 +8,7 @@ export interface AssignmentHookResult { handleAssignedPage: () => void; handleAssign: () => void; handleAssignData: (data: T[]) => void; + isActivityOccurred?: (allItems: T[]) => boolean; disableTransferButton: boolean; assignedItems: T[]; } From a64543ea21b4729735425478ad2d46d79cfa8acc Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Thu, 23 Jan 2025 09:28:29 +0530 Subject: [PATCH 2/2] feat: use the views table for the workspace table view Signed-off-by: Amit Amrutiya --- src/custom/Workspaces/AssignmentModal.tsx | 2 +- src/custom/Workspaces/WorkspaceViewsTable.tsx | 312 ++++++++++++++++++ 2 files changed, 313 insertions(+), 1 deletion(-) create mode 100644 src/custom/Workspaces/WorkspaceViewsTable.tsx diff --git a/src/custom/Workspaces/AssignmentModal.tsx b/src/custom/Workspaces/AssignmentModal.tsx index 0274426b0..61d20fbe6 100644 --- a/src/custom/Workspaces/AssignmentModal.tsx +++ b/src/custom/Workspaces/AssignmentModal.tsx @@ -130,7 +130,7 @@ const AssignmentModal: React.FC = ({ Cancel - + Save diff --git a/src/custom/Workspaces/WorkspaceViewsTable.tsx b/src/custom/Workspaces/WorkspaceViewsTable.tsx new file mode 100644 index 000000000..2335dcf00 --- /dev/null +++ b/src/custom/Workspaces/WorkspaceViewsTable.tsx @@ -0,0 +1,312 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { MUIDataTableColumn, 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 { NameDiv } from '../CatalogDesignTable/style'; +import { RESOURCE_TYPES } from '../CatalogDetail/types'; +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 SearchBar from '../SearchBar'; +import { TooltipIcon } from '../TooltipIconButton'; +import AssignmentModal from './AssignmentModal'; +import EditButton from './EditButton'; +import useViewAssignment from './hooks/useViewsAssignment'; +import { CellStyle, CustomBodyRenderStyle, TableHeader, TableRightActionHeader } from './styles'; + +interface ViewsTableProps { + workspaceId: string; + workspaceName: string; + useGetViewsOfWorkspaceQuery: any; + useUnassignViewFromWorkspaceMutation: any; + useAssignViewToWorkspaceMutation: any; + isRemoveAllowed: boolean; + isAssignAllowed: boolean; + handleShowDetails: (viewId: string, viewName: string, filterType: string) => void; +} + +const colViews: ColView[] = [ + ['id', 'na'], + ['name', 'xs'], + ['description', 'm'], + ['organization_id', 'l'], + ['created_at', 'xl'], + ['updated_at', 'xl'], + ['visibility', 'l'], + ['actions', 'xs'] +]; + +export const ResizableDescriptionCell = ({ value }: { value: string }) => ( +
+ + + + {value} + + + +
+); + +const WorkspaceViewsTable: React.FC = ({ + workspaceId, + workspaceName, + isRemoveAllowed, + useGetViewsOfWorkspaceQuery, + useUnassignViewFromWorkspaceMutation, + useAssignViewToWorkspaceMutation, + isAssignAllowed, + handleShowDetails +}) => { + 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('updated_at desc'); + const { data: viewsOfWorkspace } = useGetViewsOfWorkspaceQuery({ + workspaceId, + page: page, + pageSize: pageSize, + search: search, + order: sortOrder + }); + const { width } = useWindowDimensions(); + const [unassignviewFromWorkspace] = useUnassignViewFromWorkspaceMutation(); + const columns: MUIDataTableColumn[] = [ + { + name: 'id', + label: 'ID', + options: { + filter: false, + customBodyRender: (value) => + } + }, + { + name: 'name', + label: 'Name', + options: { + filter: false, + sort: true, + searchable: true, + customBodyRender: (value, tableMeta) => { + const viewId = tableMeta.tableData[tableMeta.rowIndex]?.id ?? ''; + const viewName = tableMeta.tableData[tableMeta.rowIndex]?.name ?? ''; + return ( + handleShowDetails(viewId, viewName, RESOURCE_TYPES.VIEW)}> + {value} + + ); + } + } + }, + { + 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, + setCellHeaderProps: () => { + return { align: 'center' }; + } + } + }, + { + name: 'actions', + label: 'Actions', + options: { + filter: false, + sort: false, + searchable: false, + customBodyRender: (_: string, tableMeta: MUIDataTableMeta) => ( + + { + isRemoveAllowed && + unassignviewFromWorkspace({ + workspaceId, + viewId: tableMeta.rowData[0] + }); + }} + iconType="delete" + > + + + + ) + } + } + ]; + + const viewAssignment = useViewAssignment({ + workspaceId, + useGetViewsOfWorkspaceQuery, + useUnassignViewFromWorkspaceMutation, + useAssignViewToWorkspaceMutation + }); + + const [columnVisibility, setColumnVisibility] = useState>(() => { + const showCols = updateVisibleColumns(colViews, width); + const initialVisibility: Record = {}; + columns.forEach((col) => { + initialVisibility[col.name] = showCols[col.name]; + }); + return initialVisibility; + }); + + const options = { + filter: false, + responsive: 'standard', + selectableRows: 'none', + count: viewsOfWorkspace?.total_count, + rowsPerPage: pageSize, + page, + elevation: 0, + sortOrder: { + name: sortOrder.split(' ')[0], + direction: sortOrder.split(' ')[1] + }, + 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 Views + + + { + setSearch(value); + }} + onClear={() => { + setSearch(''); + }} + expanded={isSearchExpanded} + setExpanded={setIsSearchExpanded} + placeholder="Search workspaces..." + /> + + + + + + + + + + + } + name="Views" + assignableData={viewAssignment.data} + handleAssignedData={viewAssignment.handleAssignData} + originalAssignedData={viewAssignment.workspaceData} + emptyStateIcon={} + handleAssignablePage={viewAssignment.handleAssignablePage} + handleAssignedPage={viewAssignment.handleAssignedPage} + originalLeftCount={viewAssignment.data?.length || 0} + originalRightCount={viewsOfWorkspace?.total_count || 0} + onAssign={viewAssignment.handleAssign} + disableTransfer={viewAssignment.disableTransferButton} + helpText={`Assign Views to ${workspaceName}`} + isAssignAllowed={isAssignAllowed} + isRemoveAllowed={isRemoveAllowed} + /> + + ); +}; + +export default WorkspaceViewsTable;