diff --git a/apps/web/src/components/CheckboxDropdown.tsx b/apps/web/src/components/CheckboxDropdown.tsx index e10d39fa..bcfec462 100644 --- a/apps/web/src/components/CheckboxDropdown.tsx +++ b/apps/web/src/components/CheckboxDropdown.tsx @@ -24,7 +24,10 @@ interface CheckboxDropdownProps { createNewItemLabel?: string; menuSpacing?: "sm" | "md" | "lg"; position?: "left" | "right"; - handleSelect: (groupKey: string | null, item: { key: string }) => void; + handleSelect: ( + groupKey: string | null, + item: { key: string; value: string }, + ) => void; handleEdit?: (key: string) => void; handleCreate?: () => void; asChild?: boolean; @@ -58,7 +61,7 @@ export default function CheckboxDropdown({ className="group flex items-center rounded-[5px] p-2 hover:bg-light-200 dark:hover:bg-dark-300" onClick={(e) => { e.preventDefault(); - handleSelect(groupKey, { key: item.key }); + handleSelect(groupKey, { key: item.key, value: item.value }); }} > event.stopPropagation()} - onChange={() => handleSelect(groupKey, { key: item.key })} + onChange={() => + handleSelect(groupKey, { key: item.key, value: item.value }) + } checked={item.selected} /> {item.leftIcon && ( diff --git a/apps/web/src/views/card/components/ActivityList.tsx b/apps/web/src/views/card/components/ActivityList.tsx index b42fa530..f04b76d4 100644 --- a/apps/web/src/views/card/components/ActivityList.tsx +++ b/apps/web/src/views/card/components/ActivityList.tsx @@ -9,7 +9,7 @@ import { HiOutlineUserPlus, } from "react-icons/hi2"; -import { type GetCardByIdOutput } from "@kan/api/types"; +import type { GetCardByIdOutput } from "@kan/api/types"; import Avatar from "~/components/Avatar"; import Comment from "./Comment"; @@ -148,7 +148,7 @@ const ActivityList = ({ }) => { return (
- {activities?.map((activity, index) => { + {activities.map((activity, index) => { const activityText = getActivityText({ type: activity.type, toTitle: activity.toTitle, diff --git a/apps/web/src/views/card/components/LabelSelector.tsx b/apps/web/src/views/card/components/LabelSelector.tsx index 6fd486a2..8bf4bcf3 100644 --- a/apps/web/src/views/card/components/LabelSelector.tsx +++ b/apps/web/src/views/card/components/LabelSelector.tsx @@ -14,12 +14,14 @@ interface LabelSelectorProps { selected: boolean; leftIcon: React.ReactNode; }[]; + handleSelectLabel: (labelPublicId: string) => void; isLoading: boolean; } export default function LabelSelector({ cardPublicId, labels, + handleSelectLabel, isLoading, }: LabelSelectorProps) { const { openModal } = useModal(); @@ -44,9 +46,10 @@ export default function LabelSelector({ ) : ( - addOrRemoveLabel.mutate({ cardPublicId, labelPublicId: label.key }) - } + handleSelect={(_, label) => { + handleSelectLabel(label.key); + addOrRemoveLabel.mutate({ cardPublicId, labelPublicId: label.key }); + }} handleEdit={(labelPublicId) => openModal("EDIT_LABEL", labelPublicId)} handleCreate={() => openModal("NEW_LABEL")} createNewItemLabel="Create new label" diff --git a/apps/web/src/views/card/components/ListSelector.tsx b/apps/web/src/views/card/components/ListSelector.tsx index d84ada1a..ebba9353 100644 --- a/apps/web/src/views/card/components/ListSelector.tsx +++ b/apps/web/src/views/card/components/ListSelector.tsx @@ -10,12 +10,14 @@ interface ListSelectorProps { value: string; selected: boolean; }[]; + handleChangeList: (newListPublicId: string, newListName: string) => void; isLoading: boolean; } export default function ListSelector({ cardPublicId, lists, + handleChangeList, isLoading, }: ListSelectorProps) { const utils = api.useUtils(); @@ -39,12 +41,13 @@ export default function ListSelector({ ) : ( + handleSelect={(_, member) => { + handleChangeList(member.key, member.value); updateCardList.mutate({ cardPublicId, newListPublicId: member.key, - }) - } + }); + }} asChild > diff --git a/apps/web/src/views/card/components/MemberSelector.tsx b/apps/web/src/views/card/components/MemberSelector.tsx index 44f9c74f..6c5d724d 100644 --- a/apps/web/src/views/card/components/MemberSelector.tsx +++ b/apps/web/src/views/card/components/MemberSelector.tsx @@ -5,6 +5,7 @@ import { HiMiniPlus } from "react-icons/hi2"; import Avatar from "~/components/Avatar"; import CheckboxDropdown from "~/components/CheckboxDropdown"; import { useModal } from "~/providers/modal"; +import { usePopup } from "~/providers/popup"; import { api } from "~/utils/api"; interface MemberSelectorProps { @@ -16,16 +17,19 @@ interface MemberSelectorProps { leftIcon: React.ReactNode; imageUrl: string | undefined; }[]; + handleSelectMember: (memberPublicId: string) => void; isLoading: boolean; } export default function MemberSelector({ cardPublicId, members, + handleSelectMember, isLoading, }: MemberSelectorProps) { const router = useRouter(); const { openModal } = useModal(); + const { showPopup } = usePopup(); const utils = api.useUtils(); const refetchCard = () => utils.card.byId.refetch({ cardPublicId }); @@ -34,6 +38,14 @@ export default function MemberSelector({ onSuccess: async () => { await refetchCard(); }, + onError: async () => { + await refetchCard(); + showPopup({ + header: "Unable to update members", + message: "Please try again later, or contact customer support.", + icon: "error", + }); + }, }); const selectedMembers = members.filter((member) => member.selected); @@ -52,12 +64,13 @@ export default function MemberSelector({ ) : ( + handleSelect={(_, member) => { + handleSelectMember(member.key); addOrRemoveMember.mutate({ cardPublicId, workspaceMemberPublicId: member.key, - }) - } + }); + }} handleCreate={handleInviteMember} createNewItemLabel="Invite member" asChild diff --git a/apps/web/src/views/card/index.tsx b/apps/web/src/views/card/index.tsx index a7346de9..ae57900f 100644 --- a/apps/web/src/views/card/index.tsx +++ b/apps/web/src/views/card/index.tsx @@ -1,9 +1,12 @@ import Link from "next/link"; import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; import ContentEditable from "react-contenteditable"; import { useForm } from "react-hook-form"; import { IoChevronForwardSharp } from "react-icons/io5"; +import type { GetCardByIdOutput } from "@kan/api/types"; + import Avatar from "~/components/Avatar"; import LabelIcon from "~/components/LabelIcon"; import Modal from "~/components/modal"; @@ -35,6 +38,7 @@ export default function CardPage() { const utils = api.useUtils(); const { modalContentType, entityId } = useModal(); const { showPopup } = usePopup(); + const [card, setCard] = useState(); const cardId = Array.isArray(router.query.cardId) ? router.query.cardId[0] @@ -44,13 +48,85 @@ export default function CardPage() { cardPublicId: cardId ?? "", }); - const board = data?.list?.board; + useEffect(() => { + setCard(data); + }, [data]); + + const board = card?.list?.board; const boardId = board?.publicId; const labels = board?.labels; - const activities = data?.activities; + const activities = card?.activities; const workspaceMembers = board?.workspace?.members; - const selectedLabels = data?.labels; - const selectedMembers = data?.members; + const selectedLabels = card?.labels; + const selectedMembers = card?.members; + + const handleChangeList = (newListPublicId: string) => { + if (!card) return; + + setCard({ + ...card, + list: { + ...card.list, + publicId: newListPublicId, + name: card.list?.name ?? "", + board: card.list?.board ?? null, + }, + }); + }; + + const handleSelectLabel = (labelPublicId: string) => { + if (!card) return; + + const isSelected = card.labels.some( + (label) => label.publicId === labelPublicId, + ); + + const updatedLabels = isSelected + ? card.labels.filter((label) => label.publicId !== labelPublicId) + : [ + ...(card.labels ?? []), + labels?.find((label) => label.publicId === labelPublicId), + ].filter(Boolean); + + setCard({ + ...card, + labels: updatedLabels as { + publicId: string; + name: string; + colourCode: string | null; + }[], + }); + }; + + const handleSelectMember = (memberPublicId: string) => { + if (!card) return; + + const isSelected = card.members.some( + (member) => member.publicId === memberPublicId, + ); + + const updatedMembers = isSelected + ? card.members.filter((member) => member.publicId !== memberPublicId) + : [ + ...(card.members ?? []), + workspaceMembers?.find( + (member) => member.publicId === memberPublicId, + ), + ]; + + setCard({ + ...card, + members: updatedMembers.filter(Boolean) as { + publicId: string; + user: { + id: string; + name: string | null; + email: string; + image: string | null; + } | null; + }[], + }); + }; const formattedLabels = labels?.map((label) => { @@ -70,7 +146,7 @@ export default function CardPage() { board?.lists.map((list) => ({ key: list.publicId, value: list.name, - selected: list.publicId === data?.list?.publicId, + selected: list.publicId === card?.list?.publicId, })) ?? []; const formattedMembers = @@ -219,6 +295,7 @@ export default function CardPage() {
@@ -227,6 +304,7 @@ export default function CardPage() { @@ -235,6 +313,7 @@ export default function CardPage() {