feat: create label from new card modal

This commit is contained in:
Henry
2025-04-17 14:09:56 +01:00
parent fa16c7ccbd
commit e9211464d6
8 changed files with 77 additions and 31 deletions

View File

@@ -135,12 +135,12 @@ export default function CheckboxDropdown({
>
<Menu.Items
className={twMerge(
"absolute z-50 mt-2 w-56 origin-top-left rounded-md border-[1px] border-light-200 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200",
"mt-2s absolute z-50 w-56 origin-top-left rounded-md border-[1px] border-light-200 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200",
position === "left" ? "left-0" : "right-0",
menuSpacingClass[menuSpacing],
)}
>
<div className="p-1">
<div className="max-h-[350px] overflow-y-auto p-1">
{!selectedGroup ? (
<>
{items && renderMenuItems(items, null)}

View File

@@ -4,20 +4,17 @@ import { usePopup } from "~/providers/popup";
import { api } from "~/utils/api";
export function DeleteLabelConfirmation({
cardPublicId,
labelPublicId,
refetch,
}: {
cardPublicId: string;
labelPublicId: string;
refetch: () => void;
}) {
const utils = api.useUtils();
const { closeModal } = useModal();
const { showPopup } = usePopup();
const refetchCard = () => utils.card.byId.refetch({ cardPublicId });
const deleteLabelMutation = api.label.delete.useMutation({
onSuccess: () => refetchCard(),
onSuccess: () => refetch(),
onError: () =>
showPopup({
header: "Error deleting label",

View File

@@ -23,13 +23,14 @@ interface Colour {
}
export function LabelForm({
cardPublicId,
boardPublicId,
refetch,
isEdit,
}: {
cardPublicId: string;
boardPublicId: string;
refetch: () => void;
isEdit?: boolean;
}) {
const utils = api.useUtils();
const { closeModal, entityId, openModal } = useModal();
const label = api.label.byPublicId.useQuery(
@@ -52,17 +53,15 @@ export function LabelForm({
},
});
const refetchCard = () => utils.card.byId.refetch({ cardPublicId });
const isCreateAnotherEnabled = watch("isCreateAnotherEnabled");
const createLabel = api.label.create.useMutation({
onSuccess: async () => {
onSuccess: () => {
const currentColourIndex = colours.findIndex(
(c) => c.code === watch("colour").code,
);
try {
await refetchCard();
refetch();
if (!isCreateAnotherEnabled) closeModal();
reset({
name: "",
@@ -76,8 +75,8 @@ export function LabelForm({
});
const updateLabel = api.label.update.useMutation({
onSuccess: async () => {
await refetchCard();
onSuccess: () => {
refetch();
closeModal();
reset({
name: "",
@@ -98,8 +97,8 @@ export function LabelForm({
} else {
createLabel.mutate({
name: values.name,
cardPublicId,
colourCode: values.colour.code,
boardPublicId,
});
}
};

View File

@@ -43,7 +43,7 @@ export function NewCardForm({
queryParams,
}: NewCardFormProps) {
const { showPopup } = usePopup();
const { closeModal } = useModal();
const { closeModal, openModal } = useModal();
const utils = api.useUtils();
@@ -299,6 +299,11 @@ export function NewCardForm({
<CheckboxDropdown
items={formattedLabels}
handleSelect={(_groupKey, item) => handleSelectLabels(item.key)}
handleEdit={(labelPublicId) =>
openModal("EDIT_LABEL", labelPublicId)
}
handleCreate={() => openModal("NEW_LABEL")}
createNewItemLabel="Create new label"
>
<div className="flex h-full w-full items-center rounded-[5px] border-[1px] border-light-600 bg-light-200 px-2 py-1 text-left text-xs text-light-800 hover:bg-light-300 dark:border-dark-600 dark:bg-dark-400 dark:text-dark-1000 dark:hover:bg-dark-500">
{!labelPublicIds.length ? (

View File

@@ -11,6 +11,8 @@ import { HiOutlinePlusSmall, HiOutlineSquare3Stack3D } from "react-icons/hi2";
import type { UpdateBoardInput } from "@kan/api/types";
import Button from "~/components/Button";
import { DeleteLabelConfirmation } from "~/components/DeleteLabelConfirmation";
import { LabelForm } from "~/components/LabelForm";
import Modal from "~/components/modal";
import { NewWorkspaceForm } from "~/components/NewWorkspaceForm";
import { PageHead } from "~/components/PageHead";
@@ -40,7 +42,7 @@ export default function BoardPage() {
const utils = api.useUtils();
const { showPopup } = usePopup();
const { workspace } = useWorkspace();
const { openModal, modalContentType } = useModal();
const { openModal, modalContentType, entityId } = useModal();
const [selectedPublicListId, setSelectedPublicListId] =
useState<PublicListId>("");
const [isInitialLoading, setIsInitialLoading] = useState(true);
@@ -78,6 +80,10 @@ export default function BoardPage() {
placeholderData: keepPreviousData,
});
const refetchBoard = async () => {
if (boardId) await utils.board.byId.refetch({ boardPublicId: boardId });
};
useEffect(() => {
if (boardId) {
setIsInitialLoading(false);
@@ -409,6 +415,22 @@ export default function BoardPage() {
/>
)}
{modalContentType === "NEW_WORKSPACE" && <NewWorkspaceForm />}
{modalContentType === "NEW_LABEL" && (
<LabelForm boardPublicId={boardId ?? ""} refetch={refetchBoard} />
)}
{modalContentType === "EDIT_LABEL" && (
<LabelForm
boardPublicId={boardId ?? ""}
refetch={refetchBoard}
isEdit
/>
)}
{modalContentType === "DELETE_LABEL" && (
<DeleteLabelConfirmation
refetch={refetchBoard}
labelPublicId={entityId}
/>
)}
{modalContentType === "UPDATE_BOARD_SLUG" && (
<UpdateBoardSlugForm
boardPublicId={boardId ?? ""}

View File

@@ -5,6 +5,7 @@ import { useForm } from "react-hook-form";
import { IoChevronForwardSharp } from "react-icons/io5";
import Avatar from "~/components/Avatar";
import { LabelForm } from "~/components/LabelForm";
import LabelIcon from "~/components/LabelIcon";
import Modal from "~/components/modal";
import { NewWorkspaceForm } from "~/components/NewWorkspaceForm";
@@ -15,12 +16,11 @@ import { useWorkspace } from "~/providers/workspace";
import { api } from "~/utils/api";
import { formatMemberDisplayName } from "~/utils/helpers";
import { getPublicUrl } from "~/utils/supabase/getPublicUrl";
import { DeleteLabelConfirmation } from "../../components/DeleteLabelConfirmation";
import ActivityList from "./components/ActivityList";
import { DeleteCardConfirmation } from "./components/DeleteCardConfirmation";
import { DeleteCommentConfirmation } from "./components/DeleteCommentConfirmation";
import { DeleteLabelConfirmation } from "./components/DeleteLabelConfirmation";
import Dropdown from "./components/Dropdown";
import { LabelForm } from "./components/LabelForm";
import LabelSelector from "./components/LabelSelector";
import ListSelector from "./components/ListSelector";
import MemberSelector from "./components/MemberSelector";
@@ -47,6 +47,10 @@ export default function CardPage() {
cardPublicId: cardId ?? "",
});
const refetchCard = async () => {
if (cardId) await utils.card.byId.refetch({ cardPublicId: cardId });
};
const board = card?.list?.board;
const boardId = board?.publicId;
const labels = board?.labels;
@@ -246,14 +250,18 @@ export default function CardPage() {
<Modal>
{modalContentType === "NEW_LABEL" && (
<LabelForm cardPublicId={cardId} />
<LabelForm boardPublicId={boardId ?? ""} refetch={refetchCard} />
)}
{modalContentType === "EDIT_LABEL" && (
<LabelForm cardPublicId={cardId} isEdit />
<LabelForm
boardPublicId={boardId ?? ""}
refetch={refetchCard}
isEdit
/>
)}
{modalContentType === "DELETE_LABEL" && (
<DeleteLabelConfirmation
cardPublicId={cardId}
refetch={refetchCard}
labelPublicId={entityId}
/>
)}

View File

@@ -1,6 +1,7 @@
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import * as boardRepo from "@kan/db/repository/board.repo";
import * as cardRepo from "@kan/db/repository/card.repo";
import * as labelRepo from "@kan/db/repository/label.repo";
@@ -45,7 +46,7 @@ export const labelRouter = createTRPCRouter({
.input(
z.object({
name: z.string().min(1).max(36),
cardPublicId: z.string().min(12),
boardPublicId: z.string().min(12),
colourCode: z.string().length(7),
}),
)
@@ -59,14 +60,14 @@ export const labelRouter = createTRPCRouter({
code: "UNAUTHORIZED",
});
const card = await cardRepo.getCardWithListByPublicId(
const board = await boardRepo.getIdByPublicId(
ctx.db,
input.cardPublicId,
input.boardPublicId,
);
if (!card?.list)
if (!board)
throw new TRPCError({
message: `Card with public ID ${input.cardPublicId} not found`,
message: `Board with public ID ${input.boardPublicId} not found`,
code: "NOT_FOUND",
});
@@ -74,7 +75,7 @@ export const labelRouter = createTRPCRouter({
name: input.name,
colourCode: input.colourCode,
createdBy: userId,
boardId: card.list.boardId,
boardId: board.id,
});
if (!result)

View File

@@ -16,6 +16,20 @@ export const getAllByWorkspaceId = async (
return data ?? [];
};
export const getIdByPublicId = async (
db: SupabaseClient<Database>,
boardPublicId: string,
) => {
const { data } = await db
.from("board")
.select("id")
.eq("publicId", boardPublicId)
.limit(1)
.single();
return data;
};
export const getByPublicId = async (
db: SupabaseClient<Database>,
boardPublicId: string,