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"
+ >
+
+
+ );
+}
\ No newline at end of file