Skip to content

Commit 59e05f8

Browse files
authored
support for tile duplication (#337)
1 parent c7c12a4 commit 59e05f8

File tree

3 files changed

+102
-7
lines changed

3 files changed

+102
-7
lines changed

src/pages/Dashboards/Dashboard.tsx

+76-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Box, Button, Divider, FileInput, Modal, Stack, Text } from '@mantine/core';
1+
import { Box, Button, Divider, FileInput, Modal, Stack, Text, TextInput } from '@mantine/core';
22
import Toolbar from './Toolbar';
33
import 'react-grid-layout/css/styles.css';
44
import 'react-resizable/css/styles.css';
@@ -20,10 +20,10 @@ import { useDashboardsQuery } from '@/hooks/useDashboards';
2020
import Tile from './Tile';
2121
import { Layout } from 'react-grid-layout';
2222
import { useAppStore } from '@/layouts/MainLayout/providers/AppProvider';
23-
import { ImportDashboardType } from '@/@types/parseable/api/dashboards';
23+
import { EditTileType, ImportDashboardType, Tile as TileType } from '@/@types/parseable/api/dashboards';
2424
import { templates } from './assets/templates';
2525

26-
const { toggleCreateDashboardModal, toggleCreateTileModal, toggleDeleteTileModal, handlePaging, toggleImportDashboardModal } =
26+
const { toggleCreateDashboardModal, toggleCreateTileModal, toggleDuplicateTileModal, toggleDeleteTileModal, handlePaging, toggleImportDashboardModal } =
2727
dashboardsStoreReducers;
2828

2929
const TilesView = (props: { onLayoutChange: (layout: Layout[]) => void }) => {
@@ -319,6 +319,78 @@ const NoTilesView = () => {
319319
);
320320
};
321321

