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 <Menu.Items
className={twMerge( 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", position === "left" ? "left-0" : "right-0",
menuSpacingClass[menuSpacing], menuSpacingClass[menuSpacing],
)} )}
> >
<div className="p-1"> <div className="max-h-[350px] overflow-y-auto p-1">
{!selectedGroup ? ( {!selectedGroup ? (
<> <>
{items && renderMenuItems(items, null)} {items && renderMenuItems(items, null)}

View File

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

View File

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

View File

@@ -43,7 +43,7 @@ export function NewCardForm({
queryParams, queryParams,
}: NewCardFormProps) { }: NewCardFormProps) {
const { showPopup } = usePopup(); const { showPopup } = usePopup();
const { closeModal } = useModal(); const { closeModal, openModal } = useModal();
const utils = api.useUtils(); const utils = api.useUtils();
@@ -299,6 +299,11 @@ export function NewCardForm({
<CheckboxDropdown <CheckboxDropdown
items={formattedLabels} items={formattedLabels}
handleSelect={(_groupKey, item) => handleSelectLabels(item.key)} 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"> <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 ? ( {!labelPublicIds.length ? (

View File

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

View File

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

View File

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

View File

@@ -16,6 +16,20 @@ export const getAllByWorkspaceId = async (
return data ?? []; 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 ( export const getByPublicId = async (
db: SupabaseClient<Database>, db: SupabaseClient<Database>,
boardPublicId: string, boardPublicId: string,