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