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;