diff --git a/ui/apps/everest/src/pages/databases/DbClusterView.tsx b/ui/apps/everest/src/pages/databases/DbClusterView.tsx index ab3de8692..bc5fd977c 100644 --- a/ui/apps/everest/src/pages/databases/DbClusterView.tsx +++ b/ui/apps/everest/src/pages/databases/DbClusterView.tsx @@ -38,6 +38,7 @@ import { beautifyDbTypeName, dbEngineToDbType } from '@percona/utils'; import { useNamespacePermissionsForResource } from 'hooks/rbac'; import DbActions from 'components/db-actions/db-actions'; import DbActionsModals from 'components/db-actions/db-actions-modals'; +import { EmptyState } from './emptyState/emptyState'; export const DbClusterView = () => { const [isNewClusterMode, setIsNewClusterMode] = useState(false); @@ -168,7 +169,7 @@ export const DbClusterView = () => { } state={{ isLoading: dbClustersLoading || loadingNamespaces }} columns={columns} data={tableData} diff --git a/ui/apps/everest/src/pages/databases/emptyState/emptyState.messages.tsx b/ui/apps/everest/src/pages/databases/emptyState/emptyState.messages.tsx new file mode 100644 index 000000000..718d75137 --- /dev/null +++ b/ui/apps/everest/src/pages/databases/emptyState/emptyState.messages.tsx @@ -0,0 +1,6 @@ +export const Messages = { + noDbClusters: 'You currently do not have any database cluster.', + createToStart: 'Create one to get started.', + create: 'Create Database', + contactSupport: 'Contact Percona Support', +}; diff --git a/ui/apps/everest/src/pages/databases/emptyState/emptyState.tsx b/ui/apps/everest/src/pages/databases/emptyState/emptyState.tsx new file mode 100644 index 000000000..915da9b14 --- /dev/null +++ b/ui/apps/everest/src/pages/databases/emptyState/emptyState.tsx @@ -0,0 +1,47 @@ +import { Box, Button, Divider, Link, Typography } from '@mui/material'; +import HelpIcon from '@mui/icons-material/Help'; +import AddIcon from '@mui/icons-material/Add'; +import { EmptyStateIcon } from '@percona/ui-lib'; +import { Link as MUILink } from 'react-router-dom'; +import { Messages } from './emptyState.messages'; + +const centeredContainerStyle = { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', +}; + +export const EmptyState = () => { + return ( + <> + + + + {Messages.noDbClusters} + {Messages.createToStart} + + + + + + + + + + ); +}; diff --git a/ui/packages/ui-lib/src/icons/empty-state/empty-state.tsx b/ui/packages/ui-lib/src/icons/empty-state/empty-state.tsx new file mode 100644 index 000000000..6b6347525 --- /dev/null +++ b/ui/packages/ui-lib/src/icons/empty-state/empty-state.tsx @@ -0,0 +1,114 @@ +const EmptyStateIcon = ({ w, h }: { w: string; h: string }) => { + return ( + + + + + + + + + + + + + + + + ); +}; + +export default EmptyStateIcon; diff --git a/ui/packages/ui-lib/src/icons/empty-state/index.tsx b/ui/packages/ui-lib/src/icons/empty-state/index.tsx new file mode 100644 index 000000000..60a3cde41 --- /dev/null +++ b/ui/packages/ui-lib/src/icons/empty-state/index.tsx @@ -0,0 +1 @@ +export { default as EmptyStateIcon } from './empty-state'; diff --git a/ui/packages/ui-lib/src/icons/icons.stories.tsx b/ui/packages/ui-lib/src/icons/icons.stories.tsx index e7b1007c6..a6b27d278 100644 --- a/ui/packages/ui-lib/src/icons/icons.stories.tsx +++ b/ui/packages/ui-lib/src/icons/icons.stories.tsx @@ -18,6 +18,7 @@ import { import { DatabaseIcon, NetworkNode } from './other'; import { GenericErrorIcon } from './generic-error'; import { NoMatchIcon } from './no-match'; +import { EmptyStateIcon } from './empty-state'; import { ErrorIcon, PausedIcon, @@ -75,6 +76,8 @@ const icons = { noMatch: [NoMatchIcon], + emptyState: [EmptyStateIcon], + status: [ ErrorIcon, WarningIcon, @@ -206,6 +209,31 @@ export const NoMatch: StoryObj = { }, }; +export const EmptyState: StoryObj = { + parameters: { + docs: { + description: { + story: `\`\`\`ts + + \`\`\``, + }, + }, + }, + + render: function Render() { + return ( + + {icons.emptyState.map((Icon) => ( + + + {Icon.name} + + ))} + + ); + }, +}; + export const Status: StoryObj = { parameters: { docs: { diff --git a/ui/packages/ui-lib/src/icons/index.ts b/ui/packages/ui-lib/src/icons/index.ts index 19b1a1cae..a8c2659be 100644 --- a/ui/packages/ui-lib/src/icons/index.ts +++ b/ui/packages/ui-lib/src/icons/index.ts @@ -9,3 +9,5 @@ export { NoMatchIcon } from './no-match'; export * from './generic-error'; export * from './status'; + +export * from './empty-state'; diff --git a/ui/packages/ui-lib/src/table/table.tsx b/ui/packages/ui-lib/src/table/table.tsx index 8b02ad50f..05c7b954f 100644 --- a/ui/packages/ui-lib/src/table/table.tsx +++ b/ui/packages/ui-lib/src/table/table.tsx @@ -3,12 +3,27 @@ import KeyboardDoubleArrowDownIcon from '@mui/icons-material/KeyboardDoubleArrow import MoreVertIcon from '@mui/icons-material/MoreVert'; import SearchIcon from '@mui/icons-material/Search'; import ViewColumnIcon from '@mui/icons-material/ViewColumn'; -import { Alert } from '@mui/material'; +import { Alert, Box } from '@mui/material'; import { MaterialReactTable, MRT_VisibilityState } from 'material-react-table'; import { useEffect } from 'react'; import { ICONS_OPACITY } from './table.constants'; import { TableProps } from './table.types'; import usePersistentColumnVisibility from './usePersistentColumnVisibility'; + +const noDataAlertWithMessage = (message?: string) => ( + + {message} + +); + // eslint-disable-next-line @typescript-eslint/no-explicit-any function Table>(props: TableProps) { const { @@ -23,6 +38,7 @@ function Table>(props: TableProps) { tableName, state, initialState, + emptyState, ...rest } = props; const [columnVisibility, setColumnVisibility] = @@ -89,20 +105,16 @@ function Table>(props: TableProps) { return ( ( - + <> {/* This means there was data before filtering, so we show the message of empty filtering result */} - {getPreFilteredRowModel().rows.length > 0 - ? emptyFilterResultsMessage - : noDataMessage} - + {getPreFilteredRowModel().rows.length > 0 ? ( + noDataAlertWithMessage(emptyFilterResultsMessage) + ) : emptyState ? ( + {emptyState} + ) : ( + noDataAlertWithMessage(noDataMessage) + )} + )} layoutMode="grid" enablePagination={data.length > 10} diff --git a/ui/packages/ui-lib/src/table/table.types.ts b/ui/packages/ui-lib/src/table/table.types.ts index c8eaacad1..82777a68f 100644 --- a/ui/packages/ui-lib/src/table/table.types.ts +++ b/ui/packages/ui-lib/src/table/table.types.ts @@ -17,8 +17,9 @@ import { type MaterialReactTableProps } from 'material-react-table'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export interface TableProps> extends MaterialReactTableProps { - noDataMessage: string; + noDataMessage?: string; emptyFilterResultsMessage?: string; hideExpandAllIcon?: boolean; tableName: string; + emptyState?: React.ReactNode; }