feat: assign members on new card

This commit is contained in:
Henry
2024-03-24 11:18:31 +00:00
parent 9a89a31d6a
commit f524d49d7f
4 changed files with 124 additions and 11 deletions

View File

@@ -14,6 +14,7 @@ import { useModal } from "~/app/providers/modal";
interface FormData { interface FormData {
title: string; title: string;
listPublicId: string; listPublicId: string;
memberPublicIds: string[];
} }
interface NewCardFormProps { interface NewCardFormProps {
@@ -34,9 +35,12 @@ export function NewCardForm({ listPublicId }: NewCardFormProps) {
defaultValues: { defaultValues: {
title: "", title: "",
listPublicId, listPublicId,
memberPublicIds: [],
}, },
}); });
const memberPublicIds = watch("memberPublicIds") || [];
const refetchBoard = () => const refetchBoard = () =>
utils.board.byId.refetch({ id: boardData.publicId }); utils.board.byId.refetch({ id: boardData.publicId });
@@ -64,10 +68,18 @@ export function NewCardForm({ listPublicId }: NewCardFormProps) {
selected: list.publicId === watch("listPublicId"), selected: list.publicId === watch("listPublicId"),
})) ?? []; })) ?? [];
const formattedMembers =
boardData?.workspace?.members?.map((member) => ({
key: member.publicId,
value: member.user.name,
selected: memberPublicIds.includes(member.publicId),
})) ?? [];
const onSubmit = (data: FormData) => { const onSubmit = (data: FormData) => {
createCard.mutate({ createCard.mutate({
title: data.title, title: data.title,
listPublicId: data.listPublicId, listPublicId: data.listPublicId,
memberPublicIds: data.memberPublicIds,
}); });
reset(); reset();
}; };
@@ -76,6 +88,17 @@ export function NewCardForm({ listPublicId }: NewCardFormProps) {
setValue("listPublicId", listPublicId); setValue("listPublicId", listPublicId);
}; };
const handleSelectMembers = (memberPublicId: string): void => {
const currentIndex = memberPublicIds.indexOf(memberPublicId);
if (currentIndex === -1) {
setValue("memberPublicIds", [...memberPublicIds, memberPublicId]);
} else {
const newMemberPublicIds = [...memberPublicIds];
newMemberPublicIds.splice(currentIndex, 1);
setValue("memberPublicIds", newMemberPublicIds);
}
};
const selectedList = formattedLists.find((item) => item.selected); const selectedList = formattedLists.find((item) => item.selected);
return ( return (
@@ -105,16 +128,59 @@ export function NewCardForm({ listPublicId }: NewCardFormProps) {
className="block w-full rounded-md border-0 bg-dark-300 bg-white/5 py-1.5 text-dark-1000 shadow-sm ring-1 ring-inset ring-dark-700 focus:ring-2 focus:ring-inset focus:ring-dark-700 sm:text-sm sm:leading-6" className="block w-full rounded-md border-0 bg-dark-300 bg-white/5 py-1.5 text-dark-1000 shadow-sm ring-1 ring-inset ring-dark-700 focus:ring-2 focus:ring-inset focus:ring-dark-700 sm:text-sm sm:leading-6"
/> />
</div> </div>
<div className="mt-2 w-fit"> <div className="mt-2 flex space-x-1">
<CheckboxDropdown <div className="w-fit">
items={formattedLists} <CheckboxDropdown
handleSelect={(list: { key: string }) => handleSelectList(list.key)} items={formattedLists}
> handleSelect={(list: { key: string }) =>
<div className="flex h-full w-full items-center rounded-[5px] border-[1px] border-dark-600 bg-dark-400 px-2 py-1.5 text-left text-xs text-dark-1000 hover:bg-dark-500"> handleSelectList(list.key)
{selectedList?.value} }
</div> >
</CheckboxDropdown> <div className="flex h-full w-full items-center rounded-[5px] border-[1px] border-dark-600 bg-dark-400 px-2 py-1 text-left text-xs text-dark-1000 hover:bg-dark-500">
{selectedList?.value}
</div>
</CheckboxDropdown>
</div>
<div className="w-fit">
<CheckboxDropdown
items={formattedMembers}
handleSelect={(list: { key: string }) =>
handleSelectMembers(list.key)
}
>
<div className="flex h-full w-full items-center rounded-[5px] border-[1px] border-dark-600 bg-dark-400 px-2 py-1 text-left text-xs text-dark-1000 hover:bg-dark-500">
{!memberPublicIds.length ? (
"Members"
) : (
<>
{memberPublicIds.map((memberPublicId) => {
const member = formattedMembers.find(
(member) => member.key === memberPublicId,
);
return (
<span
key={member?.key}
className="inline-flex h-4 w-4 items-center justify-center rounded-full bg-gray-400 ring-1 ring-dark-500"
>
<span className="text-[8px] font-medium leading-none text-white">
{member?.value
.split(" ")
.map((namePart) =>
namePart.charAt(0).toUpperCase(),
)
.join("")}
</span>
</span>
);
})}
</>
)}
</div>
</CheckboxDropdown>
</div>
</div> </div>
<div className="mt-3 flex items-center justify-end"> <div className="mt-3 flex items-center justify-end">
<span className="mr-2 text-xs text-dark-900">Create more</span> <span className="mr-2 text-xs text-dark-900">Create more</span>
<Switch <Switch

View File

@@ -20,6 +20,9 @@ interface BoardData {
name: string; name: string;
publicId: string; publicId: string;
lists: List[]; lists: List[];
workspace: {
members: Members[];
};
} }
interface List { interface List {
@@ -30,6 +33,13 @@ interface List {
cards: Card[]; cards: Card[];
} }
interface Members {
publicId: string;
user: {
name: string;
};
}
interface Card { interface Card {
publicId: string; publicId: string;
title: string; title: string;
@@ -52,6 +62,9 @@ const initialBoardData: BoardData = {
name: "", name: "",
publicId: "", publicId: "",
lists: [], lists: [],
workspace: {
members: [],
},
}; };
const BoardContext = createContext<BoardContextProps | undefined>(undefined); const BoardContext = createContext<BoardContextProps | undefined>(undefined);

View File

@@ -43,6 +43,25 @@ export const boardRouter = createTRPCRouter({
name: true, name: true,
}, },
with: { with: {
workspace: {
columns: {
publicId: true,
},
with: {
members: {
columns: {
publicId: true,
},
with: {
user: {
columns: {
name: true
}
}
}
}
}
},
lists: { lists: {
orderBy: [asc(lists.index)], orderBy: [asc(lists.index)],
where: isNull(cards.deletedAt), where: isNull(cards.deletedAt),

View File

@@ -1,5 +1,5 @@
import { z } from "zod"; import { z } from "zod";
import { and, desc, eq, isNull, sql } from "drizzle-orm"; import { and, desc, eq, isNull, inArray, sql } from "drizzle-orm";
import { cards, cardsToLabels, cardToWorkspaceMembers, labels, lists, workspaceMembers } from "~/server/db/schema"; import { cards, cardsToLabels, cardToWorkspaceMembers, labels, lists, workspaceMembers } from "~/server/db/schema";
import { generateUID } from "~/utils/generateUID"; import { generateUID } from "~/utils/generateUID";
@@ -15,6 +15,7 @@ export const cardRouter = createTRPCRouter({
z.object({ z.object({
title: z.string().min(1), title: z.string().min(1),
listPublicId: z.string().min(12), listPublicId: z.string().min(12),
memberPublicIds: z.array(z.string().min(12))
}), }),
) )
.mutation(({ ctx, input }) => { .mutation(({ ctx, input }) => {
@@ -40,13 +41,27 @@ export const cardRouter = createTRPCRouter({
orderBy: desc(cards.index) orderBy: desc(cards.index)
}); });
return tx.insert(cards).values({ const newCard = await tx.insert(cards).values({
publicId: generateUID(), publicId: generateUID(),
title: input.title, title: input.title,
createdBy: userId, createdBy: userId,
listId: list.id, listId: list.id,
index: latestCard ? latestCard.index + 1 : 0 index: latestCard ? latestCard.index + 1 : 0
}); });
if (newCard.insertId && input.memberPublicIds.length) {
const members = await tx.query.workspaceMembers.findMany({
where: inArray(workspaceMembers.publicId, input.memberPublicIds),
});
if (!members.length) return;
const membersInsert = members.map((member) => ({ cardId: Number(newCard.insertId), workspaceMemberId: member.id}))
await tx.insert(cardToWorkspaceMembers).values(membersInsert);
}
return newCard;
}) })
}), }),
addOrRemoveLabel: publicProcedure addOrRemoveLabel: publicProcedure