feat: add duplication action for items (#926)
This commit is contained in:
@@ -45,6 +45,10 @@ interface CreateItem {
|
|||||||
kind: WidgetKind;
|
kind: WidgetKind;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DuplicateItem {
|
||||||
|
itemId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const useItemActions = () => {
|
export const useItemActions = () => {
|
||||||
const { updateBoard } = useUpdateBoard();
|
const { updateBoard } = useUpdateBoard();
|
||||||
|
|
||||||
@@ -87,6 +91,38 @@ export const useItemActions = () => {
|
|||||||
[updateBoard],
|
[updateBoard],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const duplicateItem = useCallback(
|
||||||
|
({ itemId }: DuplicateItem) => {
|
||||||
|
updateBoard((previous) => {
|
||||||
|
const itemToDuplicate = previous.sections
|
||||||
|
.flatMap((section) => section.items)
|
||||||
|
.find((item) => item.id === itemId);
|
||||||
|
|
||||||
|
if (!itemToDuplicate) return previous;
|
||||||
|
|
||||||
|
const newItem = {
|
||||||
|
...itemToDuplicate,
|
||||||
|
id: createId(),
|
||||||
|
yOffset: undefined,
|
||||||
|
xOffset: undefined,
|
||||||
|
} satisfies Omit<Item, "yOffset" | "xOffset"> & { yOffset?: number; xOffset?: number };
|
||||||
|
|
||||||
|
return {
|
||||||
|
...previous,
|
||||||
|
sections: previous.sections.map((section) => {
|
||||||
|
// Return same section if item is not in it
|
||||||
|
if (!section.items.some((item) => item.id === itemId)) return section;
|
||||||
|
return {
|
||||||
|
...section,
|
||||||
|
items: section.items.concat(newItem as unknown as Item),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[updateBoard],
|
||||||
|
);
|
||||||
|
|
||||||
const updateItemOptions = useCallback(
|
const updateItemOptions = useCallback(
|
||||||
({ itemId, newOptions }: UpdateItemOptions) => {
|
({ itemId, newOptions }: UpdateItemOptions) => {
|
||||||
updateBoard((previous) => {
|
updateBoard((previous) => {
|
||||||
@@ -258,6 +294,7 @@ export const useItemActions = () => {
|
|||||||
updateItemOptions,
|
updateItemOptions,
|
||||||
updateItemAdvancedOptions,
|
updateItemAdvancedOptions,
|
||||||
updateItemIntegrations,
|
updateItemIntegrations,
|
||||||
|
duplicateItem,
|
||||||
createItem,
|
createItem,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { RefObject } from "react";
|
|||||||
import { useEffect, useMemo, useRef } from "react";
|
import { useEffect, useMemo, useRef } from "react";
|
||||||
import { ActionIcon, Card, Menu } from "@mantine/core";
|
import { ActionIcon, Card, Menu } from "@mantine/core";
|
||||||
import { useElementSize } from "@mantine/hooks";
|
import { useElementSize } from "@mantine/hooks";
|
||||||
import { IconDotsVertical, IconLayoutKanban, IconPencil, IconTrash } from "@tabler/icons-react";
|
import { IconCopy, IconDotsVertical, IconLayoutKanban, IconPencil, IconTrash } from "@tabler/icons-react";
|
||||||
import { QueryErrorResetBoundary } from "@tanstack/react-query";
|
import { QueryErrorResetBoundary } from "@tanstack/react-query";
|
||||||
import combineClasses from "clsx";
|
import combineClasses from "clsx";
|
||||||
import { ErrorBoundary } from "react-error-boundary";
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
@@ -147,7 +147,8 @@ const ItemMenu = ({
|
|||||||
const { openModal } = useModalAction(WidgetEditModal);
|
const { openModal } = useModalAction(WidgetEditModal);
|
||||||
const { openConfirmModal } = useConfirmModal();
|
const { openConfirmModal } = useConfirmModal();
|
||||||
const [isEditMode] = useEditMode();
|
const [isEditMode] = useEditMode();
|
||||||
const { updateItemOptions, updateItemAdvancedOptions, updateItemIntegrations, removeItem } = useItemActions();
|
const { updateItemOptions, updateItemAdvancedOptions, updateItemIntegrations, duplicateItem, removeItem } =
|
||||||
|
useItemActions();
|
||||||
const { data: integrationData, isPending } = clientApi.integration.all.useQuery();
|
const { data: integrationData, isPending } = clientApi.integration.all.useQuery();
|
||||||
const currentDefinition = useMemo(() => widgetImports[item.kind].definition, [item.kind]);
|
const currentDefinition = useMemo(() => widgetImports[item.kind].definition, [item.kind]);
|
||||||
|
|
||||||
@@ -216,6 +217,9 @@ const ItemMenu = ({
|
|||||||
{tItem("action.edit")}
|
{tItem("action.edit")}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item leftSection={<IconLayoutKanban size={16} />}>{tItem("action.move")}</Menu.Item>
|
<Menu.Item leftSection={<IconLayoutKanban size={16} />}>{tItem("action.move")}</Menu.Item>
|
||||||
|
<Menu.Item leftSection={<IconCopy size={16} />} onClick={() => duplicateItem({ itemId: item.id })}>
|
||||||
|
{tItem("action.duplicate")}
|
||||||
|
</Menu.Item>
|
||||||
<Menu.Divider />
|
<Menu.Divider />
|
||||||
<Menu.Label c="red.6">{t("common.dangerZone")}</Menu.Label>
|
<Menu.Label c="red.6">{t("common.dangerZone")}</Menu.Label>
|
||||||
<Menu.Item c="red.6" leftSection={<IconTrash size={16} />} onClick={openRemoveModal}>
|
<Menu.Item c="red.6" leftSection={<IconTrash size={16} />} onClick={openRemoveModal}>
|
||||||
|
|||||||
@@ -662,6 +662,7 @@ export default {
|
|||||||
import: "Import item",
|
import: "Import item",
|
||||||
edit: "Edit item",
|
edit: "Edit item",
|
||||||
move: "Move item",
|
move: "Move item",
|
||||||
|
duplicate: "Duplicate item",
|
||||||
remove: "Remove item",
|
remove: "Remove item",
|
||||||
},
|
},
|
||||||
menu: {
|
menu: {
|
||||||
|
|||||||
Reference in New Issue
Block a user