2024-07-16 14:18:59 +05:30

1059 lines
29 KiB
TypeScript

/* eslint-disable no-nested-ternary */
/* eslint-disable jsx-a11y/img-redundant-alt */
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/no-static-element-interactions */
import './DashboardList.styles.scss';
import { Color } from '@signozhq/design-tokens';
import {
Button,
Dropdown,
Flex,
Input,
MenuProps,
Modal,
Popover,
Skeleton,
Switch,
Table,
Tag,
Tooltip,
Typography,
} from 'antd';
import { TableProps } from 'antd/lib';
import logEvent from 'api/common/logEvent';
import createDashboard from 'api/dashboard/create';
import { AxiosError } from 'axios';
import cx from 'classnames';
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
import { dashboardListMessage } from 'components/facingIssueBtn/util';
import { ENTITY_VERSION_V4 } from 'constants/app';
import ROUTES from 'constants/routes';
import { Base64Icons } from 'container/NewDashboard/DashboardSettings/General/utils';
import dayjs from 'dayjs';
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
import useComponentPermission from 'hooks/useComponentPermission';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { get, isEmpty, isUndefined } from 'lodash-es';
import {
ArrowDownWideNarrow,
ArrowUpRight,
CalendarClock,
Check,
Clock4,
Ellipsis,
EllipsisVertical,
Expand,
HdmiPort,
LayoutGrid,
Link2,
Plus,
Radius,
RotateCw,
Search,
} from 'lucide-react';
import { handleContactSupport } from 'pages/Integrations/utils';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import {
ChangeEvent,
Key,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { generatePath } from 'react-router-dom';
import { useCopyToClipboard } from 'react-use';
import { AppState } from 'store/reducers';
import { Dashboard } from 'types/api/dashboard/getAll';
import AppReducer from 'types/reducer/app';
import { isCloudUser } from 'utils/app';
import useUrlQuery from '../../hooks/useUrlQuery';
import DashboardTemplatesModal from './DashboardTemplates/DashboardTemplatesModal';
import ImportJSON from './ImportJSON';
import { DeleteButton } from './TableComponents/DeleteButton';
import {
DashboardDynamicColumns,
DynamicColumns,
filterDashboard,
} from './utils';
// eslint-disable-next-line sonarjs/cognitive-complexity
function DashboardsList(): JSX.Element {
const {
data: dashboardListResponse = [],
isLoading: isDashboardListLoading,
error: dashboardFetchError,
refetch: refetchDashboardList,
} = useGetAllDashboard();
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
const {
listSortOrder: sortOrder,
setListSortOrder: setSortOrder,
} = useDashboard();
const [action, createNewDashboard] = useComponentPermission(
['action', 'create_new_dashboards'],
role,
);
const [searchValue, setSearchValue] = useState<string>('');
const [
showNewDashboardTemplatesModal,
setShowNewDashboardTemplatesModal,
] = useState(false);
const { t } = useTranslation('dashboard');
const [
isImportJSONModalVisible,
setIsImportJSONModalVisible,
] = useState<boolean>(false);
const [uploadedGrafana, setUploadedGrafana] = useState<boolean>(false);
const [isFilteringDashboards, setIsFilteringDashboards] = useState(false);
const [isConfigureMetadataOpen, setIsConfigureMetadata] = useState<boolean>(
false,
);
const params = useUrlQuery();
const searchParams = params.get('search');
const [searchString, setSearchString] = useState<string>(searchParams || '');
const getLocalStorageDynamicColumns = (): DashboardDynamicColumns => {
const dashboardDynamicColumnsString = localStorage.getItem('dashboard');
let dashboardDynamicColumns: DashboardDynamicColumns = {
createdAt: true,
createdBy: true,
updatedAt: false,
updatedBy: false,
};
if (typeof dashboardDynamicColumnsString === 'string') {
try {
const tempDashboardDynamicColumns = JSON.parse(
dashboardDynamicColumnsString,
);
if (isEmpty(tempDashboardDynamicColumns)) {
localStorage.setItem('dashboard', JSON.stringify(dashboardDynamicColumns));
} else {
dashboardDynamicColumns = { ...tempDashboardDynamicColumns };
}
} catch (error) {
console.error(error);
}
} else {
localStorage.setItem('dashboard', JSON.stringify(dashboardDynamicColumns));
}
return dashboardDynamicColumns;
};
const [visibleColumns, setVisibleColumns] = useState<DashboardDynamicColumns>(
() => getLocalStorageDynamicColumns(),
);
function setDynamicColumnsLocalStorage(
visibleColumns: DashboardDynamicColumns,
): void {
try {
localStorage.setItem('dashboard', JSON.stringify(visibleColumns));
} catch (error) {
console.error(error);
}
}
const [dashboards, setDashboards] = useState<Dashboard[]>();
const sortDashboardsByCreatedAt = (dashboards: Dashboard[]): void => {
const sortedDashboards = dashboards.sort(
(a, b) =>
new Date(b.created_at).getTime() - new Date(a.created_at).getTime(),
);
setDashboards(sortedDashboards);
};
const sortDashboardsByUpdatedAt = (dashboards: Dashboard[]): void => {
const sortedDashboards = dashboards.sort(
(a, b) =>
new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime(),
);
setDashboards(sortedDashboards);
};
useEffect(() => {
params.set('columnKey', sortOrder.columnKey as string);
params.set('order', sortOrder.order as string);
params.set('page', sortOrder.pagination || '1');
history.replace({ search: params.toString() });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [sortOrder]);
const sortHandle = (key: string): void => {
if (!dashboards) return;
if (key === 'createdAt') {
sortDashboardsByCreatedAt(dashboards);
setSortOrder({
columnKey: 'createdAt',
order: 'descend',
pagination: sortOrder.pagination || '1',
});
} else if (key === 'updatedAt') {
sortDashboardsByUpdatedAt(dashboards);
setSortOrder({
columnKey: 'updatedAt',
order: 'descend',
pagination: sortOrder.pagination || '1',
});
}
};
function handlePageSizeUpdate(page: number): void {
setSortOrder((order) => ({
...order,
pagination: String(page),
}));
}
useEffect(() => {
const filteredDashboards = filterDashboard(
searchString,
dashboardListResponse,
);
if (sortOrder.columnKey === 'updatedAt') {
sortDashboardsByUpdatedAt(filteredDashboards || []);
} else if (sortOrder.columnKey === 'createdAt') {
sortDashboardsByCreatedAt(filteredDashboards || []);
} else if (sortOrder.columnKey === 'null') {
setSortOrder({
columnKey: 'updatedAt',
order: 'descend',
pagination: sortOrder.pagination || '1',
});
sortDashboardsByUpdatedAt(filteredDashboards || []);
}
}, [
dashboardListResponse,
searchString,
setSortOrder,
sortOrder.columnKey,
sortOrder.pagination,
]);
const [newDashboardState, setNewDashboardState] = useState({
loading: false,
error: false,
errorMessage: '',
});
const data: Data[] =
dashboards?.map((e) => ({
createdAt: e.created_at,
description: e.data.description || '',
id: e.uuid,
lastUpdatedTime: e.updated_at,
name: e.data.title,
tags: e.data.tags || [],
key: e.uuid,
createdBy: e.created_by,
isLocked: !!e.isLocked || false,
lastUpdatedBy: e.updated_by,
image: e.data.image || Base64Icons[0],
refetchDashboardList,
})) || [];
const onNewDashboardHandler = useCallback(async () => {
try {
logEvent('Dashboard List: Create dashboard clicked', {});
setNewDashboardState({
...newDashboardState,
loading: true,
});
const response = await createDashboard({
title: t('new_dashboard_title', {
ns: 'dashboard',
}),
uploadedGrafana: false,
version: ENTITY_VERSION_V4,
});
if (response.statusCode === 200) {
history.push(
generatePath(ROUTES.DASHBOARD, {
dashboardId: response.payload.uuid,
}),
);
} else {
setNewDashboardState({
...newDashboardState,
loading: false,
error: true,
errorMessage: response.error || 'Something went wrong',
});
}
} catch (error) {
setNewDashboardState({
...newDashboardState,
error: true,
errorMessage: (error as AxiosError).toString() || 'Something went Wrong',
});
}
}, [newDashboardState, t]);
const onModalHandler = (uploadedGrafana: boolean): void => {
logEvent('Dashboard List: Import JSON clicked', {});
setIsImportJSONModalVisible((state) => !state);
setUploadedGrafana(uploadedGrafana);
};
const handleSearch = (event: ChangeEvent<HTMLInputElement>): void => {
setIsFilteringDashboards(true);
setSearchValue(event.target.value);
const searchText = (event as React.BaseSyntheticEvent)?.target?.value || '';
const filteredDashboards = filterDashboard(searchText, dashboardListResponse);
setDashboards(filteredDashboards);
setIsFilteringDashboards(false);
setSearchString(searchText);
};
const [state, setCopy] = useCopyToClipboard();
const { notifications } = useNotifications();
useEffect(() => {
if (state.error) {
notifications.error({
message: t('something_went_wrong', {
ns: 'common',
}),
});
}
if (state.value) {
notifications.success({
message: t('success', {
ns: 'common',
}),
});
}
}, [state.error, state.value, t, notifications]);
function getFormattedTime(dashboard: Dashboard, option: string): string {
const timeOptions: Intl.DateTimeFormatOptions = {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
};
const formattedTime = new Date(get(dashboard, option, '')).toLocaleTimeString(
'en-US',
timeOptions,
);
const dateOptions: Intl.DateTimeFormatOptions = {
month: 'short',
day: 'numeric',
year: 'numeric',
};
const formattedDate = new Date(get(dashboard, option, '')).toLocaleDateString(
'en-US',
dateOptions,
);
// Combine time and date
return `${formattedDate}${formattedTime}`;
}
const onLastUpdated = (time: string): string => {
const currentTime = dayjs();
const lastRefresh = dayjs(time);
const secondsDiff = currentTime.diff(lastRefresh, 'seconds');
const minutedDiff = currentTime.diff(lastRefresh, 'minutes');
const hoursDiff = currentTime.diff(lastRefresh, 'hours');
const daysDiff = currentTime.diff(lastRefresh, 'days');
const monthsDiff = currentTime.diff(lastRefresh, 'months');
if (isEmpty(time)) {
return `No updates yet!`;
}
if (monthsDiff > 0) {
return `Last Updated ${monthsDiff} months ago`;
}
if (daysDiff > 0) {
return `Last Updated ${daysDiff} days ago`;
}
if (hoursDiff > 0) {
return `Last Updated ${hoursDiff} hrs ago`;
}
if (minutedDiff > 0) {
return `Last Updated ${minutedDiff} mins ago`;
}
return `Last Updated ${secondsDiff} sec ago`;
};
const columns: TableProps<Data>['columns'] = [
{
title: 'Dashboards',
key: 'dashboard',
render: (dashboard: Data): JSX.Element => {
const timeOptions: Intl.DateTimeFormatOptions = {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
};
const formattedTime = new Date(dashboard.createdAt).toLocaleTimeString(
'en-US',
timeOptions,
);
const dateOptions: Intl.DateTimeFormatOptions = {
month: 'short',
day: 'numeric',
year: 'numeric',
};
const formattedDate = new Date(dashboard.createdAt).toLocaleDateString(
'en-US',
dateOptions,
);
// Combine time and date
const formattedDateAndTime = `${formattedDate}${formattedTime}`;
const getLink = (): string => `${ROUTES.ALL_DASHBOARD}/${dashboard.id}`;
const onClickHandler = (event: React.MouseEvent<HTMLElement>): void => {
if (event.metaKey || event.ctrlKey) {
window.open(getLink(), '_blank');
} else {
history.push(getLink());
}
logEvent('Dashboard List: Clicked on dashboard', {
dashboardId: dashboard.id,
dashboardName: dashboard.name,
});
};
return (
<div className="dashboard-list-item" onClick={onClickHandler}>
<div className="title-with-action">
<div className="dashboard-title">
<img
src={dashboard?.image || Base64Icons[0]}
style={{ height: '14px', width: '14px' }}
alt="dashboard-image"
/>
<Typography.Text>{dashboard.name}</Typography.Text>
</div>
<div className="tags-with-actions">
{dashboard?.tags && dashboard.tags.length > 0 && (
<div className="dashboard-tags">
{dashboard.tags.map((tag) => (
<Tag className="tag" key={tag}>
{tag}
</Tag>
))}
</div>
)}
{action && (
<Popover
trigger="click"
content={
<div className="dashboard-action-content">
<section className="section-1">
<Button
type="text"
className="action-btn"
icon={<Expand size={14} />}
onClick={onClickHandler}
>
View
</Button>
<Button
type="text"
className="action-btn"
icon={<Link2 size={14} />}
onClick={(e): void => {
e.stopPropagation();
e.preventDefault();
setCopy(`${window.location.origin}${getLink()}`);
}}
>
Copy Link
</Button>
</section>
<section className="section-2">
<DeleteButton
name={dashboard.name}
id={dashboard.id}
isLocked={dashboard.isLocked}
createdBy={dashboard.createdBy}
/>
</section>
</div>
}
placement="bottomRight"
arrow={false}
rootClassName="dashboard-actions"
>
<EllipsisVertical
size={14}
onClick={(e): void => {
e.stopPropagation();
e.preventDefault();
}}
/>
</Popover>
)}
</div>
</div>
<div className="dashboard-details">
<div className="dashboard-created-at">
<CalendarClock size={14} />
<Typography.Text>{formattedDateAndTime}</Typography.Text>
</div>
{dashboard.createdBy && (
<div className="created-by">
<div className="dashboard-tag">
<Typography.Text className="tag-text">
{dashboard.createdBy?.substring(0, 1).toUpperCase()}
</Typography.Text>
</div>
<Typography.Text className="dashboard-created-by">
{dashboard.createdBy}
</Typography.Text>
</div>
)}
{visibleColumns.updatedAt && (
<div className="dashboard-created-at">
<CalendarClock size={14} />
<Typography.Text>
{onLastUpdated(dashboard.lastUpdatedTime)}
</Typography.Text>
</div>
)}
{dashboard.lastUpdatedBy && visibleColumns.updatedBy && (
<div className="updated-by">
<Typography.Text className="text">
Last Updated By - &nbsp;
</Typography.Text>
<div className="dashboard-tag">
<Typography.Text className="tag-text">
{dashboard.lastUpdatedBy?.substring(0, 1).toUpperCase()}
</Typography.Text>
</div>
<Typography.Text className="dashboard-created-by">
{dashboard.lastUpdatedBy}
</Typography.Text>
</div>
)}
</div>
</div>
);
},
},
];
const getCreateDashboardItems = useMemo(() => {
const menuItems: MenuProps['items'] = [
{
label: (
<div
className="create-dashboard-menu-item"
onClick={(): void => onModalHandler(false)}
>
<Radius size={14} /> Import JSON
</div>
),
key: '1',
},
];
if (createNewDashboard) {
menuItems.unshift({
label: (
<div
className="create-dashboard-menu-item"
onClick={(): void => {
onNewDashboardHandler();
}}
>
<LayoutGrid size={14} /> Create dashboard
</div>
),
key: '0',
});
}
return menuItems;
}, [createNewDashboard, onNewDashboardHandler]);
const showPaginationItem = (total: number, range: number[]): JSX.Element => (
<>
<Typography.Text className="numbers">
{range[0]} &#8212; {range[1]}
</Typography.Text>
<Typography.Text className="total">of {total}</Typography.Text>
</>
);
const paginationConfig = data.length > 20 && {
pageSize: 20,
showTotal: showPaginationItem,
showSizeChanger: false,
onChange: (page: any): void => handlePageSizeUpdate(page),
current: Number(sortOrder.pagination),
defaultCurrent: Number(sortOrder.pagination) || 1,
hideOnSinglePage: true,
};
const logEventCalledRef = useRef(false);
useEffect(() => {
if (
!logEventCalledRef.current &&
!isDashboardListLoading &&
!isUndefined(dashboardListResponse)
) {
logEvent('Dashboard List: Page visited', {
number: dashboardListResponse?.length,
});
logEventCalledRef.current = true;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isDashboardListLoading]);
return (
<div className="dashboards-list-container">
<div className="dashboards-list-view-content">
<div className="dashboards-list-title-container">
<Typography.Title className="title">Dashboards</Typography.Title>
<Flex align="center" justify="space-between">
<Typography.Text className="subtitle">
Create and manage dashboards for your workspace.
</Typography.Text>
<FacingIssueBtn
attributes={{
screen: 'Dashboard list page',
}}
eventName="Dashboard: Facing Issues in dashboard"
message={dashboardListMessage}
buttonText="Facing issues with dashboards?"
onHoverText="Click here to get help with dashboards"
/>
</Flex>
</div>
{isDashboardListLoading || isFilteringDashboards ? (
<div className="loading-dashboard-details">
<Skeleton.Input active size="large" className="skeleton-1" />
<Skeleton.Input active size="large" className="skeleton-1" />
<Skeleton.Input active size="large" className="skeleton-1" />
<Skeleton.Input active size="large" className="skeleton-1" />
</div>
) : dashboardFetchError ? (
<div className="dashboard-error-state">
<img
src="/Icons/awwSnap.svg"
alt="something went wrong"
className="error-img"
/>
<Typography.Text className="error-text">
Something went wrong :/ Please retry or contact support.
</Typography.Text>
<section className="action-btns">
<Button
className="retry-btn"
type="text"
icon={<RotateCw size={16} />}
onClick={(): Promise<any> => refetchDashboardList()}
>
Retry
</Button>
<Button
type="text"
className="learn-more"
onClick={(): void => handleContactSupport(isCloudUser())}
>
Contact Support
</Button>
<ArrowUpRight size={16} className="learn-more-arrow" />
</section>
</div>
) : dashboards?.length === 0 && !searchValue ? (
<div className="dashboard-empty-state">
<img
src="/Icons/dashboards.svg"
alt="dashboards"
className="dashboard-img"
/>
<section className="text">
<Typography.Text className="no-dashboard">
No dashboards yet.{' '}
</Typography.Text>
<Typography.Text className="info">
Create a dashboard to start visualizing your data
</Typography.Text>
</section>
{createNewDashboard && (
<section className="actions">
<Dropdown
overlayClassName="new-dashboard-menu"
menu={{ items: getCreateDashboardItems }}
placement="bottomRight"
trigger={['click']}
>
<Button
type="text"
className="new-dashboard"
icon={<Plus size={14} />}
onClick={(): void => {
logEvent('Dashboard List: New dashboard clicked', {});
}}
>
New Dashboard
</Button>
</Dropdown>
<Button
type="text"
className="learn-more"
onClick={(): void => {
window.open(
'https://signoz.io/docs/userguide/manage-dashboards?utm_source=product&utm_medium=dashboard-list-empty-state',
'_blank',
);
}}
>
Learn more
</Button>
<ArrowUpRight size={16} className="learn-more-arrow" />
</section>
)}
</div>
) : (
<>
<div className="dashboards-list-header-container">
<Input
placeholder="Search by name, description, or tags..."
prefix={<Search size={12} color={Color.BG_VANILLA_400} />}
value={searchValue}
onChange={handleSearch}
/>
{createNewDashboard && (
<Dropdown
overlayClassName="new-dashboard-menu"
menu={{ items: getCreateDashboardItems }}
placement="bottomRight"
trigger={['click']}
>
<Button
type="primary"
className="periscope-btn primary btn"
icon={<Plus size={14} />}
onClick={(): void => {
logEvent('Dashboard List: New dashboard clicked', {});
}}
>
New dashboard
</Button>
</Dropdown>
)}
</div>
{dashboards?.length === 0 ? (
<div className="no-search">
<img src="/Icons/emptyState.svg" alt="img" className="img" />
<Typography.Text className="text">
No dashboards found for {searchValue}. Create a new dashboard?
</Typography.Text>
</div>
) : (
<>
<div className="all-dashboards-header">
<Typography.Text className="typography">
All Dashboards
</Typography.Text>
<section className="right-actions">
<Tooltip title="Sort">
<Popover
trigger="click"
content={
<div className="sort-content">
<Typography.Text className="sort-heading">
Sort By
</Typography.Text>
<Button
type="text"
className={cx('sort-btns')}
onClick={(): void => sortHandle('createdAt')}
>
Last created
{sortOrder.columnKey === 'createdAt' && <Check size={14} />}
</Button>
<Button
type="text"
className={cx('sort-btns')}
onClick={(): void => sortHandle('updatedAt')}
>
Last updated
{sortOrder.columnKey === 'updatedAt' && <Check size={14} />}
</Button>
</div>
}
rootClassName="sort-dashboards"
placement="bottomRight"
arrow={false}
>
<ArrowDownWideNarrow size={14} />
</Popover>
</Tooltip>
<Popover
trigger="click"
content={
<div className="configure-content">
<Button
type="text"
icon={<HdmiPort size={14} />}
className="configure-btn"
onClick={(e): void => {
e.preventDefault();
e.stopPropagation();
setIsConfigureMetadata(true);
}}
>
Configure metadata
</Button>
</div>
}
rootClassName="configure-group"
placement="bottomRight"
arrow={false}
>
<Ellipsis size={14} />
</Popover>
</section>
</div>
<Table
columns={columns}
dataSource={data}
showSorterTooltip
loading={isDashboardListLoading || isFilteringDashboards}
showHeader={false}
pagination={paginationConfig}
/>
</>
)}
</>
)}
<ImportJSON
isImportJSONModalVisible={isImportJSONModalVisible}
uploadedGrafana={uploadedGrafana}
onModalHandler={(): void => onModalHandler(false)}
/>
<DashboardTemplatesModal
showNewDashboardTemplatesModal={showNewDashboardTemplatesModal}
onCreateNewDashboard={onNewDashboardHandler}
onCancel={(): void => {
setShowNewDashboardTemplatesModal(false);
}}
/>
<Modal
open={isConfigureMetadataOpen}
onCancel={(): void => {
setIsConfigureMetadata(false);
// reset to default if the changes are not applied
setVisibleColumns(getLocalStorageDynamicColumns());
}}
title="Configure Metadata"
footer={
<Button
type="text"
icon={<Check size={14} />}
className="save-changes"
onClick={(): void => {
setIsConfigureMetadata(false);
setDynamicColumnsLocalStorage(visibleColumns);
}}
>
Save Changes
</Button>
}
rootClassName="configure-metadata-root"
>
<div className="configure-content">
<div className="configure-preview">
<section className="header">
<img
src={dashboards?.[0]?.data?.image || Base64Icons[0]}
alt="dashboard-image"
style={{ height: '14px', width: '14px' }}
/>
<Typography.Text className="title">
{dashboards?.[0]?.data?.title}
</Typography.Text>
</section>
<section className="details">
<section className="createdAt">
{visibleColumns.createdAt && (
<Typography.Text className="formatted-time">
<CalendarClock size={14} />
{getFormattedTime(dashboards?.[0] as Dashboard, 'created_at')}
</Typography.Text>
)}
{visibleColumns.createdBy && (
<div className="user">
<Typography.Text className="user-tag">
{dashboards?.[0]?.created_by?.substring(0, 1).toUpperCase()}
</Typography.Text>
<Typography.Text className="dashboard-created-by">
{dashboards?.[0]?.created_by}
</Typography.Text>
</div>
)}
</section>
<section className="updatedAt">
{visibleColumns.updatedAt && (
<Typography.Text className="formatted-time">
<CalendarClock size={14} />
{onLastUpdated(dashboards?.[0]?.updated_at || '')}
</Typography.Text>
)}
{visibleColumns.updatedBy && (
<div className="user">
<Typography.Text className="user-tag">
{dashboards?.[0]?.updated_by?.substring(0, 1).toUpperCase()}
</Typography.Text>
<Typography.Text className="dashboard-created-by">
{dashboards?.[0]?.updated_by}
</Typography.Text>
</div>
)}
</section>
</section>
</div>
<div className="metadata-action">
<div className="left">
<CalendarClock size={14} />
<Typography.Text>Created at</Typography.Text>
</div>
<div className="connection-line" />
<div className="right">
<Switch
size="small"
checked
disabled
onChange={(check): void =>
setVisibleColumns((prev) => ({
...prev,
[DynamicColumns.CREATED_AT]: check,
}))
}
/>
</div>
</div>
<div className="metadata-action">
<div className="left">
<CalendarClock size={14} />
<Typography.Text>Created by</Typography.Text>
</div>
<div className="connection-line" />
<div className="right">
<Switch
size="small"
disabled
checked
onChange={(check): void =>
setVisibleColumns((prev) => ({
...prev,
[DynamicColumns.CREATED_BY]: check,
}))
}
/>
</div>
</div>
<div className="metadata-action">
<div className="left">
<Clock4 size={14} />
<Typography.Text>Updated at</Typography.Text>
</div>
<div className="connection-line" />
<div className="right">
<Switch
size="small"
checked={visibleColumns.updatedAt}
onChange={(check): void =>
setVisibleColumns((prev) => ({
...prev,
[DynamicColumns.UPDATED_AT]: check,
}))
}
/>
</div>
</div>
<div className="metadata-action">
<div className="left">
<Clock4 size={14} />
<Typography.Text>Updated by</Typography.Text>
</div>
<div className="connection-line" />
<div className="right">
<Switch
size="small"
checked={visibleColumns.updatedBy}
onChange={(check): void =>
setVisibleColumns((prev) => ({
...prev,
[DynamicColumns.UPDATED_BY]: check,
}))
}
/>
</div>
</div>
</div>
</Modal>
</div>
</div>
);
}
export interface Data {
key: Key;
name: string;
description: string;
tags: string[];
createdBy: string;
createdAt: string;
lastUpdatedTime: string;
lastUpdatedBy: string;
isLocked: boolean;
id: string;
image?: string;
}
export default DashboardsList;