feat: duplicate card via dropdown
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
HiHashtag,
|
||||
HiLink,
|
||||
HiOutlineCheckCircle,
|
||||
HiOutlineDocumentDuplicate,
|
||||
HiOutlineTrash,
|
||||
} from "react-icons/hi2";
|
||||
|
||||
@@ -13,6 +14,7 @@ import Dropdown from "~/components/Dropdown";
|
||||
import { usePermissions } from "~/hooks/usePermissions";
|
||||
import { useModal } from "~/providers/modal";
|
||||
import { usePopup } from "~/providers/popup";
|
||||
import { api } from "~/utils/api";
|
||||
|
||||
export default function CardDropdown({
|
||||
cardPublicId,
|
||||
@@ -20,19 +22,44 @@ export default function CardDropdown({
|
||||
boardPublicId,
|
||||
cardCreatedBy,
|
||||
ticketNumber,
|
||||
listPublicId,
|
||||
cardIndex,
|
||||
}: {
|
||||
cardPublicId: string;
|
||||
isTemplate?: boolean;
|
||||
boardPublicId?: string;
|
||||
cardCreatedBy?: string | null;
|
||||
ticketNumber?: string | null;
|
||||
listPublicId?: string;
|
||||
cardIndex?: number;
|
||||
}) {
|
||||
const { openModal } = useModal();
|
||||
const { showPopup } = usePopup();
|
||||
const { canEditCard, canDeleteCard } = usePermissions();
|
||||
const { data: session } = authClient.useSession();
|
||||
const utils = api.useUtils();
|
||||
const isCreator = cardCreatedBy && session?.user.id === cardCreatedBy;
|
||||
|
||||
const duplicateCard = api.card.duplicate.useMutation({
|
||||
onSuccess: () => {
|
||||
showPopup({
|
||||
header: t`Card duplicated`,
|
||||
icon: "success",
|
||||
message: t`Card duplicated successfully.`,
|
||||
});
|
||||
},
|
||||
onError: () => {
|
||||
showPopup({
|
||||
header: t`Unable to duplicate card`,
|
||||
icon: "error",
|
||||
message: t`Please try again.`,
|
||||
});
|
||||
},
|
||||
onSettled: async () => {
|
||||
await utils.board.byId.invalidate();
|
||||
},
|
||||
});
|
||||
|
||||
const handleCopyCardLink = async () => {
|
||||
const path =
|
||||
isTemplate && boardPublicId
|
||||
@@ -99,6 +126,24 @@ export default function CardDropdown({
|
||||
<HiOutlineCheckCircle className="h-[16px] w-[16px] text-dark-900" />
|
||||
),
|
||||
},
|
||||
{
|
||||
label: t`Duplicate card`,
|
||||
action: () => {
|
||||
if (!listPublicId || cardIndex === undefined) return;
|
||||
duplicateCard.mutate({
|
||||
cardPublicId,
|
||||
listPublicId,
|
||||
index: cardIndex + 1,
|
||||
copyLabels: true,
|
||||
copyMembers: true,
|
||||
copyChecklists: true,
|
||||
});
|
||||
},
|
||||
icon: (
|
||||
<HiOutlineDocumentDuplicate className="h-[16px] w-[16px] text-dark-900" />
|
||||
),
|
||||
disabled: duplicateCard.isPending || !listPublicId,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(canDeleteCard || isCreator
|
||||
|
||||
@@ -185,7 +185,11 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) {
|
||||
? router.query.cardId[0]
|
||||
: router.query.cardId;
|
||||
|
||||
const { data: card, isLoading, error } = api.card.byId.useQuery(
|
||||
const {
|
||||
data: card,
|
||||
isLoading,
|
||||
error,
|
||||
} = api.card.byId.useQuery(
|
||||
{ cardPublicId: cardId ?? "" },
|
||||
{ enabled: !!cardId && cardId.length >= 12 },
|
||||
);
|
||||
@@ -340,14 +344,15 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) {
|
||||
>
|
||||
{board?.name}
|
||||
</Link>
|
||||
{card.cardNumber != null && card.list.board.workspace.cardPrefix && (
|
||||
<>
|
||||
<IoChevronForwardSharp className="h-[10px] w-[10px] text-light-900 dark:text-dark-900" />
|
||||
<span className="whitespace-nowrap text-sm font-bold leading-[1.5rem] text-light-700 dark:text-dark-800">
|
||||
{card.list.board.workspace.cardPrefix}-{card.cardNumber}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{card.cardNumber != null &&
|
||||
card.list.board.workspace.cardPrefix && (
|
||||
<>
|
||||
<IoChevronForwardSharp className="h-[10px] w-[10px] text-light-900 dark:text-dark-900" />
|
||||
<span className="whitespace-nowrap text-sm font-bold leading-[1.5rem] text-light-700 dark:text-dark-800">
|
||||
{card.list.board.workspace.cardPrefix}-{card.cardNumber}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Dropdown
|
||||
@@ -356,10 +361,13 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) {
|
||||
boardPublicId={boardId}
|
||||
cardCreatedBy={card?.createdBy}
|
||||
ticketNumber={
|
||||
card.cardNumber != null && card.list.board.workspace.cardPrefix
|
||||
card.cardNumber != null &&
|
||||
card.list.board.workspace.cardPrefix
|
||||
? `${card.list.board.workspace.cardPrefix}-${card.cardNumber}`
|
||||
: null
|
||||
}
|
||||
listPublicId={card?.list.publicId}
|
||||
cardIndex={card?.index}
|
||||
/>
|
||||
<Link
|
||||
href={`/${isTemplate ? "templates" : "boards"}/${boardId}`}
|
||||
|
||||
@@ -47,6 +47,7 @@ export const cardDetailSchema = z.object({
|
||||
title: z.string(),
|
||||
description: z.string().nullable(),
|
||||
cardNumber: z.number().nullable(),
|
||||
index: z.number(),
|
||||
dueDate: z.date().nullable(),
|
||||
createdBy: z.string().nullable(),
|
||||
labels: z.array(labelSchema),
|
||||
|
||||
@@ -107,7 +107,12 @@ export const create = async (
|
||||
cardNumber,
|
||||
dueDate: cardInput.dueDate ?? null,
|
||||
})
|
||||
.returning({ id: cards.id, listId: cards.listId, publicId: cards.publicId, cardNumber: cards.cardNumber });
|
||||
.returning({
|
||||
id: cards.id,
|
||||
listId: cards.listId,
|
||||
publicId: cards.publicId,
|
||||
cardNumber: cards.cardNumber,
|
||||
});
|
||||
|
||||
if (!result[0]) throw new Error("Unable to create card");
|
||||
|
||||
@@ -321,8 +326,7 @@ export const bulkCreate = async (
|
||||
.where(eq(workspaces.id, workspaceId))
|
||||
.returning({ cardCounter: workspaces.cardCounter });
|
||||
|
||||
if (!counterResult)
|
||||
throw new Error(`Workspace ${workspaceId} not found`);
|
||||
if (!counterResult) throw new Error(`Workspace ${workspaceId} not found`);
|
||||
|
||||
const last = counterResult.cardCounter;
|
||||
const start = last - count + 1;
|
||||
@@ -486,6 +490,7 @@ export const getWithListAndMembersByPublicId = async (
|
||||
dueDate: true,
|
||||
createdBy: true,
|
||||
cardNumber: true,
|
||||
index: true,
|
||||
},
|
||||
with: {
|
||||
labels: {
|
||||
|
||||
Reference in New Issue
Block a user