diff --git a/src/constants/constants.ts b/src/constants/constants.ts index e65be201..acd42608 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', + OPERATOR: 'operator' +} as const; diff --git a/src/custom/CatalogDesignTable/CatalogDesignTable.tsx b/src/custom/CatalogDesignTable/CatalogDesignTable.tsx index 42d9c111..064015e4 100644 --- a/src/custom/CatalogDesignTable/CatalogDesignTable.tsx +++ b/src/custom/CatalogDesignTable/CatalogDesignTable.tsx @@ -1,10 +1,12 @@ /* 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'; 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'; @@ -13,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; @@ -22,8 +24,9 @@ interface CatalogDesignsTableProps { page: number; setPage: (page: number) => void; columnVisibility: Record; - colViews: Record | undefined; + colViews: ColView[]; handleBulkDeleteModal: (patterns: Pattern[], modalRef: React.RefObject) => void; + setSearch?: (search: string) => void; handleBulkpatternsDataUnpublishModal: ( selected: any, patterns: Pattern[], @@ -43,8 +46,9 @@ export const CatalogDesignsTable: React.FC = ({ page = 0, setPage, columnVisibility = {}, - colViews = {}, + colViews = [], handleBulkDeleteModal, + setSearch, handleBulkpatternsDataUnpublishModal }) => { const theme = useTheme(); @@ -60,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 = {}; @@ -105,6 +109,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 && @@ -123,7 +130,7 @@ export const CatalogDesignsTable: React.FC = ({ break; } }, - [columns, setPage, setPageSize, setSortOrder, sortOrder] + [columns, setPage, setSearch, setPageSize, setSortOrder, sortOrder] ); const options = useMemo( @@ -136,6 +143,11 @@ export const CatalogDesignsTable: React.FC = ({ rowsPerPage: pageSize, page, elevation: 0, + sortOrder: { + name: 'updated_at', + direction: 'desc' + }, + onTableChange: handleTableChange, customToolbarSelect: _.isNil(filter) ? (selected: any) => ( diff --git a/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx b/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx new file mode 100644 index 00000000..06c092e9 --- /dev/null +++ b/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx @@ -0,0 +1,236 @@ +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 { ColView } from '../Helpers/ResponsiveColumns/responsive-coulmns.tsx'; +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; + isDownloadAllowed: boolean; + isCopyLinkAllowed: boolean; + isDeleteAllowed: boolean; + isPublishAllowed: boolean; + isUnpublishAllowed: boolean; + // for workspace designs table page only + isFromWorkspaceTable?: boolean; + isRemoveAllowed?: boolean; +} + +export const colViews: ColView[] = [ + ['id', 'na'], + ['name', 'xs'], + ['first_name', 'xs'], + ['created_at', 'na'], + ['updated_at', 'l'], + ['visibility', 'l'], + ['user_id', 'na'], + ['actions', 'xs'] +]; + +export const createDesignsColumnsConfig = ({ + handleDeleteModal, + handlePublishModal, + handleUnpublishModal, + handleCopyUrl, + handleClone, + handleShowDetails, + isUnpublishAllowed, + isCopyLinkAllowed, + isDeleteAllowed, + isPublishAllowed, + isDownloadAllowed, + isRemoveAllowed, + isFromWorkspaceTable = false +}: 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: 'user_id', + label: 'User ID', + options: { + filter: false, + sort: false, + searchable: false + } + }, + { + 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: !isDownloadAllowed, + icon: + }, + { + title: 'Copy Link', + disabled: rowData.visibility === 'private' || !isCopyLinkAllowed, + 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: isFromWorkspaceTable ? 'Remove Design' : 'Delete', + disabled: isFromWorkspaceTable ? !isRemoveAllowed : !isDeleteAllowed, + onClick: () => handleDeleteModal(rowData)(), + icon: + } + ]; + + const publishAction = { + title: 'Publish', + disabled: !isPublishAllowed, + onClick: () => handlePublishModal(rowData), + icon: + }; + + const unpublishAction = { + title: 'Unpublish', + onClick: () => handleUnpublishModal(rowData)(), + disabled: !isUnpublishAllowed, + 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..bcdf1c45 100644 --- a/src/custom/CatalogDesignTable/columnConfig.tsx +++ b/src/custom/CatalogDesignTable/columnConfig.tsx @@ -17,12 +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'], @@ -83,12 +83,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.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/index.ts b/src/custom/CatalogDesignTable/index.ts index 98c1393f..b1f9a4aa 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 './DesignTableColumnConfig'; 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/custom/CustomColumnVisibilityControl/CustomColumnVisibilityControl.tsx b/src/custom/CustomColumnVisibilityControl/CustomColumnVisibilityControl.tsx index 7541891d..bb420e38 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'; @@ -7,10 +8,10 @@ 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: CustomColumn[]; + columns: MUIDataTableColumn[]; customToolsProps: { columnVisibility: Record; setColumnVisibility: React.Dispatch>>; 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 }; 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..489abb38 100644 --- a/src/custom/ResponsiveDataTable.tsx +++ b/src/custom/ResponsiveDataTable.tsx @@ -1,10 +1,11 @@ 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'; import { EllipsisIcon } from '../icons/Ellipsis'; -import TooltipIcon from './TooltipIcon'; +import { ColView } from './Helpers/ResponsiveColumns/responsive-coulmns.tsx'; +import { TooltipIcon } from './TooltipIconButton'; export const IconWrapper = styled('div')<{ disabled?: boolean }>(({ disabled = false }) => ({ cursor: disabled ? 'not-allowed' : 'pointer', @@ -277,13 +278,13 @@ 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?: Record | undefined; + colViews?: ColView[]; rowsPerPageOptions?: number[] | undefined; backgroundColor?: string; } 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/TeamTable/TeamTable.tsx b/src/custom/TeamTable/TeamTable.tsx new file mode 100644 index 00000000..cfa5de4a --- /dev/null +++ b/src/custom/TeamTable/TeamTable.tsx @@ -0,0 +1,89 @@ +/* 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'; +import UsersTable from '../UsersTable/UsersTable.js'; + +interface TeamTableProps { + teams: any; + tableOptions: object; + columnVisibility: Record; + colViews: ColView[]; + tableCols: any[]; + columns: MUIDataTableColumn[]; + 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..8ebd6321 --- /dev/null +++ b/src/custom/TeamTable/TeamTableConfiguration.tsx @@ -0,0 +1,412 @@ +/* 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'; +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'; +import { Team } from '../Workspaces/types'; + +// currently team does not support bulk team delete +interface DeleteTeamsBtnProps { + selected: any; + teams: Team[]; + 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: Team[]; + 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: 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; + 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: MUIDataTableColumn[] = [ + { + name: 'id', + label: 'ID', + options: { + filter: false, + sort: false, + searchable: false, + customBodyRender: (value: string) => + } + }, + { + name: 'name', + label: 'Name', + options: { + filter: false, + sort: true, + searchable: true + } + }, + { + name: 'description', + label: 'Description', + options: { + filter: false, + sort: true, + searchable: false, + customBodyRender: (value: string) => + } + }, + { + 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: string) => JSON.stringify(v), + filterOptions: { + names: ['Deleted', 'Not Deleted'], + 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; + }, + fullWidth: true + } + } + }, + { + name: 'actions', + label: 'Actions', + options: { + filter: false, + sort: false, + searchable: false, + customBodyRender: (_: string, tableMeta: MUIDataTableMeta) => { + 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' + } + }, + sortOrder: { + name: 'created_at', + direction: 'desc' + }, + 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 }; 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} + + ); } 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/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/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; diff --git a/src/custom/UsersTable/UsersTable.tsx b/src/custom/UsersTable/UsersTable.tsx new file mode 100644 index 00000000..16d991b8 --- /dev/null +++ b/src/custom/UsersTable/UsersTable.tsx @@ -0,0 +1,479 @@ +/* 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'; +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: MUIDataTableMeta; + 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: MUIDataTableColumn[]) => { + 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, + sortOrder: { + name: 'last_login_time', + direction: 'desc' + }, + 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: MUIDataTableColumn[] = [ + { + 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: MUIDataTableMeta) => ( + 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: MUIDataTableMeta) => ( +
+ {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: (_: string, tableMeta: MUIDataTableMeta) => { + const rowData = users[tableMeta.rowIndex]; + return parseDeletionTimestamp(rowData); + } + } + }, + { + name: 'actions', + label: 'Actions', + options: { + filter: false, + sort: false, + searchable: false, + customBodyRender: (_: string, tableMeta: MUIDataTableMeta) => + 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 }; diff --git a/src/custom/Workspaces/AssignmentModal.tsx b/src/custom/Workspaces/AssignmentModal.tsx new file mode 100644 index 00000000..4fc4d865 --- /dev/null +++ b/src/custom/Workspaces/AssignmentModal.tsx @@ -0,0 +1,87 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Modal, ModalBody, ModalButtonPrimary, ModalButtonSecondary, ModalFooter } from '../Modal'; +import { TransferList } from '../TransferModal/TransferList'; +import { ModalActionDiv } from './styles'; + +interface AssignmentModalProps { + open: boolean; + onClose: (e?: React.MouseEvent) => void; + title: string; + headerIcon: JSX.Element; + name: string; + assignableData: any[]; + handleAssignedData: (data: any) => void; + originalAssignedData: any[]; + emptyStateIcon: JSX.Element; + handleAssignablePage: () => void; + handleAssignedPage: () => void; + originalLeftCount: number; + originalRightCount: number; + onAssign: () => void; + disableTransfer: boolean; + isAssignAllowed: boolean; + isRemoveAllowed: boolean; + helpText: string; +} + +const AssignmentModal: React.FC = ({ + open, + onClose, + title, + headerIcon, + name, + assignableData, + handleAssignedData, + originalAssignedData, + emptyStateIcon, + handleAssignablePage, + handleAssignedPage, + originalLeftCount, + originalRightCount, + onAssign, + disableTransfer, + isAssignAllowed, + isRemoveAllowed, + helpText +}) => { + return ( + + + + + + + Cancel + + Save + + + + + ); +}; + +export default AssignmentModal; diff --git a/src/custom/Workspaces/DesignTable.tsx b/src/custom/Workspaces/DesignTable.tsx new file mode 100644 index 00000000..d0d1f3e2 --- /dev/null +++ b/src/custom/Workspaces/DesignTable.tsx @@ -0,0 +1,279 @@ +/* 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 SearchBar from '../SearchBar'; +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; + GenericRJSFModal: any; + isDownloadAllowed: boolean; + isCopyLinkAllowed: boolean; + isDeleteAllowed: boolean; + isPublishAllowed: boolean; + isUnpublishAllowed: boolean; + isAssignAllowed: boolean; + isRemoveAllowed: boolean; + setDesignSearch: (value: string) => void; +} + +export interface PublishModalState { + open: boolean; + pattern: Partial; +} + +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, + isCopyLinkAllowed, + isDeleteAllowed, + isDownloadAllowed, + isPublishAllowed, + isUnpublishAllowed, + useAssignDesignToWorkspaceMutation, + useUnassignDesignFromWorkspaceMutation, + GenericRJSFModal, + isAssignAllowed, + isRemoveAllowed, + useGetWorkspaceDesignsQuery, + setDesignSearch +}) => { + 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 [isSearchExpanded, setIsSearchExpanded] = useState(false); + + 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, + isCopyLinkAllowed, + isDeleteAllowed, + isDownloadAllowed, + isPublishAllowed, + isUnpublishAllowed, + isFromWorkspaceTable: true, + isRemoveAllowed + }); + + const [publishSchema, setPublishSchema] = useState<{ + rjsfSchema: any; + uiSchema: any; + }>({ + rjsfSchema: {}, + uiSchema: {} + }); + + const { width } = useWindowDimensions(); + const [columnVisibility, setColumnVisibility] = useState>(() => { + const showCols = updateVisibleColumns(designColumnsColViews, width); + const initialVisibility: Record = {}; + 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 designAssignment = useDesignAssignment({ + workspaceId, + useAssignDesignToWorkspaceMutation, + useUnassignDesignFromWorkspaceMutation, + useGetDesignsOfWorkspaceQuery: useGetWorkspaceDesignsQuery + }); + + const tableHeaderContent = ( + + + Assigned Designs + + + { + setDesignSearch(value); + }} + onClear={() => { + setDesignSearch(''); + }} + expanded={isSearchExpanded} + setExpanded={setIsSearchExpanded} + placeholder="Search designs..." + /> + + + + + ); + + return ( + + + } + sx={{ + backgroundColor: 'background.paper' + }} + > + {tableHeaderContent} + + + + handleBulkWorkspaceDesignDeleteModal(designs, modalRef, workspaceName, workspaceId) + } + filter={'my-designs'} + setSearch={setDesignSearch} + /> + + + } + name="Designs" + assignableData={designAssignment.data} + handleAssignedData={designAssignment.handleAssignData} + originalAssignedData={designAssignment.workspaceData} + emptyStateIcon={} + 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}`} + isAssignAllowed={isAssignAllowed} + isRemoveAllowed={isRemoveAllowed} + /> + 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/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/EnvironmentTable.tsx b/src/custom/Workspaces/EnvironmentTable.tsx new file mode 100644 index 00000000..127a1339 --- /dev/null +++ b/src/custom/Workspaces/EnvironmentTable.tsx @@ -0,0 +1,315 @@ +/* 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 { 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 useEnvironmentAssignment from './hooks/useEnvironmentAssignment'; +import { CellStyle, CustomBodyRenderStyle, TableHeader, TableRightActionHeader } from './styles'; + +interface EnvironmentTableProps { + workspaceId: string; + workspaceName: string; + useGetEnvironmentsOfWorkspaceQuery: any; + useUnassignEnvironmentFromWorkspaceMutation: any; + useAssignEnvironmentToWorkspaceMutation: any; + isRemoveAllowed: boolean; + isAssignAllowed: 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, + isRemoveAllowed, + useGetEnvironmentsOfWorkspaceQuery, + useUnassignEnvironmentFromWorkspaceMutation, + useAssignEnvironmentToWorkspaceMutation, + 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(''); + const { data: environmentsOfWorkspace } = useGetEnvironmentsOfWorkspaceQuery({ + workspaceId, + page: page, + pageSize: pageSize, + search: search, + order: sortOrder + }); + const { width } = useWindowDimensions(); + const [unassignEnvironmentFromWorkspace] = useUnassignEnvironmentFromWorkspaceMutation(); + const columns: MUIDataTableColumn[] = [ + { + 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: (_: string, tableMeta: MUIDataTableMeta) => ( + + { + isRemoveAllowed && + 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: Record = {}; + 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, + sortOrder: { + name: 'updated_at', + direction: 'desc' + }, + 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': + 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; + } + } + }; + const [tableCols, updateCols] = useState(columns); + + return ( + + + } + sx={{ + backgroundColor: 'background.paper' + }} + > + + + Assigned Environments + + + { + setSearch(value); + }} + onClear={() => { + setSearch(''); + }} + expanded={isSearchExpanded} + setExpanded={setIsSearchExpanded} + placeholder="Search workspaces..." + /> + + + + + + + + + + + } + 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}`} + isAssignAllowed={isAssignAllowed} + isRemoveAllowed={isRemoveAllowed} + /> + + ); +}; + +export default EnvironmentTable; 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/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/hooks/useDesignAssignment.tsx b/src/custom/Workspaces/hooks/useDesignAssignment.tsx new file mode 100644 index 00000000..9931f581 --- /dev/null +++ b/src/custom/Workspaces/hooks/useDesignAssignment.tsx @@ -0,0 +1,152 @@ +/* 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); + + addedDesignsIds.map((id) => + assignDesignToWorkspace({ + workspaceId, + designId: id + }).unwrap() + ); + + removedDesignsIds.map((id) => + unassignDesignFromWorkspace({ + workspaceId, + designId: id + }).unwrap() + ); + + setDesignsData([]); + setWorkspaceDesignsData([]); + setDesignsPage(0); + setDesignsOfWorkspacePage(0); + 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; diff --git a/src/custom/Workspaces/hooks/useEnvironmentAssignment.tsx b/src/custom/Workspaces/hooks/useEnvironmentAssignment.tsx new file mode 100644 index 00000000..155f28c3 --- /dev/null +++ b/src/custom/Workspaces/hooks/useEnvironmentAssignment.tsx @@ -0,0 +1,159 @@ +/* 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 [disableTransferButton, setDisableTransferButton] = useState(true); + const [assignedEnvironments, setAssignedEnvironments] = useState([]); + + 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(); + + 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 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); + + addedEnvironmentsIds.map((id) => + assignEnvironmentToWorkspace({ + workspaceId, + environmentId: id + }).unwrap() + ); + + removedEnvironmentsIds.map((id) => + unassignEnvironmentFromWorkspace({ + workspaceId, + environmentId: id + }).unwrap() + ); + + setEnvironmentsData([]); + setWorkspaceEnvironmentsData([]); + setEnvironmentsPage(0); + setEnvironmentsOfWorkspacePage(0); + handleAssignEnvironmentModalClose(); + }; + + 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/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; diff --git a/src/custom/Workspaces/index.ts b/src/custom/Workspaces/index.ts new file mode 100644 index 00000000..6a0dfa9d --- /dev/null +++ b/src/custom/Workspaces/index.ts @@ -0,0 +1,17 @@ +import AssignmentModal from './AssignmentModal'; +import DesignTable from './DesignTable'; +import EnvironmentTable from './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 new file mode 100644 index 00000000..c26b225a --- /dev/null +++ b/src/custom/Workspaces/styles.ts @@ -0,0 +1,112 @@ +import EditIcon from '@mui/icons-material/Edit'; +import { buttonDisabled, styled } from '../../theme'; +import { KEPPEL } 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 : KEPPEL, + '& svg': { + color: disabled ? buttonDisabled : KEPPEL + } + }, + '& svg': { + color: theme.palette.error.main, + cursor: disabled ? 'not-allowed' : 'pointer' + }, + ...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' +}); + +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%' +}); + +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 new file mode 100644 index 00000000..5ad04b8c --- /dev/null +++ b/src/custom/Workspaces/types.ts @@ -0,0 +1,44 @@ +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; + team_id: string; + description?: string; + team_name: string; + deleted_at: { + Valid: boolean; + }; +} diff --git a/src/custom/index.tsx b/src/custom/index.tsx index ded1e4f5..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, @@ -136,4 +146,5 @@ export type { export * from './CatalogDesignTable'; export * from './CatalogDetail'; export * from './Dialog'; +export * from './Workspaces'; export * from './permissions'; 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/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 }; 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'; 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'