feat: assign members on new card
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user