mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-24 10:56:53 +00:00
673 lines
20 KiB
TypeScript
673 lines
20 KiB
TypeScript
import './GridCardLayout.styles.scss';
|
|
|
|
import * as Sentry from '@sentry/react';
|
|
import { Color } from '@signozhq/design-tokens';
|
|
import { Button, Form, Input, Modal, Typography } from 'antd';
|
|
import { useForm } from 'antd/es/form/Form';
|
|
import logEvent from 'api/common/logEvent';
|
|
import cx from 'classnames';
|
|
import { ENTITY_VERSION_V5 } from 'constants/app';
|
|
import { QueryParams } from 'constants/query';
|
|
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
|
|
import { themeColors } from 'constants/theme';
|
|
import { DEFAULT_ROW_NAME } from 'container/NewDashboard/DashboardDescription/utils';
|
|
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
|
import useComponentPermission from 'hooks/useComponentPermission';
|
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
|
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
|
import useUrlQuery from 'hooks/useUrlQuery';
|
|
import { defaultTo, isUndefined } from 'lodash-es';
|
|
import isEqual from 'lodash-es/isEqual';
|
|
import {
|
|
Check,
|
|
ChevronDown,
|
|
ChevronUp,
|
|
GripVertical,
|
|
LockKeyhole,
|
|
X,
|
|
} from 'lucide-react';
|
|
import { useAppContext } from 'providers/App/App';
|
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
|
import { sortLayout } from 'providers/Dashboard/util';
|
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
import { FullScreen, FullScreenHandle } from 'react-full-screen';
|
|
import { ItemCallback, Layout } from 'react-grid-layout';
|
|
import { useDispatch } from 'react-redux';
|
|
import { useLocation } from 'react-router-dom';
|
|
import { UpdateTimeInterval } from 'store/actions';
|
|
import { Widgets } from 'types/api/dashboard/getAll';
|
|
import { Props } from 'types/api/dashboard/update';
|
|
import { ROLES, USER_ROLES } from 'types/roles';
|
|
import { ComponentTypes } from 'utils/permission';
|
|
|
|
import { EditMenuAction, ViewMenuAction } from './config';
|
|
import DashboardEmptyState from './DashboardEmptyState/DashboardEmptyState';
|
|
import GridCard from './GridCard';
|
|
import { Card, CardContainer, ReactGridLayout } from './styles';
|
|
import {
|
|
hasColumnWidthsChanged,
|
|
removeUndefinedValuesFromLayout,
|
|
} from './utils';
|
|
import { MenuItemKeys } from './WidgetHeader/contants';
|
|
import { WidgetRowHeader } from './WidgetRow';
|
|
|
|
interface GraphLayoutProps {
|
|
handle: FullScreenHandle;
|
|
}
|
|
|
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
function GraphLayout(props: GraphLayoutProps): JSX.Element {
|
|
const { handle } = props;
|
|
const { safeNavigate } = useSafeNavigate();
|
|
const {
|
|
selectedDashboard,
|
|
layouts,
|
|
setLayouts,
|
|
panelMap,
|
|
setPanelMap,
|
|
setSelectedDashboard,
|
|
isDashboardLocked,
|
|
dashboardQueryRangeCalled,
|
|
setDashboardQueryRangeCalled,
|
|
setSelectedRowWidgetId,
|
|
isDashboardFetching,
|
|
columnWidths,
|
|
} = useDashboard();
|
|
const { data } = selectedDashboard || {};
|
|
const { pathname } = useLocation();
|
|
const dispatch = useDispatch();
|
|
|
|
const { widgets, variables } = data || {};
|
|
|
|
const { user } = useAppContext();
|
|
|
|
const isDarkMode = useIsDarkMode();
|
|
|
|
const [dashboardLayout, setDashboardLayout] = useState<Layout[]>([]);
|
|
|
|
const [isSettingsModalOpen, setIsSettingsModalOpen] = useState<boolean>(false);
|
|
|
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState<boolean>(false);
|
|
|
|
const [currentSelectRowId, setCurrentSelectRowId] = useState<string | null>(
|
|
null,
|
|
);
|
|
|
|
const [currentPanelMap, setCurrentPanelMap] = useState<
|
|
Record<string, { widgets: Layout[]; collapsed: boolean }>
|
|
>({});
|
|
|
|
useEffect(() => {
|
|
setCurrentPanelMap(panelMap);
|
|
}, [panelMap]);
|
|
|
|
const [form] = useForm<{
|
|
title: string;
|
|
}>();
|
|
|
|
const updateDashboardMutation = useUpdateDashboard();
|
|
|
|
const urlQuery = useUrlQuery();
|
|
|
|
let permissions: ComponentTypes[] = ['save_layout', 'add_panel'];
|
|
|
|
if (isDashboardLocked) {
|
|
permissions = ['edit_locked_dashboard', 'add_panel_locked_dashboard'];
|
|
}
|
|
|
|
const userRole: ROLES | null =
|
|
selectedDashboard?.createdBy === user?.email
|
|
? (USER_ROLES.AUTHOR as ROLES)
|
|
: user.role;
|
|
|
|
const [saveLayoutPermission, addPanelPermission] = useComponentPermission(
|
|
permissions,
|
|
userRole,
|
|
);
|
|
|
|
const [deleteWidget, editWidget] = useComponentPermission(
|
|
['delete_widget', 'edit_widget'],
|
|
user.role,
|
|
);
|
|
|
|
useEffect(() => {
|
|
setDashboardLayout(sortLayout(layouts));
|
|
}, [layouts]);
|
|
|
|
useEffect(() => {
|
|
setDashboardQueryRangeCalled(false);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const timeoutId = setTimeout(() => {
|
|
// Send Sentry event if query_range is not called within expected timeframe (2 mins) when there are widgets
|
|
if (!dashboardQueryRangeCalled && data?.widgets?.length) {
|
|
Sentry.captureEvent({
|
|
message: `Dashboard query range not called within expected timeframe even when there are ${data?.widgets?.length} widgets`,
|
|
level: 'warning',
|
|
});
|
|
}
|
|
}, 120000);
|
|
|
|
return (): void => clearTimeout(timeoutId);
|
|
}, [dashboardQueryRangeCalled, data?.widgets?.length]);
|
|
|
|
const logEventCalledRef = useRef(false);
|
|
useEffect(() => {
|
|
if (!logEventCalledRef.current && !isUndefined(data)) {
|
|
logEvent('Dashboard Detail: Opened', {
|
|
dashboardId: selectedDashboard?.id,
|
|
dashboardName: data.title,
|
|
numberOfPanels: data.widgets?.length,
|
|
numberOfVariables: Object.keys(data?.variables || {}).length || 0,
|
|
});
|
|
logEventCalledRef.current = true;
|
|
}
|
|
}, [data, selectedDashboard?.id]);
|
|
|
|
const onSaveHandler = (): void => {
|
|
if (!selectedDashboard) return;
|
|
|
|
const updatedDashboard: Props = {
|
|
id: selectedDashboard.id,
|
|
data: {
|
|
...selectedDashboard.data,
|
|
panelMap: { ...currentPanelMap },
|
|
layout: dashboardLayout.filter((e) => e.i !== PANEL_TYPES.EMPTY_WIDGET),
|
|
widgets: selectedDashboard?.data?.widgets?.map((widget) => {
|
|
if (columnWidths?.[widget.id]) {
|
|
return {
|
|
...widget,
|
|
columnWidths: columnWidths[widget.id],
|
|
};
|
|
}
|
|
return widget;
|
|
}),
|
|
},
|
|
};
|
|
|
|
updateDashboardMutation.mutate(updatedDashboard, {
|
|
onSuccess: (updatedDashboard) => {
|
|
setSelectedRowWidgetId(null);
|
|
if (updatedDashboard.data) {
|
|
if (updatedDashboard.data.data.layout)
|
|
setLayouts(sortLayout(updatedDashboard.data.data.layout));
|
|
setSelectedDashboard(updatedDashboard.data);
|
|
setPanelMap(updatedDashboard.data?.data?.panelMap || {});
|
|
}
|
|
},
|
|
});
|
|
};
|
|
|
|
const widgetActions = !isDashboardLocked
|
|
? [...ViewMenuAction, ...EditMenuAction]
|
|
: [...ViewMenuAction, MenuItemKeys.CreateAlerts];
|
|
|
|
const handleLayoutChange = (layout: Layout[]): void => {
|
|
const filterLayout = removeUndefinedValuesFromLayout(layout);
|
|
const filterDashboardLayout = removeUndefinedValuesFromLayout(
|
|
dashboardLayout,
|
|
);
|
|
if (!isEqual(filterLayout, filterDashboardLayout)) {
|
|
const updatedLayout = sortLayout(layout);
|
|
setDashboardLayout(updatedLayout);
|
|
}
|
|
};
|
|
|
|
const onDragSelect = useCallback(
|
|
(start: number, end: number) => {
|
|
const startTimestamp = Math.trunc(start);
|
|
const endTimestamp = Math.trunc(end);
|
|
|
|
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
|
|
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
|
|
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
|
|
safeNavigate(generatedUrl);
|
|
|
|
if (startTimestamp !== endTimestamp) {
|
|
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
|
}
|
|
},
|
|
[dispatch, pathname, safeNavigate, urlQuery],
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (
|
|
isDashboardLocked ||
|
|
!saveLayoutPermission ||
|
|
updateDashboardMutation.isLoading ||
|
|
isDashboardFetching
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const shouldSaveLayout =
|
|
dashboardLayout &&
|
|
Array.isArray(dashboardLayout) &&
|
|
dashboardLayout.length > 0 &&
|
|
!isEqual(layouts, dashboardLayout);
|
|
|
|
const shouldSaveColumnWidths =
|
|
dashboardLayout &&
|
|
Array.isArray(dashboardLayout) &&
|
|
dashboardLayout.length > 0 &&
|
|
hasColumnWidthsChanged(columnWidths, selectedDashboard);
|
|
|
|
if (shouldSaveLayout || shouldSaveColumnWidths) {
|
|
onSaveHandler();
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [dashboardLayout, columnWidths]);
|
|
|
|
const onSettingsModalSubmit = (): void => {
|
|
const newTitle = form.getFieldValue('title');
|
|
if (!selectedDashboard) return;
|
|
|
|
if (!currentSelectRowId) return;
|
|
|
|
const currentWidget = selectedDashboard?.data?.widgets?.find(
|
|
(e) => e.id === currentSelectRowId,
|
|
);
|
|
|
|
if (!currentWidget) return;
|
|
|
|
currentWidget.title = newTitle;
|
|
const updatedWidgets = selectedDashboard?.data?.widgets?.filter(
|
|
(e) => e.id !== currentSelectRowId,
|
|
);
|
|
|
|
updatedWidgets?.push(currentWidget);
|
|
|
|
const updatedSelectedDashboard: Props = {
|
|
id: selectedDashboard.id,
|
|
data: {
|
|
...selectedDashboard.data,
|
|
widgets: updatedWidgets,
|
|
},
|
|
};
|
|
|
|
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
|
|
onSuccess: (updatedDashboard) => {
|
|
if (setLayouts) setLayouts(updatedDashboard.data?.data?.layout || []);
|
|
if (setSelectedDashboard && updatedDashboard.data) {
|
|
setSelectedDashboard(updatedDashboard.data);
|
|
}
|
|
if (setPanelMap) setPanelMap(updatedDashboard.data?.data?.panelMap || {});
|
|
form.setFieldValue('title', '');
|
|
setIsSettingsModalOpen(false);
|
|
setCurrentSelectRowId(null);
|
|
},
|
|
});
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (!currentSelectRowId) return;
|
|
form.setFieldValue(
|
|
'title',
|
|
(widgets?.find((widget) => widget.id === currentSelectRowId)
|
|
?.title as string) || DEFAULT_ROW_NAME,
|
|
);
|
|
}, [currentSelectRowId, form, widgets]);
|
|
|
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
const handleRowCollapse = (id: string): void => {
|
|
if (!selectedDashboard) return;
|
|
const rowProperties = { ...currentPanelMap[id] };
|
|
const updatedPanelMap = { ...currentPanelMap };
|
|
|
|
let updatedDashboardLayout = [...dashboardLayout];
|
|
if (rowProperties.collapsed === true) {
|
|
rowProperties.collapsed = false;
|
|
const widgetsInsideTheRow = rowProperties.widgets;
|
|
let maxY = 0;
|
|
widgetsInsideTheRow.forEach((w) => {
|
|
maxY = Math.max(maxY, w.y + w.h);
|
|
});
|
|
const currentRowWidget = dashboardLayout.find((w) => w.i === id);
|
|
if (currentRowWidget && widgetsInsideTheRow.length) {
|
|
maxY -= currentRowWidget.h + currentRowWidget.y;
|
|
}
|
|
|
|
const idxCurrentRow = dashboardLayout.findIndex((w) => w.i === id);
|
|
|
|
for (let j = idxCurrentRow + 1; j < dashboardLayout.length; j++) {
|
|
updatedDashboardLayout[j].y += maxY;
|
|
if (updatedPanelMap[updatedDashboardLayout[j].i]) {
|
|
updatedPanelMap[updatedDashboardLayout[j].i].widgets = updatedPanelMap[
|
|
updatedDashboardLayout[j].i
|
|
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
|
].widgets.map((w) => ({
|
|
...w,
|
|
y: w.y + maxY,
|
|
}));
|
|
}
|
|
}
|
|
updatedDashboardLayout = [...updatedDashboardLayout, ...widgetsInsideTheRow];
|
|
} else {
|
|
rowProperties.collapsed = true;
|
|
const currentIdx = dashboardLayout.findIndex((w) => w.i === id);
|
|
|
|
let widgetsInsideTheRow: Layout[] = [];
|
|
let isPanelMapUpdated = false;
|
|
for (let j = currentIdx + 1; j < dashboardLayout.length; j++) {
|
|
if (currentPanelMap[dashboardLayout[j].i]) {
|
|
rowProperties.widgets = widgetsInsideTheRow;
|
|
widgetsInsideTheRow = [];
|
|
isPanelMapUpdated = true;
|
|
break;
|
|
} else {
|
|
widgetsInsideTheRow.push(dashboardLayout[j]);
|
|
}
|
|
}
|
|
if (!isPanelMapUpdated) {
|
|
rowProperties.widgets = widgetsInsideTheRow;
|
|
}
|
|
let maxY = 0;
|
|
widgetsInsideTheRow.forEach((w) => {
|
|
maxY = Math.max(maxY, w.y + w.h);
|
|
});
|
|
const currentRowWidget = dashboardLayout[currentIdx];
|
|
if (currentRowWidget && widgetsInsideTheRow.length) {
|
|
maxY -= currentRowWidget.h + currentRowWidget.y;
|
|
}
|
|
for (let j = currentIdx + 1; j < updatedDashboardLayout.length; j++) {
|
|
updatedDashboardLayout[j].y += maxY;
|
|
if (updatedPanelMap[updatedDashboardLayout[j].i]) {
|
|
updatedPanelMap[updatedDashboardLayout[j].i].widgets = updatedPanelMap[
|
|
updatedDashboardLayout[j].i
|
|
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
|
].widgets.map((w) => ({
|
|
...w,
|
|
y: w.y + maxY,
|
|
}));
|
|
}
|
|
}
|
|
|
|
updatedDashboardLayout = updatedDashboardLayout.filter(
|
|
(widget) => !rowProperties.widgets.some((w: Layout) => w.i === widget.i),
|
|
);
|
|
}
|
|
setCurrentPanelMap((prev) => ({
|
|
...prev,
|
|
...updatedPanelMap,
|
|
[id]: {
|
|
...rowProperties,
|
|
},
|
|
}));
|
|
|
|
setDashboardLayout(sortLayout(updatedDashboardLayout));
|
|
};
|
|
|
|
const handleDragStop: ItemCallback = (_, oldItem, newItem): void => {
|
|
if (oldItem?.i && currentPanelMap?.[oldItem.i]) {
|
|
const differenceY = newItem.y - oldItem.y;
|
|
const widgetsInsideRow = (currentPanelMap[oldItem.i]?.widgets ?? []).map(
|
|
(w) => ({
|
|
...w,
|
|
y: w.y + differenceY,
|
|
}),
|
|
);
|
|
setCurrentPanelMap((prev) => ({
|
|
...prev,
|
|
[oldItem.i]: {
|
|
...prev[oldItem.i],
|
|
widgets: widgetsInsideRow,
|
|
},
|
|
}));
|
|
}
|
|
};
|
|
|
|
const handleRowDelete = (): void => {
|
|
if (!selectedDashboard) return;
|
|
|
|
if (!currentSelectRowId) return;
|
|
|
|
const updatedWidgets = selectedDashboard?.data?.widgets?.filter(
|
|
(e) => e.id !== currentSelectRowId,
|
|
);
|
|
|
|
const updatedLayout =
|
|
selectedDashboard.data.layout?.filter((e) => e.i !== currentSelectRowId) ||
|
|
[];
|
|
|
|
const updatedPanelMap = { ...currentPanelMap };
|
|
delete updatedPanelMap[currentSelectRowId];
|
|
|
|
const updatedSelectedDashboard: Props = {
|
|
id: selectedDashboard.id,
|
|
data: {
|
|
...selectedDashboard.data,
|
|
widgets: updatedWidgets,
|
|
layout: updatedLayout,
|
|
panelMap: updatedPanelMap,
|
|
},
|
|
};
|
|
|
|
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
|
|
onSuccess: (updatedDashboard) => {
|
|
if (setLayouts) setLayouts(updatedDashboard.data?.data?.layout || []);
|
|
if (setSelectedDashboard && updatedDashboard.data) {
|
|
setSelectedDashboard(updatedDashboard.data);
|
|
}
|
|
if (setPanelMap) setPanelMap(updatedDashboard.data?.data?.panelMap || {});
|
|
setIsDeleteModalOpen(false);
|
|
setCurrentSelectRowId(null);
|
|
},
|
|
});
|
|
};
|
|
const isDashboardEmpty = useMemo(
|
|
() =>
|
|
selectedDashboard?.data.layout
|
|
? selectedDashboard?.data.layout?.length === 0
|
|
: true,
|
|
[selectedDashboard],
|
|
);
|
|
|
|
let isDataAvailableInAnyWidget = false;
|
|
const isLogEventCalled = useRef<boolean>(false);
|
|
|
|
return isDashboardEmpty ? (
|
|
<DashboardEmptyState />
|
|
) : (
|
|
<FullScreen
|
|
handle={handle}
|
|
className="fullscreen-grid-container"
|
|
data-overlayscrollbars-initialize
|
|
>
|
|
<ReactGridLayout
|
|
cols={12}
|
|
rowHeight={45}
|
|
autoSize
|
|
width={100}
|
|
useCSSTransforms
|
|
isDraggable={!isDashboardLocked && addPanelPermission}
|
|
isDroppable={!isDashboardLocked && addPanelPermission}
|
|
isResizable={!isDashboardLocked && addPanelPermission}
|
|
allowOverlap={false}
|
|
onLayoutChange={handleLayoutChange}
|
|
onDragStop={handleDragStop}
|
|
draggableHandle=".drag-handle"
|
|
layout={dashboardLayout}
|
|
style={{ backgroundColor: isDarkMode ? '' : themeColors.snowWhite }}
|
|
>
|
|
{dashboardLayout.map((layout) => {
|
|
const { i: id } = layout;
|
|
const currentWidget = (widgets || [])?.find((e) => e.id === id);
|
|
|
|
if (currentWidget?.panelTypes === PANEL_GROUP_TYPES.ROW) {
|
|
const rowWidgetProperties = currentPanelMap[id] || {};
|
|
let { title } = currentWidget;
|
|
if (rowWidgetProperties.collapsed) {
|
|
const widgetCount = rowWidgetProperties.widgets?.length || 0;
|
|
const collapsedText = `(${widgetCount} widget${
|
|
widgetCount > 1 ? 's' : ''
|
|
})`;
|
|
title += ` ${collapsedText}`;
|
|
}
|
|
|
|
return (
|
|
<CardContainer
|
|
className="row-card"
|
|
isDarkMode={isDarkMode}
|
|
key={id}
|
|
data-grid={JSON.stringify(currentWidget)}
|
|
>
|
|
<div className={cx('row-panel')}>
|
|
<div style={{ display: 'flex', gap: '6px', alignItems: 'center' }}>
|
|
{rowWidgetProperties.collapsed && (
|
|
<GripVertical
|
|
size={14}
|
|
className="drag-handle"
|
|
color={isDarkMode ? Color.BG_VANILLA_100 : Color.BG_INK_300}
|
|
cursor="move"
|
|
/>
|
|
)}
|
|
<Typography.Text className="section-title">{title}</Typography.Text>
|
|
{rowWidgetProperties.collapsed ? (
|
|
<ChevronDown
|
|
size={14}
|
|
onClick={(): void => handleRowCollapse(id)}
|
|
className="row-icon"
|
|
/>
|
|
) : (
|
|
<ChevronUp
|
|
size={14}
|
|
onClick={(): void => handleRowCollapse(id)}
|
|
className="row-icon"
|
|
/>
|
|
)}
|
|
</div>
|
|
<WidgetRowHeader
|
|
id={id}
|
|
rowWidgetProperties={rowWidgetProperties}
|
|
setCurrentSelectRowId={setCurrentSelectRowId}
|
|
setIsDeleteModalOpen={setIsDeleteModalOpen}
|
|
setIsSettingsModalOpen={setIsSettingsModalOpen}
|
|
editWidget={editWidget}
|
|
deleteWidget={deleteWidget}
|
|
/>
|
|
</div>
|
|
</CardContainer>
|
|
);
|
|
}
|
|
|
|
const checkIfDataExists = (isDataAvailable: boolean): void => {
|
|
if (!isDataAvailableInAnyWidget && isDataAvailable) {
|
|
isDataAvailableInAnyWidget = true;
|
|
}
|
|
if (!isLogEventCalled.current && isDataAvailableInAnyWidget) {
|
|
isLogEventCalled.current = true;
|
|
logEvent('Dashboard Detail: Panel data fetched', {
|
|
isDataAvailableInAnyWidget,
|
|
});
|
|
}
|
|
};
|
|
|
|
return (
|
|
<CardContainer
|
|
className={isDashboardLocked ? '' : 'enable-resize'}
|
|
isDarkMode={isDarkMode}
|
|
key={id}
|
|
data-grid={JSON.stringify(currentWidget)}
|
|
>
|
|
<Card
|
|
className="grid-item"
|
|
isDarkMode={isDarkMode}
|
|
$panelType={currentWidget?.panelTypes || PANEL_TYPES.TIME_SERIES}
|
|
>
|
|
<GridCard
|
|
widget={(currentWidget as Widgets) || ({ id, query: {} } as Widgets)}
|
|
headerMenuList={widgetActions}
|
|
variables={variables}
|
|
// version={selectedDashboard?.data?.version}
|
|
version={ENTITY_VERSION_V5}
|
|
onDragSelect={onDragSelect}
|
|
dataAvailable={checkIfDataExists}
|
|
/>
|
|
</Card>
|
|
</CardContainer>
|
|
);
|
|
})}
|
|
</ReactGridLayout>
|
|
{isDashboardLocked && (
|
|
<div className="footer">
|
|
<Button
|
|
type="text"
|
|
icon={<LockKeyhole size={14} />}
|
|
className="locked-text"
|
|
>
|
|
Locked
|
|
</Button>
|
|
<div className="locked-bar" />
|
|
</div>
|
|
)}
|
|
<Modal
|
|
open={isSettingsModalOpen}
|
|
title="Rename Section"
|
|
rootClassName="rename-section"
|
|
destroyOnClose
|
|
footer={null}
|
|
onCancel={(): void => {
|
|
setIsSettingsModalOpen(false);
|
|
setCurrentSelectRowId(null);
|
|
}}
|
|
>
|
|
<Form form={form} onFinish={onSettingsModalSubmit} requiredMark>
|
|
<Typography.Text className="typography">
|
|
Enter section name
|
|
</Typography.Text>
|
|
<Form.Item required name={['title']}>
|
|
<Input
|
|
placeholder="Enter row name here..."
|
|
defaultValue={defaultTo(
|
|
widgets?.find((widget) => widget.id === currentSelectRowId)
|
|
?.title as string,
|
|
'Sample Title',
|
|
)}
|
|
/>
|
|
</Form.Item>
|
|
<Form.Item>
|
|
<div className="action-btns">
|
|
<Button
|
|
type="primary"
|
|
htmlType="submit"
|
|
className="ok-btn"
|
|
icon={<Check size={14} />}
|
|
disabled={updateDashboardMutation.isLoading}
|
|
>
|
|
Apply Changes
|
|
</Button>
|
|
<Button
|
|
type="text"
|
|
className="cancel-btn"
|
|
icon={<X size={14} />}
|
|
onClick={(): void => {
|
|
setIsSettingsModalOpen(false);
|
|
setCurrentSelectRowId(null);
|
|
}}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
</div>
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
<Modal
|
|
open={isDeleteModalOpen}
|
|
title="Delete Row"
|
|
destroyOnClose
|
|
onCancel={(): void => {
|
|
setIsDeleteModalOpen(false);
|
|
setCurrentSelectRowId(null);
|
|
}}
|
|
onOk={(): void => handleRowDelete()}
|
|
>
|
|
<Typography.Text>Are you sure you want to delete this row</Typography.Text>
|
|
</Modal>
|
|
</FullScreen>
|
|
);
|
|
}
|
|
|
|
export default GraphLayout;
|