From 81946e50a9f643826f6c75ce2de54f92b74819bc Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Thu, 8 Aug 2024 20:37:41 +0200 Subject: [PATCH] feat: add duplication action for items (#926) --- .../components/board/items/item-actions.tsx | 37 +++++++++++++++++++ .../src/components/board/sections/content.tsx | 8 +++- packages/translation/src/lang/en.ts | 1 + 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/apps/nextjs/src/components/board/items/item-actions.tsx b/apps/nextjs/src/components/board/items/item-actions.tsx index 5fd6c0841..937ca6246 100644 --- a/apps/nextjs/src/components/board/items/item-actions.tsx +++ b/apps/nextjs/src/components/board/items/item-actions.tsx @@ -45,6 +45,10 @@ interface CreateItem { kind: WidgetKind; } +interface DuplicateItem { + itemId: string; +} + export const useItemActions = () => { const { updateBoard } = useUpdateBoard(); @@ -87,6 +91,38 @@ export const useItemActions = () => { [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 & { 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( ({ itemId, newOptions }: UpdateItemOptions) => { updateBoard((previous) => { @@ -258,6 +294,7 @@ export const useItemActions = () => { updateItemOptions, updateItemAdvancedOptions, updateItemIntegrations, + duplicateItem, createItem, }; }; diff --git a/apps/nextjs/src/components/board/sections/content.tsx b/apps/nextjs/src/components/board/sections/content.tsx index d68e35df0..cae6e6fb0 100644 --- a/apps/nextjs/src/components/board/sections/content.tsx +++ b/apps/nextjs/src/components/board/sections/content.tsx @@ -2,7 +2,7 @@ import type { RefObject } from "react"; import { useEffect, useMemo, useRef } from "react"; import { ActionIcon, Card, Menu } from "@mantine/core"; 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 combineClasses from "clsx"; import { ErrorBoundary } from "react-error-boundary"; @@ -147,7 +147,8 @@ const ItemMenu = ({ const { openModal } = useModalAction(WidgetEditModal); const { openConfirmModal } = useConfirmModal(); 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 currentDefinition = useMemo(() => widgetImports[item.kind].definition, [item.kind]); @@ -216,6 +217,9 @@ const ItemMenu = ({ {tItem("action.edit")} }>{tItem("action.move")} + } onClick={() => duplicateItem({ itemId: item.id })}> + {tItem("action.duplicate")} + {t("common.dangerZone")} } onClick={openRemoveModal}> diff --git a/packages/translation/src/lang/en.ts b/packages/translation/src/lang/en.ts index 00aa2f60d..26e182939 100644 --- a/packages/translation/src/lang/en.ts +++ b/packages/translation/src/lang/en.ts @@ -662,6 +662,7 @@ export default { import: "Import item", edit: "Edit item", move: "Move item", + duplicate: "Duplicate item", remove: "Remove item", }, menu: {