322+
const findTileByTileId = (tiles: TileType[], tileId: string | null) => {
323+
return _.find(tiles, tile => tile.tile_id === tileId)
324+
}
325+
326+
const DuplicateTileModal = () => {
327+
const [duplicateTileModalOpen, setDashboardsStore] = useDashboardsStore(store => store.duplicateTileModalOpen)
328+
const [editTileId] = useDashboardsStore(store => store.editTileId);
329+
const [activeDashboard] = useDashboardsStore(store => store.activeDashboard)
330+
const [inputValue, setInputValue] = useState<string>('');
331+
const onClose = useCallback(() => {
332+
setDashboardsStore((store) => toggleDuplicateTileModal(store, false, null));
333+
}, []);
334+
335+
const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
336+
setInputValue(e.target.value);
337+
}, []);
338+
const { updateDashboard, isUpdatingDashboard } = useDashboardsQuery({});
339+
340+
const handleSubmit = useCallback(() => {
341+
const currentTile = findTileByTileId(activeDashboard?.tiles || [], editTileId);
342+
if (currentTile && activeDashboard) {
343+
const currentOrder = currentTile.order;
344+
const tempTiles = [...activeDashboard.tiles] as EditTileType[];
345+
const duplicatedTile = _.omit({ ...currentTile, name: inputValue }, 'tile_id');
346+
tempTiles.splice(currentOrder, 0, duplicatedTile);
347+
const updatedTilesWithOrder = assignOrderToTiles(tempTiles);
348+
return updateDashboard({
349+
dashboard: { ...activeDashboard, tiles: updatedTilesWithOrder },
350+
onSuccess: () => {
351+
onClose();
352+
},
353+
});
354+
}
355+
}, [inputValue, editTileId, activeDashboard]);
356+
357+
useEffect(() => {
358+
const currentTile = findTileByTileId(activeDashboard?.tiles || [], editTileId);
359+
if (currentTile) {
360+
setInputValue(currentTile?.name);
361+
}
362+
}, [editTileId]);
363+
364+
return (
365+
<Modal
366+
opened={duplicateTileModalOpen}
367+
onClose={onClose}
368+
size="auto"
369+
centered
370+
styles={{
371+
body: { padding: '0 1rem 1rem 1rem', width: 400 },
372+
header: { padding: '1rem', paddingBottom: '0.4rem' },
373+
}}
374+
title={<Text style={{ fontSize: '0.9rem', fontWeight: 600 }}>Duplicate Tile</Text>}>
375+
<Stack>
376+
<Stack gap={12}>
377+
<TextInput value={inputValue} onChange={handleInputChange} />
378+
</Stack>
379+
<Stack style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-end' }}>
380+
<Box>
381+
<Button onClick={onClose} variant="outline">
382+
Cancel
383+
</Button>
384+
</Box>
385+
<Box>
386+
<Button onClick={handleSubmit} loading={isUpdatingDashboard} disabled={_.isEmpty(inputValue)}>Done</Button>
387+
</Box>
388+
</Stack>
389+
</Stack>
390+
</Modal>
391+
);
392+
};
393+
322394
const Dashboard = () => {
323395
const [dashboards] = useDashboardsStore((store) => store.dashboards);
324396
const layoutRef = useRef<Layout[]>([]);
@@ -333,6 +405,7 @@ const Dashboard = () => {
333405
return (
334406
<Stack style={{ flex: 1 }} gap={0}>
335407
<DeleteTileModal />
408+
<DuplicateTileModal/>
336409
<Toolbar layoutRef={layoutRef} />
337410
<ImportDashboardModal/>
338411
<TilesView onLayoutChange={onLayoutChange} />

src/pages/Dashboards/Tile.tsx

+12-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import classes from './styles/tile.module.css';
33
import {
44
IconAlertTriangle,
55
IconBraces,
6+
IconCopyPlus,
67
IconDotsVertical,
78
IconGripVertical,
89
IconPencil,
@@ -71,7 +72,7 @@ const ParseableLogo = () => (
7172
</div>
7273
);
7374

74-
const { toggleCreateTileModal, toggleDeleteTileModal } = dashboardsStoreReducers;
75+
const { toggleCreateTileModal, toggleDeleteTileModal, toggleDuplicateTileModal } = dashboardsStoreReducers;
7576

7677
const NoDataView = () => {
7778
return (
@@ -171,6 +172,10 @@ function TileControls(props: { tile: TileType; data: TileQueryResponse }) {
171172
setDashboardsStore((store) => toggleCreateTileModal(store, true, tile_id));
172173
}, []);
173174

175+
const openDuplicateTileModal = useCallback(() => {
176+
setDashboardsStore((store) => toggleDuplicateTileModal(store, true, tile_id));
177+
}, []);
178+
174179
const openDeleteModal = useCallback(() => {
175180
setDashboardsStore((store) => toggleDeleteTileModal(store, true, tile_id));
176181
}, []);
@@ -197,6 +202,12 @@ function TileControls(props: { tile: TileType; data: TileQueryResponse }) {
197202
leftSection={<IconPencil className={classes.tileCtrlItemIcon} size="1rem" stroke={1.2} />}>
198203
<Text className={classes.tileCtrlItemText}>Edit</Text>
199204
</Menu.Item>
205+
<Menu.Item
206+
className={classes.tileCtrlItem}
207+
onClick={openDuplicateTileModal}
208+
leftSection={<IconCopyPlus className={classes.tileCtrlItemIcon} size="1rem" stroke={1.2} />}>
209+
<Text className={classes.tileCtrlItemText}>Duplicate</Text>
210+
</Menu.Item>
200211
<Menu.Item
201212
className={classes.tileCtrlItem}
202213
onClick={exportTileConfig}

src/pages/Dashboards/providers/DashboardsProvider.ts

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { Dashboard, Tile, TileQueryResponse, tileSizeWidthMap } from '@/@types/parseable/api/dashboards';
1+
import { Dashboard, EditTileType, Tile, TileQueryResponse, tileSizeWidthMap } from '@/@types/parseable/api/dashboards';
22
import initContext from '@/utils/initContext';
33
import _ from 'lodash';
44
import { Layout } from 'react-grid-layout';
55

6-
export const TILES_PER_PAGE = 5;
6+
export const TILES_PER_PAGE = 10;
77

88
export const sortTilesByOrder = (tiles: Tile[], idsByOrder: string[]): Tile[] => {
99
return _.chain(idsByOrder)
@@ -15,7 +15,7 @@ export const sortTilesByOrder = (tiles: Tile[], idsByOrder: string[]): Tile[] =>
1515
.value();
1616
};
1717

18-
export const assignOrderToTiles = (tiles: Tile[]) => {
18+
export const assignOrderToTiles = (tiles: Tile[] | EditTileType[]) => {
1919
return _.map(tiles, (tile, index) => {
2020
return { ...tile, order: index + 1 };
2121
});
@@ -77,6 +77,7 @@ type DashboardsStore = {
7777
editDashboardModalOpen: boolean;
7878
deleteDashboardModalOpen: boolean;
7979
createTileFormOpen: boolean;
80+
duplicateTileModalOpen: boolean;
8081
vizEditorModalOpen: boolean;
8182
allowDrag: boolean;
8283
editTileId: string | null;
@@ -99,6 +100,7 @@ const initialState: DashboardsStore = {
99100
deleteDashboardModalOpen: false,
100101
createTileFormOpen: false,
101102
vizEditorModalOpen: false,
103+
duplicateTileModalOpen: false,
102104
allowDrag: false,
103105
editTileId: null,
104106
tilesData: {},
@@ -118,6 +120,7 @@ type DashboardsStoreReducers = {
118120
toggleEditDashboardModal: (store: DashboardsStore, val: boolean) => ReducerOutput;
119121
selectDashboard: (store: DashboardsStore, dashboardId?: string | null, dashboard?: Dashboard) => ReducerOutput;
120122
toggleCreateTileModal: (store: DashboardsStore, val: boolean, tileId?: string | null) => ReducerOutput;
123+
toggleDuplicateTileModal: (store: DashboardsStore, val: boolean, tileId?: string | null) => ReducerOutput;
121124
toggleVizEditorModal: (store: DashboardsStore, val: boolean) => ReducerOutput;
122125
toggleAllowDrag: (store: DashboardsStore) => ReducerOutput;
123126
toggleDeleteDashboardModal: (store: DashboardsStore, val: boolean) => ReducerOutput;
@@ -148,6 +151,13 @@ const toggleCreateTileModal = (_store: DashboardsStore, val: boolean, tileId: st
148151
};
149152
};
150153

154+
const toggleDuplicateTileModal = (_store: DashboardsStore, val: boolean, tileId: string | null = null) => {
155+
return {
156+
duplicateTileModalOpen: val,
157+
editTileId: tileId,
158+
};
159+
};
160+
151161
const toggleVizEditorModal = (_store: DashboardsStore, val: boolean) => {
152162
return {
153163
vizEditorModalOpen: val,
@@ -275,6 +285,7 @@ const dashboardsStoreReducers: DashboardsStoreReducers = {
275285
toggleImportTileModal,
276286
toggleImportDashboardModal,
277287
handlePaging,
288+
toggleDuplicateTileModal
278289
};
279290

280291
export { DashbaordsProvider, useDashboardsStore, dashboardsStoreReducers };

0 commit comments

Comments
 (0)