feat: create label from new card modal
This commit is contained in:
@@ -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)}
|
||||
|
||||
@@ -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",
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -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 ?? ""}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user