feat: update label
This commit is contained in:
@@ -2,9 +2,10 @@ import { createContext, useContext, useState } from "react";
|
|||||||
|
|
||||||
type ModalContextType = {
|
type ModalContextType = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
openModal: (contentType: string) => void;
|
openModal: (contentType: string, entityId?: string) => void;
|
||||||
closeModal: () => void;
|
closeModal: () => void;
|
||||||
modalContentType: string;
|
modalContentType: string;
|
||||||
|
entityId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -15,11 +16,13 @@ const ModalContext = createContext<ModalContextType | undefined>(undefined);
|
|||||||
|
|
||||||
export const ModalProvider: React.FC<Props> = ({ children }) => {
|
export const ModalProvider: React.FC<Props> = ({ children }) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [entityId, setEntityId] = useState("");
|
||||||
const [modalContentType, setModalContentType] = useState("");
|
const [modalContentType, setModalContentType] = useState("");
|
||||||
|
|
||||||
const openModal = (contentType: string) => {
|
const openModal = (contentType: string, entityId?: string) => {
|
||||||
setIsOpen(true);
|
setIsOpen(true);
|
||||||
setModalContentType(contentType);
|
setModalContentType(contentType);
|
||||||
|
if (entityId) setEntityId(entityId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
@@ -28,7 +31,7 @@ export const ModalProvider: React.FC<Props> = ({ children }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalContext.Provider
|
<ModalContext.Provider
|
||||||
value={{ isOpen, openModal, closeModal, modalContentType }}
|
value={{ isOpen, openModal, closeModal, modalContentType, entityId }}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</ModalContext.Provider>
|
</ModalContext.Provider>
|
||||||
|
|||||||
@@ -7,6 +7,19 @@ import * as cardRepo from "~/server/db/repository/card.repo";
|
|||||||
import * as labelRepo from "~/server/db/repository/label.repo";
|
import * as labelRepo from "~/server/db/repository/label.repo";
|
||||||
|
|
||||||
export const labelRouter = createTRPCRouter({
|
export const labelRouter = createTRPCRouter({
|
||||||
|
byPublicId: protectedProcedure
|
||||||
|
.input(z.object({ publicId: z.string().min(12) }))
|
||||||
|
.query(async ({ ctx, input }) => {
|
||||||
|
const label = await labelRepo.getByPublicId(ctx.db, input.publicId);
|
||||||
|
|
||||||
|
if (!label)
|
||||||
|
throw new TRPCError({
|
||||||
|
message: `Label with public ID ${input.publicId} not found`,
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
});
|
||||||
|
|
||||||
|
return label;
|
||||||
|
}),
|
||||||
create: protectedProcedure
|
create: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
@@ -42,6 +55,19 @@ export const labelRouter = createTRPCRouter({
|
|||||||
boardId: card.list.boardId,
|
boardId: card.list.boardId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}),
|
||||||
|
update: protectedProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
publicId: z.string().min(12),
|
||||||
|
name: z.string().min(1).max(36),
|
||||||
|
colourCode: z.string().length(7),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.mutation(async ({ ctx, input }) => {
|
||||||
|
const result = await labelRepo.update(ctx.db, input);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -52,10 +52,29 @@ export const getByPublicId = async (
|
|||||||
) => {
|
) => {
|
||||||
const { data } = await db
|
const { data } = await db
|
||||||
.from("label")
|
.from("label")
|
||||||
.select(`id`)
|
.select(`id, publicId, name, colourCode`)
|
||||||
.eq("publicId", labelPublicId)
|
.eq("publicId", labelPublicId)
|
||||||
.limit(1)
|
.limit(1)
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const update = async (
|
||||||
|
db: SupabaseClient<Database>,
|
||||||
|
labelInput: {
|
||||||
|
publicId: string;
|
||||||
|
name: string;
|
||||||
|
colourCode: string;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
const { data } = await db
|
||||||
|
.from("label")
|
||||||
|
.update({
|
||||||
|
name: labelInput.name,
|
||||||
|
colourCode: labelInput.colourCode,
|
||||||
|
})
|
||||||
|
.eq("publicId", labelInput.publicId);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
@@ -10,11 +10,10 @@ import Button from "~/components/Button";
|
|||||||
import Input from "~/components/Input";
|
import Input from "~/components/Input";
|
||||||
import Toggle from "~/components/Toggle";
|
import Toggle from "~/components/Toggle";
|
||||||
|
|
||||||
import { type NewLabelInput } from "~/types/router.types";
|
type LabelFormInput = {
|
||||||
|
name: string;
|
||||||
type NewLabelFormInput = NewLabelInput & {
|
|
||||||
colour: Colour;
|
colour: Colour;
|
||||||
isCreateAnotherEnabled: boolean;
|
isCreateAnotherEnabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Colour = {
|
type Colour = {
|
||||||
@@ -33,19 +32,38 @@ const colours = [
|
|||||||
{ name: "Pink", code: "#db2777" },
|
{ name: "Pink", code: "#db2777" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function NewLabelForm({ cardPublicId }: { cardPublicId: string }) {
|
export function LabelForm({
|
||||||
|
cardPublicId,
|
||||||
|
isEdit,
|
||||||
|
}: {
|
||||||
|
cardPublicId: string;
|
||||||
|
isEdit?: boolean;
|
||||||
|
}) {
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const { closeModal } = useModal();
|
const { closeModal, entityId } = useModal();
|
||||||
|
|
||||||
|
const label = api.label.byPublicId.useQuery(
|
||||||
|
{
|
||||||
|
publicId: entityId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: isEdit && !!entityId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const { control, register, reset, handleSubmit, setValue, watch } =
|
const { control, register, reset, handleSubmit, setValue, watch } =
|
||||||
useForm<NewLabelFormInput>({
|
useForm<LabelFormInput>({
|
||||||
defaultValues: {
|
values: {
|
||||||
name: "",
|
name: isEdit && label.data?.name ? label.data.name : "",
|
||||||
colour: colours[0],
|
colour: (isEdit && label.data?.colourCode
|
||||||
|
? colours.find((c) => c.code === label.data.colourCode)
|
||||||
|
: colours[0]) as Colour,
|
||||||
isCreateAnotherEnabled: false,
|
isCreateAnotherEnabled: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log({ entityId });
|
||||||
|
|
||||||
const refetchCard = () => utils.card.byId.refetch({ id: cardPublicId });
|
const refetchCard = () => utils.card.byId.refetch({ id: cardPublicId });
|
||||||
|
|
||||||
const isCreateAnotherEnabled = watch("isCreateAnotherEnabled");
|
const isCreateAnotherEnabled = watch("isCreateAnotherEnabled");
|
||||||
@@ -69,21 +87,42 @@ export function NewLabelForm({ cardPublicId }: { cardPublicId: string }) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = (values: NewLabelFormInput) => {
|
const updateLabel = api.label.update.useMutation({
|
||||||
|
onSuccess: async () => {
|
||||||
|
await refetchCard();
|
||||||
|
closeModal();
|
||||||
|
reset({
|
||||||
|
name: "",
|
||||||
|
colour: colours[0],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = (values: LabelFormInput) => {
|
||||||
if (!values.colour?.code) return;
|
if (!values.colour?.code) return;
|
||||||
|
|
||||||
createLabel.mutate({
|
if (isEdit) {
|
||||||
name: values.name,
|
updateLabel.mutate({
|
||||||
cardPublicId,
|
publicId: label.data?.publicId || "",
|
||||||
colourCode: values.colour.code,
|
name: values.name,
|
||||||
});
|
colourCode: values.colour.code,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
createLabel.mutate({
|
||||||
|
name: values.name,
|
||||||
|
cardPublicId,
|
||||||
|
colourCode: values.colour.code,
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="px-5 pt-5">
|
<div className="px-5 pt-5">
|
||||||
<div className="flex w-full items-center justify-between pb-4 text-neutral-900 dark:text-dark-1000">
|
<div className="flex w-full items-center justify-between pb-4 text-neutral-900 dark:text-dark-1000">
|
||||||
<h2 className="text-sm font-medium">New label</h2>
|
<h2 className="text-sm font-medium">
|
||||||
|
{isEdit ? "Edit label" : "New label"}
|
||||||
|
</h2>
|
||||||
<button
|
<button
|
||||||
className="rounded p-1 hover:bg-light-300 focus:outline-none dark:hover:bg-dark-300"
|
className="rounded p-1 hover:bg-light-300 focus:outline-none dark:hover:bg-dark-300"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@@ -163,16 +202,20 @@ export function NewLabelForm({ cardPublicId }: { cardPublicId: string }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-12 flex items-center justify-end border-t border-light-600 px-5 pb-5 pt-5 dark:border-dark-600">
|
<div className="mt-12 flex items-center justify-end border-t border-light-600 px-5 pb-5 pt-5 dark:border-dark-600">
|
||||||
<Toggle
|
{!isEdit && (
|
||||||
label="Create another"
|
<Toggle
|
||||||
isChecked={isCreateAnotherEnabled}
|
label="Create another"
|
||||||
onChange={() =>
|
isChecked={!!isCreateAnotherEnabled}
|
||||||
setValue("isCreateAnotherEnabled", !isCreateAnotherEnabled)
|
onChange={() =>
|
||||||
}
|
setValue("isCreateAnotherEnabled", !isCreateAnotherEnabled)
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Button type="submit">Create label</Button>
|
<Button type="submit">
|
||||||
|
{isEdit ? "Update label" : "Create label"}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -6,6 +6,8 @@ import { useForm } from "react-hook-form";
|
|||||||
|
|
||||||
import { useModal } from "~/providers/modal";
|
import { useModal } from "~/providers/modal";
|
||||||
|
|
||||||
|
import { HiEllipsisHorizontal } from "react-icons/hi2";
|
||||||
|
|
||||||
interface LabelSelectorProps {
|
interface LabelSelectorProps {
|
||||||
cardPublicId: string;
|
cardPublicId: string;
|
||||||
labels: {
|
labels: {
|
||||||
@@ -103,7 +105,7 @@ export default function LabelSelector({
|
|||||||
{() => (
|
{() => (
|
||||||
<div
|
<div
|
||||||
key={label.publicId}
|
key={label.publicId}
|
||||||
className="flex items-center rounded-[5px] p-2 hover:bg-light-200 dark:hover:bg-dark-300"
|
className="group flex items-center rounded-[5px] p-2 hover:bg-light-200 dark:hover:bg-dark-300"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const newValue = !watch(label.publicId);
|
const newValue = !watch(label.publicId);
|
||||||
setValue(label.publicId, newValue);
|
setValue(label.publicId, newValue);
|
||||||
@@ -124,12 +126,23 @@ export default function LabelSelector({
|
|||||||
{...register(label.publicId)}
|
{...register(label.publicId)}
|
||||||
checked={watch(label.publicId)}
|
checked={watch(label.publicId)}
|
||||||
/>
|
/>
|
||||||
<label
|
<div className="flex w-full items-center justify-between">
|
||||||
htmlFor={label.publicId}
|
<label
|
||||||
className="ml-3 text-sm"
|
htmlFor={label.publicId}
|
||||||
>
|
className="ml-3 text-sm"
|
||||||
{label.name}
|
>
|
||||||
</label>
|
{label.name}
|
||||||
|
</label>
|
||||||
|
<button
|
||||||
|
className="invisible group-hover:visible"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
openModal("EDIT_LABEL", label.publicId);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<HiEllipsisHorizontal size={20} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { DeleteCardConfirmation } from "./components/DeleteCardConfirmation";
|
|||||||
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";
|
||||||
import { NewLabelForm } from "./components/NewLabelForm";
|
import { LabelForm } from "./components/LabelForm";
|
||||||
import { NewWorkspaceForm } from "~/components/NewWorkspaceForm";
|
import { NewWorkspaceForm } from "~/components/NewWorkspaceForm";
|
||||||
|
|
||||||
import Modal from "~/components/modal";
|
import Modal from "~/components/modal";
|
||||||
@@ -177,7 +177,10 @@ export default function CardPage() {
|
|||||||
|
|
||||||
<Modal>
|
<Modal>
|
||||||
{modalContentType === "NEW_LABEL" && (
|
{modalContentType === "NEW_LABEL" && (
|
||||||
<NewLabelForm cardPublicId={cardId} />
|
<LabelForm cardPublicId={cardId} />
|
||||||
|
)}
|
||||||
|
{modalContentType === "EDIT_LABEL" && (
|
||||||
|
<LabelForm cardPublicId={cardId} isEdit />
|
||||||
)}
|
)}
|
||||||
{modalContentType === "DELETE_CARD" && (
|
{modalContentType === "DELETE_CARD" && (
|
||||||
<DeleteCardConfirmation
|
<DeleteCardConfirmation
|
||||||
|
|||||||
Reference in New Issue
Block a user