diff --git a/apps/frontend/src/modules/aspectManagement/modals/AspectDeleteModal.tsx b/apps/frontend/src/modules/aspectManagement/modals/AspectDeleteModal.tsx new file mode 100644 index 0000000..c971a90 --- /dev/null +++ b/apps/frontend/src/modules/aspectManagement/modals/AspectDeleteModal.tsx @@ -0,0 +1,97 @@ +import client from "@/honoClient"; +import { Button, Flex, Modal, Text } from "@mantine/core"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { getRouteApi, useSearch } from "@tanstack/react-router"; +import { deleteAspect } from "../queries/aspectQueries"; +import { notifications } from "@mantine/notifications"; +import fetchRPC from "@/utils/fetchRPC"; + +const routeApi = getRouteApi("/_dashboardLayout/aspect/"); + +export default function AspectDeleteModal() { + const queryClient = useQueryClient(); + + const searchParams = useSearch({ from: "/_dashboardLayout/aspect/" }) as { + delete: string; + }; + + const aspectId = searchParams.delete; + const navigate = routeApi.useNavigate(); + + const aspectQuery = useQuery({ + queryKey: ["management-aspect", aspectId], + queryFn: async () => { + if (!aspectId) return null; + return await fetchRPC( + client["management-aspect"][":id"].$get({ + param: { + id: aspectId, + }, + query: {}, + }) + ); + }, + }); + + const mutation = useMutation({ + mutationKey: ["deleteAspectMutation"], + mutationFn: async ({ id }: { id: string }) => { + return await deleteAspect(id); + }, + onError: (error: unknown) => { + if (error instanceof Error) { + notifications.show({ + message: error.message, + color: "red", + }); + } + }, + onSuccess: () => { + notifications.show({ + message: "Aspek berhasil dihapus.", + color: "green", + }); + queryClient.removeQueries({ queryKey: ["management-aspect", aspectId] }); + queryClient.invalidateQueries({ queryKey: ["management-aspect"] }); + navigate({ search: {} }); + }, + }); + + const isModalOpen = Boolean(searchParams.delete && aspectQuery.data); + + return ( + navigate({ search: {} })} + title={`Konfirmasi Hapus`} + > + + Apakah Anda yakin ingin menghapus aspek{" "} + + {aspectQuery.data?.name} + + ? Tindakan ini tidak dapat diubah. + + + {/* Buttons */} + + + + + + ); +} \ No newline at end of file diff --git a/apps/frontend/src/modules/aspectManagement/modals/AspectFormModal.tsx b/apps/frontend/src/modules/aspectManagement/modals/AspectFormModal.tsx new file mode 100644 index 0000000..cee975f --- /dev/null +++ b/apps/frontend/src/modules/aspectManagement/modals/AspectFormModal.tsx @@ -0,0 +1,224 @@ +import { Button, Flex, Modal, ScrollArea, TextInput, Group, Text } from "@mantine/core"; +import { useForm } from "@mantine/form"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { getRouteApi } from "@tanstack/react-router"; +import { createAspect, updateAspect, getAspectByIdQueryOptions } from "../queries/aspectQueries"; +import { TbDeviceFloppy } from "react-icons/tb"; +import { useEffect } from "react"; +import { notifications } from "@mantine/notifications"; +import FormResponseError from "@/errors/FormResponseError"; +import { createId } from "@paralleldrive/cuid2"; + +// Initialize route API +const routeApi = getRouteApi("/_dashboardLayout/aspect/"); + +export default function AspectFormModal() { + const queryClient = useQueryClient(); + const navigate = routeApi.useNavigate(); + const searchParams = routeApi.useSearch(); + const dataId = searchParams.detail || searchParams.edit; + const isModalOpen = Boolean(dataId || searchParams.create); + const formType = searchParams.detail ? "detail" : searchParams.edit ? "edit" : "create"; + + // Fetch aspect data if editing or viewing details + const aspectQuery = useQuery(getAspectByIdQueryOptions(dataId)); + + const modalTitle = `${formType.charAt(0).toUpperCase() + formType.slice(1)} Aspek`; + + const form = useForm({ + initialValues: { + id: "", + name: "", + subAspects: [{ id: "", name: "", questionCount: 0 }] as { id: string; name: string; questionCount: number }[], + }, + }); + + useEffect(() => { + const data = aspectQuery.data; + + if (!data) { + form.reset(); + return; + } + + form.setValues({ + id: data.id, + name: data.name, + subAspects: data.subAspects?.map(subAspect => ({ + id: subAspect.id || "", + name: subAspect.name, + questionCount: subAspect.questionCount || 0, + })) || [], + }); + + form.setErrors({}); + }, [aspectQuery.data]); + + const mutation = useMutation({ + mutationKey: ["aspectMutation"], + mutationFn: async ( + options: + | { action: "edit"; data: Parameters[0] } + | { action: "create"; data: Parameters[0] } + ) => { + return options.action === "edit" + ? await updateAspect(options.data) + : await createAspect(options.data); + }, + onError: (error: unknown) => { + if (error instanceof FormResponseError) { + form.setErrors(error.formErrors); + return; + } + + if (error instanceof Error) { + notifications.show({ + message: error.message, + color: "red", + }); + } + }, + }); + + type CreateAspectPayload = { + name: string; + subAspects?: string; + }; + + type EditAspectPayload = { + id: string; + name: string; + subAspects?: string; + }; + + const handleSubmit = async (values: typeof form.values) => { + try { + let payload: CreateAspectPayload | EditAspectPayload; + + if (formType === "create") { + payload = { + name: values.name, + subAspects: values.subAspects.length > 0 + ? JSON.stringify( + values.subAspects + .filter(subAspect => subAspect.name.trim() !== "") + .map(subAspect => subAspect.name) + ) + : "", + }; + await createAspect(payload); + } else if (formType === "edit") { + payload = { + id: values.id, + name: values.name, + subAspects: values.subAspects.length > 0 + ? JSON.stringify( + values.subAspects + .filter(subAspect => subAspect.name.trim() !== "") + .map(subAspect => ({ + id: subAspect.id || "", + name: subAspect.name, + questionCount: subAspect.questionCount || 0, + })) + ) + : "", + }; + await updateAspect(payload); + } + + queryClient.invalidateQueries({ queryKey: ["management-aspect"] }); + + notifications.show({ + message: `Aspek ${formType === "create" ? "berhasil dibuat" : "berhasil diedit"}`, + }); + + navigate({ search: {} }); + } catch (error) { + console.error("Error during submit:", error); + } + }; + + return ( + navigate({ search: {} })} + title={modalTitle} + scrollAreaComponent={ScrollArea.Autosize} + size="md" + > +
handleSubmit(values))}> + + + {form.values.subAspects.map((field, index) => ( + + { + const newSubAspects = [...form.values.subAspects]; + newSubAspects[index] = { ...newSubAspects[index], name: event.target.value }; + form.setValues({ subAspects: newSubAspects }); + }} + disabled={formType === "detail"} + /> + Jumlah Soal: {field.questionCount} + {formType !== "detail" && ( + + )} + + ))} + + {formType !== "detail" && ( + + )} + + {/* Buttons */} + + + {formType !== "detail" && ( + + )} + + +
+ ); +} \ No newline at end of file