From f7ea4ed6325b9bbb6eed47a53489edcc6b20a971 Mon Sep 17 00:00:00 2001 From: percyfikri Date: Fri, 27 Sep 2024 13:49:06 +0700 Subject: [PATCH 01/12] Create : Integrasi FE & BE Fix GetAll By Id --- apps/backend/src/data/sidebarMenus.ts | 7 ++ apps/backend/src/middlewares/authInfo.ts | 1 + .../src/routes/assessmentRequest/route.ts | 78 +++++++++++++---- apps/backend/src/types/HonoEnv.d.ts | 1 + .../queries/assessmentRequestQueries.ts | 68 +++++++++++++++ .../assessmentRequest/index.lazy.tsx | 87 +++++++++++++++++++ .../assessmentRequest/index.tsx | 18 ++++ 7 files changed, 243 insertions(+), 17 deletions(-) create mode 100644 apps/frontend/src/modules/assessmentRequestManagement/queries/assessmentRequestQueries.ts create mode 100644 apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx create mode 100644 apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.tsx diff --git a/apps/backend/src/data/sidebarMenus.ts b/apps/backend/src/data/sidebarMenus.ts index ec98d7e..9cb2421 100644 --- a/apps/backend/src/data/sidebarMenus.ts +++ b/apps/backend/src/data/sidebarMenus.ts @@ -21,6 +21,13 @@ const sidebarMenus: SidebarMenu[] = [ link: "/questions", color: "green", }, + { + label: "Permohonan Asesmen", + icon: { tb: "TbChecklist" }, + allowedPermissions: ["permissions.read"], + link: "/assessmentRequest", + color: "green", + }, ]; export default sidebarMenus; diff --git a/apps/backend/src/middlewares/authInfo.ts b/apps/backend/src/middlewares/authInfo.ts index 10514c2..957dc24 100644 --- a/apps/backend/src/middlewares/authInfo.ts +++ b/apps/backend/src/middlewares/authInfo.ts @@ -71,6 +71,7 @@ const authInfo = createMiddleware(async (c, next) => { // Setting the currentUser with fetched data c.set("currentUser", { + id: user[0].users.id, // Adding user ID here name: user[0].users.name, // Assuming the first result is the user permissions: Array.from(permissions), roles: Array.from(roles), diff --git a/apps/backend/src/routes/assessmentRequest/route.ts b/apps/backend/src/routes/assessmentRequest/route.ts index 3f189a9..3332c71 100644 --- a/apps/backend/src/routes/assessmentRequest/route.ts +++ b/apps/backend/src/routes/assessmentRequest/route.ts @@ -1,4 +1,4 @@ -import { eq } from "drizzle-orm"; +import { eq, sql, ilike, isNull, and } from "drizzle-orm"; import { Hono } from "hono"; import { z } from "zod"; import db from "../../drizzle"; @@ -20,23 +20,53 @@ const assessmentRequestRoute = new Hono() // Get assessment request by user ID .get( - "/:id", + "/", checkPermission("assessmentRequest.read"), requestValidator( "query", z.object({ - includeTrashed: z.string().default("false"), + page: z.coerce.number().int().min(0).default(0), // Menambahkan pagination page + limit: z.coerce.number().int().min(1).max(1000).default(10), // Menambahkan pagination limit + q: z.string().optional(), // Kata kunci pencarian (search) }) ), async (c) => { - const userId = c.req.param("id"); - + const currentUser = c.get("currentUser"); + const userId = currentUser?.id; // Mengambil userId dari currentUser yang disimpan di context + if (!userId) { + // Handle case where userId is undefined + return c.text("User not authenticated", 401); + } + + const { page, limit, q } = c.req.valid("query"); + + // Query untuk menghitung total data + const totalCountQuery = db + .select({ + count: sql`count(distinct ${assessments.id})`, + }) + .from(assessments) + .leftJoin(respondents, eq(assessments.respondentId, respondents.id)) + .leftJoin(users, eq(respondents.userId, users.id)) + .where( + and( + eq(users.id, userId), + q ? ilike(assessments.status, `%${q}%`) : undefined + ) + ) + + const totalCountResult = await totalCountQuery; + const totalItems = totalCountResult[0]?.count || 0; + + // Query untuk mendapatkan data assessment dengan pagination const queryResult = await db .select({ userId: users.id, createdAt: assessments.createdAt, name: users.name, code: rolesSchema.code, + id: assessments.id, + tanggal: assessments.createdAt, status: assessments.status, }) .from(users) @@ -44,21 +74,34 @@ const assessmentRequestRoute = new Hono() .leftJoin(rolesSchema, eq(rolesToUsers.roleId, rolesSchema.id)) .leftJoin(respondents, eq(users.id, respondents.userId)) .leftJoin(assessments, eq(respondents.id, assessments.respondentId)) - .where(eq(users.id, userId)); - + .where( + and( + eq(users.id, userId), + q ? ilike(assessments.status, `%${q}%`) : undefined + ) + ) + .offset(page * limit) + .limit(limit); + if (!queryResult[0]) throw notFound(); - - const assessmentRequestData = { - ...queryResult, - }; - - return c.json(assessmentRequestData); + + // Mengembalikan data dengan metadata pagination + return c.json({ + data: queryResult, + _metadata: { + currentPage: page, + totalPages: Math.ceil(totalItems / limit), + totalItems, + perPage: limit, + }, + }); } ) + // Post assessment request by user ID .post( - "/:id", + "/", checkPermission("assessmentRequest.create"), requestValidator( "json", @@ -68,11 +111,12 @@ const assessmentRequestRoute = new Hono() ), async (c) => { const { respondentId } = c.req.valid("json"); - const userId = c.req.param("id"); + const currentUser = c.get("currentUser"); + const userId = currentUser?.id; // Mengambil userId dari currentUser yang disimpan di context // Make sure the userId exists if (!userId) { - throw new HTTPException(400, { message: "User ID is required." }); + throw new HTTPException(400, { message: "User not authenticated" }); } // Validate if respondent exists @@ -101,5 +145,5 @@ const assessmentRequestRoute = new Hono() return c.json({ message: "Successfully submitted the assessment request" }, 201); } ); - + export default assessmentRequestRoute; \ No newline at end of file diff --git a/apps/backend/src/types/HonoEnv.d.ts b/apps/backend/src/types/HonoEnv.d.ts index 4cf35db..e9ceb7d 100644 --- a/apps/backend/src/types/HonoEnv.d.ts +++ b/apps/backend/src/types/HonoEnv.d.ts @@ -5,6 +5,7 @@ type HonoEnv = { Variables: { uid?: string; currentUser?: { + id: string; name: string; permissions: SpecificPermissionCode[]; roles: RoleCode[]; diff --git a/apps/frontend/src/modules/assessmentRequestManagement/queries/assessmentRequestQueries.ts b/apps/frontend/src/modules/assessmentRequestManagement/queries/assessmentRequestQueries.ts new file mode 100644 index 0000000..17344f6 --- /dev/null +++ b/apps/frontend/src/modules/assessmentRequestManagement/queries/assessmentRequestQueries.ts @@ -0,0 +1,68 @@ +import client from "@/honoClient"; +import fetchRPC from "@/utils/fetchRPC"; +import { queryOptions } from "@tanstack/react-query"; +import { InferRequestType } from "hono"; + +export const assessmentRequestQueryOptions = (page: number, limit: number, q?: string) => + queryOptions({ + queryKey: ["assesmentRequest", { page, limit, q }], + queryFn: () => + fetchRPC( + client.assessmentRequest.$get({ + query: { + limit: String(limit), + page: String(page), + q, + }, + }) + ), + }); + +export const getUserByIdQueryOptions = (userId: string | undefined) => + queryOptions({ + queryKey: ["user", userId], + queryFn: () => + fetchRPC( + client.users[":id"].$get({ + param: { + id: userId!, + }, + query: {}, + }) + ), + enabled: Boolean(userId), + }); + +export const createUser = async ( + form: InferRequestType["form"] +) => { + return await fetchRPC( + client.users.$post({ + form, + }) + ); +}; + +export const updateUser = async ( + form: InferRequestType<(typeof client.users)[":id"]["$patch"]>["form"] & { + id: string; + } +) => { + return await fetchRPC( + client.users[":id"].$patch({ + param: { + id: form.id, + }, + form, + }) + ); +}; + +export const deleteUser = async (id: string) => { + return await fetchRPC( + client.users[":id"].$delete({ + param: { id }, + form: {}, + }) + ); +}; diff --git a/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx b/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx new file mode 100644 index 0000000..08db5ed --- /dev/null +++ b/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx @@ -0,0 +1,87 @@ +import { assessmentRequestQueryOptions } from "@/modules/assessmentRequestManagement/queries/assessmentRequestQueries"; +import PageTemplate from "@/components/PageTemplate"; +import { createLazyFileRoute } from "@tanstack/react-router"; +import UserFormModal from "@/modules/assessmentRequestManagement/modals/UserFormModal"; +import ExtractQueryDataType from "@/types/ExtractQueryDataType"; +import { createColumnHelper } from "@tanstack/react-table"; +import { Badge, Flex } from "@mantine/core"; +import UserDeleteModal from "@/modules/assessmentRequestManagement/modals/UserDeleteModal"; +import { Button } from "@/shadcn/components/ui/button"; + +export const Route = createLazyFileRoute("/_dashboardLayout/assessmentRequest/")({ + component: UsersPage, +}); + +type DataType = ExtractQueryDataType; + +const columnHelper = createColumnHelper(); + +export default function UsersPage() { + return ( + , ]} + columnDefs={[ + columnHelper.display({ + header: "No", + cell: (props) => props.row.index + 1, + }), + columnHelper.display({ + header: "Tanggal", + cell: (props) => + props.row.original.createdAt + ? new Intl.DateTimeFormat("ID", { + year: "numeric", + month: "long", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + hour12: true, + }).format(new Date(props.row.original.createdAt)) + : 'N/A', + }), + + columnHelper.display({ + header: "Status", + cell: (props) => { + const status = props.row.original.status; + switch (status) { + case "menunggu konfirmasi": + return Menunggu Konfirmasi; + case "diterima": + return Diterima; + case "ditolak": + return Ditolak; + case "selesai": + return Selesai; + default: + return Tidak diketahui; + } + }, + }), + + columnHelper.display({ + header: "Actions", + cell: (props) => { + const status = props.row.original.status; + + return ( + + {status === "selesai" ? ( + + ) : status === "diterima" ? ( + + ) : status === "menunggu konfirmasi" || status === "ditolak" ? ( + + ) : null} + + ); + }, + }), + ]} + /> + ); +} \ No newline at end of file diff --git a/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.tsx b/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.tsx new file mode 100644 index 0000000..3eb169d --- /dev/null +++ b/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.tsx @@ -0,0 +1,18 @@ +import { assessmentRequestQueryOptions } from "@/modules/assessmentRequestManagement/queries/assessmentRequestQueries" +import { createFileRoute } from "@tanstack/react-router"; +import { z } from "zod"; + +const searchParamSchema = z.object({ + create: z.boolean().default(false).optional(), + edit: z.string().default("").optional(), + delete: z.string().default("").optional(), + detail: z.string().default("").optional(), +}); + +export const Route = createFileRoute("/_dashboardLayout/assessmentRequest/")({ + validateSearch: searchParamSchema, + + loader: ({ context: { queryClient } }) => { + queryClient.ensureQueryData(assessmentRequestQueryOptions(0, 10)); + }, +}); From 4453cc49d734a98f8b891fa91394e09c19414b79 Mon Sep 17 00:00:00 2001 From: percyfikri Date: Fri, 27 Sep 2024 16:27:20 +0700 Subject: [PATCH 02/12] Update : Post on AssessmentRequest and typo on respondents schema --- .../backend/src/drizzle/schema/respondents.ts | 2 +- .../src/routes/assessmentRequest/route.ts | 21 +++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/apps/backend/src/drizzle/schema/respondents.ts b/apps/backend/src/drizzle/schema/respondents.ts index 744ec4f..98d8805 100644 --- a/apps/backend/src/drizzle/schema/respondents.ts +++ b/apps/backend/src/drizzle/schema/respondents.ts @@ -15,7 +15,7 @@ export const respondents = pgTable("respondents", { phoneNumber: varchar("phoneNumber", { length: 13 }).notNull(), createdAt: timestamp("createdAt", { mode: "date" }).defaultNow(), updatedAt: timestamp("updatedAt", { mode: "date" }).defaultNow(), - deletedAt: timestamp("deletetAt", { mode: "date" }), + deletedAt: timestamp("deletedAt", { mode: "date" }), }); export const respondentsRelations = relations(respondents, ({ one }) => ({ diff --git a/apps/backend/src/routes/assessmentRequest/route.ts b/apps/backend/src/routes/assessmentRequest/route.ts index 3332c71..8499b35 100644 --- a/apps/backend/src/routes/assessmentRequest/route.ts +++ b/apps/backend/src/routes/assessmentRequest/route.ts @@ -97,7 +97,6 @@ const assessmentRequestRoute = new Hono() }); } ) - // Post assessment request by user ID .post( @@ -106,7 +105,7 @@ const assessmentRequestRoute = new Hono() requestValidator( "json", z.object({ - respondentId: z.string().min(1), + respondentId: z.string().min(1), // Memastikan respondentId minimal ada }) ), async (c) => { @@ -114,36 +113,36 @@ const assessmentRequestRoute = new Hono() const currentUser = c.get("currentUser"); const userId = currentUser?.id; // Mengambil userId dari currentUser yang disimpan di context - // Make sure the userId exists + // Memastikan user sudah terautentikasi if (!userId) { - throw new HTTPException(400, { message: "User not authenticated" }); + return c.text("User not authenticated", 401); } - // Validate if respondent exists + // Validasi apakah respondent dengan respondentId tersebut ada const respondent = await db .select() .from(respondents) - .where(eq(respondents.id, respondentId)); + .where(and(eq(respondents.id, respondentId), eq(respondents.userId, userId))); if (!respondent.length) { - throw new HTTPException(404, { message: "Respondent not found." }); + throw new HTTPException(404, { message: "Respondent not found or unauthorized." }); } - // Create the assessment request + // Membuat permohonan asesmen baru const newAssessment = await db .insert(assessments) .values({ id: createId(), respondentId, - status: "menunggu konfirmasi", + status: "menunggu konfirmasi", // Status awal permohonan validatedBy: null, validatedAt: null, createdAt: new Date(), }) .returning(); - return c.json({ message: "Successfully submitted the assessment request" }, 201); + return c.json({ message: "Successfully submitted the assessment request", data: newAssessment }, 201); } ); - + export default assessmentRequestRoute; \ No newline at end of file From e7cf3e881f57e142e720ea063c8503458540232e Mon Sep 17 00:00:00 2001 From: percyfikri Date: Mon, 30 Sep 2024 16:32:45 +0700 Subject: [PATCH 03/12] update : condition on Get method --- apps/backend/src/routes/assessmentRequest/route.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/backend/src/routes/assessmentRequest/route.ts b/apps/backend/src/routes/assessmentRequest/route.ts index 8499b35..07e823e 100644 --- a/apps/backend/src/routes/assessmentRequest/route.ts +++ b/apps/backend/src/routes/assessmentRequest/route.ts @@ -9,7 +9,7 @@ import { rolesToUsers } from "../../drizzle/schema/rolesToUsers"; import { rolesSchema } from "../../drizzle/schema/roles"; import HonoEnv from "../../types/HonoEnv"; import authInfo from "../../middlewares/authInfo"; -import { notFound } from "../../errors/DashboardError"; +import { forbidden, notFound } from "../../errors/DashboardError"; import checkPermission from "../../middlewares/checkPermission"; import requestValidator from "../../utils/requestValidator"; import { HTTPException } from "hono/http-exception"; @@ -33,11 +33,12 @@ const assessmentRequestRoute = new Hono() async (c) => { const currentUser = c.get("currentUser"); const userId = currentUser?.id; // Mengambil userId dari currentUser yang disimpan di context - if (!userId) { - // Handle case where userId is undefined - return c.text("User not authenticated", 401); - } + if (!userId) { + throw forbidden({ + message: "User not authenticated" + }); + } const { page, limit, q } = c.req.valid("query"); // Query untuk menghitung total data From ec545c3d7b8d207b5fcc24188ff28977c1dddb0b Mon Sep 17 00:00:00 2001 From: percyfikri Date: Thu, 3 Oct 2024 13:43:20 +0700 Subject: [PATCH 04/12] Update : Integrasi on Create Assessment Request --- .../backend/src/drizzle/schema/assessments.ts | 2 +- .../src/routes/assessmentRequest/route.ts | 7 +- .../modals/FormModal.tsx | 191 ++++++++++++++++++ .../queries/assessmentRequestQueries.ts | 56 +---- apps/frontend/src/routeTree.gen.ts | 66 ++++++ .../assessmentRequest/index.lazy.tsx | 7 +- apps/frontend/vite.config.ts | 2 +- 7 files changed, 277 insertions(+), 54 deletions(-) create mode 100644 apps/frontend/src/modules/assessmentRequestManagement/modals/FormModal.tsx diff --git a/apps/backend/src/drizzle/schema/assessments.ts b/apps/backend/src/drizzle/schema/assessments.ts index 75b9ccd..fd228cc 100644 --- a/apps/backend/src/drizzle/schema/assessments.ts +++ b/apps/backend/src/drizzle/schema/assessments.ts @@ -4,7 +4,7 @@ import { relations } from "drizzle-orm"; import { respondents } from "./respondents"; import { users } from "./users"; -const statusEnum = pgEnum("status", ["menunggu konfirmasi", "disetujui", "ditolak", "selesai"]); +const statusEnum = pgEnum("status", ["menunggu konfirmasi", "diterima", "ditolak", "selesai"]); export const assessments = pgTable("assessments", { id: varchar("id", { length: 50 }) diff --git a/apps/backend/src/routes/assessmentRequest/route.ts b/apps/backend/src/routes/assessmentRequest/route.ts index 07e823e..efc8cdf 100644 --- a/apps/backend/src/routes/assessmentRequest/route.ts +++ b/apps/backend/src/routes/assessmentRequest/route.ts @@ -1,4 +1,4 @@ -import { eq, sql, ilike, isNull, and } from "drizzle-orm"; +import { eq, sql, ilike, isNull, and, desc } from "drizzle-orm"; import { Hono } from "hono"; import { z } from "zod"; import db from "../../drizzle"; @@ -69,6 +69,9 @@ const assessmentRequestRoute = new Hono() id: assessments.id, tanggal: assessments.createdAt, status: assessments.status, + respondentId: respondents.id, + email: users.email, + username: users.username, }) .from(users) .leftJoin(rolesToUsers, eq(users.id, rolesToUsers.userId)) @@ -81,8 +84,10 @@ const assessmentRequestRoute = new Hono() q ? ilike(assessments.status, `%${q}%`) : undefined ) ) + .orderBy(desc(assessments.createdAt)) .offset(page * limit) .limit(limit); + if (!queryResult[0]) throw notFound(); diff --git a/apps/frontend/src/modules/assessmentRequestManagement/modals/FormModal.tsx b/apps/frontend/src/modules/assessmentRequestManagement/modals/FormModal.tsx new file mode 100644 index 0000000..9a896d0 --- /dev/null +++ b/apps/frontend/src/modules/assessmentRequestManagement/modals/FormModal.tsx @@ -0,0 +1,191 @@ +import stringToColorHex from "@/utils/stringToColorHex"; +import { + Avatar, + Button, + Center, + Flex, + Modal, + ScrollArea, + Stack, + Text +} from "@mantine/core"; +// import { Button } from "@/shadcn/components/ui/button"; +import { useForm } from "@mantine/form"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { getRouteApi } from "@tanstack/react-router"; +import { useEffect } from "react"; +import { notifications } from "@mantine/notifications"; +import FormResponseError from "@/errors/FormResponseError"; +import createInputComponents from "@/utils/createInputComponents"; +import { assessmentRequestQueryOptions, createAssessmentRequest } from "../queries/assessmentRequestQueries"; + +/** + * Change this + */ +const routeApi = getRouteApi("/_dashboardLayout/assessmentRequest/"); + +export default function UserFormModal() { + /** + * DON'T CHANGE FOLLOWING: + */ + const queryClient = useQueryClient(); + + const navigate = routeApi.useNavigate(); + + const searchParams = routeApi.useSearch(); + + const dataId = searchParams.detail || searchParams.edit; + + const isModalOpen = Boolean(dataId || searchParams.create); + + const detailId = searchParams.detail; + const editId = searchParams.edit; + + const formType = detailId ? "detail" : editId ? "edit" : "create"; + + /** + * CHANGE FOLLOWING: + */ + + const userQuery = useQuery(assessmentRequestQueryOptions(0, 10)); + + const modalTitle = + + Konfirmasi + + + const form = useForm({ + initialValues: { + id: "", + respondentsId: "", + name: "", + }, + }); + + useEffect(() => { + const data = userQuery.data; + + if (!data) { + form.reset(); + return; + } + + form.setValues({ + id: data.data[0].id ?? "", + respondentsId: data.data[0].respondentId ?? "", + name: data.data[0].name ?? "", + }); + + form.setErrors({}); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [userQuery.data]); + + const mutation = useMutation({ + mutationKey: ["usersMutation"], + mutationFn: async (options: { action: "create"; data: { respondentsId: string } }) => { + console.log("called"); + if (options.action === "create") { + return await createAssessmentRequest(options.data); + } + }, + onError: (error: unknown) => { + console.log(error); + + if (error instanceof FormResponseError) { + form.setErrors(error.formErrors); + return; + } + + if (error instanceof Error) { + notifications.show({ + message: error.message, + color: "red", + }); + } + }, + }); + + const handleSubmit = async (values: typeof form.values) => { + if (formType === "detail") return; + + if (formType === "create") { + try { + await mutation.mutateAsync({ + action: "create", + data: { + respondentsId: values.respondentsId, + }, + }); + notifications.show({ + message: "Permohonan Asesmen berhasil dibuat!", + color: "green", + }); + } catch (error) { + console.error(error); + } + } + + queryClient.invalidateQueries({ queryKey: ["users"] }); + navigate({ search: {} }); + }; + + + return ( + navigate({ search: {} })} + title= {modalTitle} + size="md" + > +
handleSubmit(values))}> + + Apakah anda yakin ingin membuat Permohonan Asesmen Baru? + + {createInputComponents({ + disableAll: mutation.isPending, + readonlyAll: formType === "detail", + inputs: [ + { + type: "text", + label: "User ID", + readOnly: true, + variant: "filled", + ...form.getInputProps("id"), + hidden: true + }, + { + type: "text", + label: "Respondent ID", + ...form.getInputProps("respondentsId"), + hidden: true, + }, + { + type: "text", + label: "Name", + ...form.getInputProps("name"), + hidden: true, + }, + ], + })} + + {/* Buttons */} + + + + +
+
+ ); +} diff --git a/apps/frontend/src/modules/assessmentRequestManagement/queries/assessmentRequestQueries.ts b/apps/frontend/src/modules/assessmentRequestManagement/queries/assessmentRequestQueries.ts index 17344f6..95f6923 100644 --- a/apps/frontend/src/modules/assessmentRequestManagement/queries/assessmentRequestQueries.ts +++ b/apps/frontend/src/modules/assessmentRequestManagement/queries/assessmentRequestQueries.ts @@ -1,7 +1,6 @@ import client from "@/honoClient"; import fetchRPC from "@/utils/fetchRPC"; import { queryOptions } from "@tanstack/react-query"; -import { InferRequestType } from "hono"; export const assessmentRequestQueryOptions = (page: number, limit: number, q?: string) => queryOptions({ @@ -18,51 +17,14 @@ export const assessmentRequestQueryOptions = (page: number, limit: number, q?: s ), }); -export const getUserByIdQueryOptions = (userId: string | undefined) => - queryOptions({ - queryKey: ["user", userId], - queryFn: () => - fetchRPC( - client.users[":id"].$get({ - param: { - id: userId!, - }, - query: {}, - }) - ), - enabled: Boolean(userId), +export const createAssessmentRequest = async ({ respondentsId }: { respondentsId: string }) => { + const response = await client.assessmentRequest.$post({ + json: { respondentId: respondentsId }, // pastikan key-nya sesuai dengan backend API Anda }); - -export const createUser = async ( - form: InferRequestType["form"] -) => { - return await fetchRPC( - client.users.$post({ - form, - }) - ); -}; - -export const updateUser = async ( - form: InferRequestType<(typeof client.users)[":id"]["$patch"]>["form"] & { - id: string; + + if (!response.ok) { + throw new Error("Failed to create assessment request"); } -) => { - return await fetchRPC( - client.users[":id"].$patch({ - param: { - id: form.id, - }, - form, - }) - ); -}; - -export const deleteUser = async (id: string) => { - return await fetchRPC( - client.users[":id"].$delete({ - param: { id }, - form: {}, - }) - ); -}; + + return await response.json(); + }; \ No newline at end of file diff --git a/apps/frontend/src/routeTree.gen.ts b/apps/frontend/src/routeTree.gen.ts index 8619974..c0c2314 100644 --- a/apps/frontend/src/routeTree.gen.ts +++ b/apps/frontend/src/routeTree.gen.ts @@ -17,12 +17,17 @@ import { Route as DashboardLayoutImport } from './routes/_dashboardLayout' import { Route as DashboardLayoutUsersIndexImport } from './routes/_dashboardLayout/users/index' import { Route as DashboardLayoutTimetableIndexImport } from './routes/_dashboardLayout/timetable/index' import { Route as DashboardLayoutDashboardIndexImport } from './routes/_dashboardLayout/dashboard/index' +import { Route as DashboardLayoutAssessmentRequestIndexImport } from './routes/_dashboardLayout/assessmentRequest/index' // Create Virtual Routes const IndexLazyImport = createFileRoute('/')() const LogoutIndexLazyImport = createFileRoute('/logout/')() const LoginIndexLazyImport = createFileRoute('/login/')() +const ForgotPasswordIndexLazyImport = createFileRoute('/forgot-password/')() +const ForgotPasswordVerifyLazyImport = createFileRoute( + '/forgot-password/verify', +)() // Create/Update Routes @@ -46,6 +51,20 @@ const LoginIndexLazyRoute = LoginIndexLazyImport.update({ getParentRoute: () => rootRoute, } as any).lazy(() => import('./routes/login/index.lazy').then((d) => d.Route)) +const ForgotPasswordIndexLazyRoute = ForgotPasswordIndexLazyImport.update({ + path: '/forgot-password/', + getParentRoute: () => rootRoute, +} as any).lazy(() => + import('./routes/forgot-password/index.lazy').then((d) => d.Route), +) + +const ForgotPasswordVerifyLazyRoute = ForgotPasswordVerifyLazyImport.update({ + path: '/forgot-password/verify', + getParentRoute: () => rootRoute, +} as any).lazy(() => + import('./routes/forgot-password/verify.lazy').then((d) => d.Route), +) + const DashboardLayoutUsersIndexRoute = DashboardLayoutUsersIndexImport.update({ path: '/users/', getParentRoute: () => DashboardLayoutRoute, @@ -65,6 +84,16 @@ const DashboardLayoutDashboardIndexRoute = getParentRoute: () => DashboardLayoutRoute, } as any) +const DashboardLayoutAssessmentRequestIndexRoute = + DashboardLayoutAssessmentRequestIndexImport.update({ + path: '/assessmentRequest/', + getParentRoute: () => DashboardLayoutRoute, + } as any).lazy(() => + import('./routes/_dashboardLayout/assessmentRequest/index.lazy').then( + (d) => d.Route, + ), + ) + // Populate the FileRoutesByPath interface declare module '@tanstack/react-router' { @@ -83,6 +112,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DashboardLayoutImport parentRoute: typeof rootRoute } + '/forgot-password/verify': { + id: '/forgot-password/verify' + path: '/forgot-password/verify' + fullPath: '/forgot-password/verify' + preLoaderRoute: typeof ForgotPasswordVerifyLazyImport + parentRoute: typeof rootRoute + } + '/forgot-password/': { + id: '/forgot-password/' + path: '/forgot-password' + fullPath: '/forgot-password' + preLoaderRoute: typeof ForgotPasswordIndexLazyImport + parentRoute: typeof rootRoute + } '/login/': { id: '/login/' path: '/login' @@ -97,6 +140,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LogoutIndexLazyImport parentRoute: typeof rootRoute } + '/_dashboardLayout/assessmentRequest/': { + id: '/_dashboardLayout/assessmentRequest/' + path: '/assessmentRequest' + fullPath: '/assessmentRequest' + preLoaderRoute: typeof DashboardLayoutAssessmentRequestIndexImport + parentRoute: typeof DashboardLayoutImport + } '/_dashboardLayout/dashboard/': { id: '/_dashboardLayout/dashboard/' path: '/dashboard' @@ -126,10 +176,13 @@ declare module '@tanstack/react-router' { export const routeTree = rootRoute.addChildren({ IndexLazyRoute, DashboardLayoutRoute: DashboardLayoutRoute.addChildren({ + DashboardLayoutAssessmentRequestIndexRoute, DashboardLayoutDashboardIndexRoute, DashboardLayoutTimetableIndexRoute, DashboardLayoutUsersIndexRoute, }), + ForgotPasswordVerifyLazyRoute, + ForgotPasswordIndexLazyRoute, LoginIndexLazyRoute, LogoutIndexLazyRoute, }) @@ -144,6 +197,8 @@ export const routeTree = rootRoute.addChildren({ "children": [ "/", "/_dashboardLayout", + "/forgot-password/verify", + "/forgot-password/", "/login/", "/logout/" ] @@ -154,17 +209,28 @@ export const routeTree = rootRoute.addChildren({ "/_dashboardLayout": { "filePath": "_dashboardLayout.tsx", "children": [ + "/_dashboardLayout/assessmentRequest/", "/_dashboardLayout/dashboard/", "/_dashboardLayout/timetable/", "/_dashboardLayout/users/" ] }, + "/forgot-password/verify": { + "filePath": "forgot-password/verify.lazy.tsx" + }, + "/forgot-password/": { + "filePath": "forgot-password/index.lazy.tsx" + }, "/login/": { "filePath": "login/index.lazy.tsx" }, "/logout/": { "filePath": "logout/index.lazy.tsx" }, + "/_dashboardLayout/assessmentRequest/": { + "filePath": "_dashboardLayout/assessmentRequest/index.tsx", + "parent": "/_dashboardLayout" + }, "/_dashboardLayout/dashboard/": { "filePath": "_dashboardLayout/dashboard/index.tsx", "parent": "/_dashboardLayout" diff --git a/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx b/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx index 08db5ed..51b9dc7 100644 --- a/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx +++ b/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx @@ -1,11 +1,10 @@ import { assessmentRequestQueryOptions } from "@/modules/assessmentRequestManagement/queries/assessmentRequestQueries"; import PageTemplate from "@/components/PageTemplate"; import { createLazyFileRoute } from "@tanstack/react-router"; -import UserFormModal from "@/modules/assessmentRequestManagement/modals/UserFormModal"; +import FormModal from "@/modules/assessmentRequestManagement/modals/FormModal"; import ExtractQueryDataType from "@/types/ExtractQueryDataType"; import { createColumnHelper } from "@tanstack/react-table"; import { Badge, Flex } from "@mantine/core"; -import UserDeleteModal from "@/modules/assessmentRequestManagement/modals/UserDeleteModal"; import { Button } from "@/shadcn/components/ui/button"; export const Route = createLazyFileRoute("/_dashboardLayout/assessmentRequest/")({ @@ -19,9 +18,9 @@ const columnHelper = createColumnHelper(); export default function UsersPage() { return ( , ]} + modals={[]} columnDefs={[ columnHelper.display({ header: "No", diff --git a/apps/frontend/vite.config.ts b/apps/frontend/vite.config.ts index 2d6bad5..6c9655b 100644 --- a/apps/frontend/vite.config.ts +++ b/apps/frontend/vite.config.ts @@ -8,7 +8,7 @@ export default defineConfig({ plugins: [react(), TanStackRouterVite()], resolve: { alias: { - "@": path.resolve(__dirname,"/src"), + "@": path.resolve(__dirname,"src"), }, }, }); From 10df38e7a72e0393b331744b2351a590d2f105c4 Mon Sep 17 00:00:00 2001 From: percyfikri Date: Fri, 4 Oct 2024 10:59:22 +0700 Subject: [PATCH 05/12] Update : Create Assessment Request --- .../modals/FormModal.tsx | 38 ++++++++++--------- .../queries/assessmentRequestQueries.ts | 4 +- .../src/shadcn/components/ui/button.tsx | 13 ++++++- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/apps/frontend/src/modules/assessmentRequestManagement/modals/FormModal.tsx b/apps/frontend/src/modules/assessmentRequestManagement/modals/FormModal.tsx index 9a896d0..bb2442e 100644 --- a/apps/frontend/src/modules/assessmentRequestManagement/modals/FormModal.tsx +++ b/apps/frontend/src/modules/assessmentRequestManagement/modals/FormModal.tsx @@ -1,7 +1,7 @@ import stringToColorHex from "@/utils/stringToColorHex"; import { Avatar, - Button, + // Button, Center, Flex, Modal, @@ -9,7 +9,7 @@ import { Stack, Text } from "@mantine/core"; -// import { Button } from "@/shadcn/components/ui/button"; +import { Button } from "@/shadcn/components/ui/button"; import { useForm } from "@mantine/form"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { getRouteApi } from "@tanstack/react-router"; @@ -34,14 +34,9 @@ export default function UserFormModal() { const searchParams = routeApi.useSearch(); - const dataId = searchParams.detail || searchParams.edit; + const isModalOpen = Boolean(searchParams.create); - const isModalOpen = Boolean(dataId || searchParams.create); - - const detailId = searchParams.detail; - const editId = searchParams.edit; - - const formType = detailId ? "detail" : editId ? "edit" : "create"; + const formType = "create"; /** * CHANGE FOLLOWING: @@ -77,7 +72,6 @@ export default function UserFormModal() { }); form.setErrors({}); - // eslint-disable-next-line react-hooks/exhaustive-deps }, [userQuery.data]); const mutation = useMutation({ @@ -88,6 +82,18 @@ export default function UserFormModal() { return await createAssessmentRequest(options.data); } }, + onSuccess: () => { + // Ini akan memaksa react-query untuk mengambil data terbaru + queryClient.invalidateQueries({ queryKey: ["assessmentRequest"] }); + + notifications.show({ + message: "Permohonan Asesmen berhasil dibuat!", + color: "green", + }); + + // Menutup modal + navigate({ search: {} }); + }, onError: (error: unknown) => { console.log(error); @@ -106,7 +112,6 @@ export default function UserFormModal() { }); const handleSubmit = async (values: typeof form.values) => { - if (formType === "detail") return; if (formType === "create") { try { @@ -116,10 +121,6 @@ export default function UserFormModal() { respondentsId: values.respondentsId, }, }); - notifications.show({ - message: "Permohonan Asesmen berhasil dibuat!", - color: "green", - }); } catch (error) { console.error(error); } @@ -141,9 +142,10 @@ export default function UserFormModal() { Apakah anda yakin ingin membuat Permohonan Asesmen Baru? + {/* Fields to display data will be sent */} {createInputComponents({ disableAll: mutation.isPending, - readonlyAll: formType === "detail", + readonlyAll: formType === "create", inputs: [ { type: "text", @@ -172,15 +174,15 @@ export default function UserFormModal() { diff --git a/apps/frontend/src/modules/assessmentRequestManagement/queries/assessmentRequestQueries.ts b/apps/frontend/src/modules/assessmentRequestManagement/queries/assessmentRequestQueries.ts index 95f6923..1be2a03 100644 --- a/apps/frontend/src/modules/assessmentRequestManagement/queries/assessmentRequestQueries.ts +++ b/apps/frontend/src/modules/assessmentRequestManagement/queries/assessmentRequestQueries.ts @@ -4,7 +4,7 @@ import { queryOptions } from "@tanstack/react-query"; export const assessmentRequestQueryOptions = (page: number, limit: number, q?: string) => queryOptions({ - queryKey: ["assesmentRequest", { page, limit, q }], + queryKey: ["assessmentRequest", { page, limit, q }], queryFn: () => fetchRPC( client.assessmentRequest.$get({ @@ -19,7 +19,7 @@ export const assessmentRequestQueryOptions = (page: number, limit: number, q?: s export const createAssessmentRequest = async ({ respondentsId }: { respondentsId: string }) => { const response = await client.assessmentRequest.$post({ - json: { respondentId: respondentsId }, // pastikan key-nya sesuai dengan backend API Anda + json: { respondentId: respondentsId }, }); if (!response.ok) { diff --git a/apps/frontend/src/shadcn/components/ui/button.tsx b/apps/frontend/src/shadcn/components/ui/button.tsx index 0ba4277..33ba732 100644 --- a/apps/frontend/src/shadcn/components/ui/button.tsx +++ b/apps/frontend/src/shadcn/components/ui/button.tsx @@ -1,6 +1,7 @@ import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" +import { TbLoader2 } from "react-icons/tb" import { cn } from "@/lib/utils" @@ -37,17 +38,25 @@ export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { asChild?: boolean + isLoading?: boolean } const Button = React.forwardRef( - ({ className, variant, size, asChild = false, ...props }, ref) => { + ({ className, variant, size, asChild = false, isLoading, children, ...props }, ref) => { const Comp = asChild ? Slot : "button" return ( + > + {isLoading ? ( + // Tampilkan spinner saat loading + ) : ( + children + )} + ) } ) From 0fccc638fe148eabe3d1e129af2ea3aad7ef147c Mon Sep 17 00:00:00 2001 From: percyfikri Date: Fri, 4 Oct 2024 15:01:44 +0700 Subject: [PATCH 06/12] update : button action with alert --- .../assessmentRequest/index.lazy.tsx | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx b/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx index 51b9dc7..83a6a49 100644 --- a/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx +++ b/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx @@ -16,6 +16,17 @@ type DataType = ExtractQueryDataType; const columnHelper = createColumnHelper(); export default function UsersPage() { + + const handleStartAssessment = (assessmentId: string) => { + const userConfirmed = window.confirm("Apakah anda yakin untuk memulai asesmen?"); + + if (userConfirmed) { + // Redirect ke URL baru di tab baru + const assessmentUrl = `/assessment?id=${assessmentId}`; + window.open(assessmentUrl, "_blank"); + } + }; + return ( { const status = props.row.original.status; + const assessmentId = props.row.original.id; + return ( {status === "selesai" ? ( - + <> + + + ) : status === "diterima" ? ( - + <> + + + ) : status === "menunggu konfirmasi" || status === "ditolak" ? ( - + <> + + + ) : null} ); From 8ff729a7abe6319c446da11dd90e277293a46ee1 Mon Sep 17 00:00:00 2001 From: percyfikri Date: Mon, 7 Oct 2024 10:58:38 +0700 Subject: [PATCH 07/12] Update : using component Shadcn, create Modal for actions with redirect --- .../modals/ConfirmModal.tsx | 34 +++ ...l.tsx => CreateAssessmentRequestModal.tsx} | 25 +- .../assessmentRequest/index.lazy.tsx | 215 +++++++++++------- .../src/shadcn/components/ui/badge.tsx | 6 + .../src/shadcn/components/ui/button.tsx | 2 +- 5 files changed, 177 insertions(+), 105 deletions(-) create mode 100644 apps/frontend/src/modules/assessmentRequestManagement/modals/ConfirmModal.tsx rename apps/frontend/src/modules/assessmentRequestManagement/modals/{FormModal.tsx => CreateAssessmentRequestModal.tsx} (91%) diff --git a/apps/frontend/src/modules/assessmentRequestManagement/modals/ConfirmModal.tsx b/apps/frontend/src/modules/assessmentRequestManagement/modals/ConfirmModal.tsx new file mode 100644 index 0000000..3ef2248 --- /dev/null +++ b/apps/frontend/src/modules/assessmentRequestManagement/modals/ConfirmModal.tsx @@ -0,0 +1,34 @@ +import { Modal, Text, Flex } from "@mantine/core"; +import { Button } from "@/shadcn/components/ui/button"; + +interface StartAssessmentModalProps { + assessmentId: string; + isOpen: boolean; + onClose: () => void; + onConfirm: (assessmentId: string) => void; +} + +export default function StartAssessmentModal({ + assessmentId, + isOpen, + onClose, + onConfirm, +}: StartAssessmentModalProps) { + return ( + + Apakah Anda yakin ingin memulai asesmen ini? + + + + + + ); +} diff --git a/apps/frontend/src/modules/assessmentRequestManagement/modals/FormModal.tsx b/apps/frontend/src/modules/assessmentRequestManagement/modals/CreateAssessmentRequestModal.tsx similarity index 91% rename from apps/frontend/src/modules/assessmentRequestManagement/modals/FormModal.tsx rename to apps/frontend/src/modules/assessmentRequestManagement/modals/CreateAssessmentRequestModal.tsx index bb2442e..2f50a42 100644 --- a/apps/frontend/src/modules/assessmentRequestManagement/modals/FormModal.tsx +++ b/apps/frontend/src/modules/assessmentRequestManagement/modals/CreateAssessmentRequestModal.tsx @@ -25,9 +25,7 @@ import { assessmentRequestQueryOptions, createAssessmentRequest } from "../queri const routeApi = getRouteApi("/_dashboardLayout/assessmentRequest/"); export default function UserFormModal() { - /** - * DON'T CHANGE FOLLOWING: - */ + const queryClient = useQueryClient(); const navigate = routeApi.useNavigate(); @@ -38,16 +36,9 @@ export default function UserFormModal() { const formType = "create"; - /** - * CHANGE FOLLOWING: - */ - const userQuery = useQuery(assessmentRequestQueryOptions(0, 10)); - const modalTitle = - - Konfirmasi - + const modalTitle = Konfirmasi const form = useForm({ initialValues: { @@ -82,8 +73,9 @@ export default function UserFormModal() { return await createAssessmentRequest(options.data); } }, + // auto refresh after mutation onSuccess: () => { - // Ini akan memaksa react-query untuk mengambil data terbaru + // force a query-reaction to retrieve the latest data queryClient.invalidateQueries({ queryKey: ["assessmentRequest"] }); notifications.show({ @@ -91,7 +83,7 @@ export default function UserFormModal() { color: "green", }); - // Menutup modal + // close modal navigate({ search: {} }); }, onError: (error: unknown) => { @@ -111,6 +103,7 @@ export default function UserFormModal() { }, }); + // Handle submit form, mutate data to server and close modal after success const handleSubmit = async (values: typeof form.values) => { if (formType === "create") { @@ -142,7 +135,7 @@ export default function UserFormModal() { Apakah anda yakin ingin membuat Permohonan Asesmen Baru? - {/* Fields to display data will be sent */} + {/* Fields to display data will be sent but only respondentId */} {createInputComponents({ disableAll: mutation.isPending, readonlyAll: formType === "create", @@ -171,7 +164,7 @@ export default function UserFormModal() { })} {/* Buttons */} - + diff --git a/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx b/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx index 83a6a49..43141a1 100644 --- a/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx +++ b/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx @@ -1,11 +1,14 @@ +import { useState } from "react"; import { assessmentRequestQueryOptions } from "@/modules/assessmentRequestManagement/queries/assessmentRequestQueries"; import PageTemplate from "@/components/PageTemplate"; import { createLazyFileRoute } from "@tanstack/react-router"; -import FormModal from "@/modules/assessmentRequestManagement/modals/FormModal"; +import FormModal from "@/modules/assessmentRequestManagement/modals/CreateAssessmentRequestModal"; import ExtractQueryDataType from "@/types/ExtractQueryDataType"; import { createColumnHelper } from "@tanstack/react-table"; -import { Badge, Flex } from "@mantine/core"; +import { Flex } from "@mantine/core"; +import { Badge } from "@/shadcn/components/ui/badge"; import { Button } from "@/shadcn/components/ui/button"; +import StartAssessmentModal from "@/modules/assessmentRequestManagement/modals/ConfirmModal"; export const Route = createLazyFileRoute("/_dashboardLayout/assessmentRequest/")({ component: UsersPage, @@ -16,101 +19,137 @@ type DataType = ExtractQueryDataType; const columnHelper = createColumnHelper(); export default function UsersPage() { - - const handleStartAssessment = (assessmentId: string) => { - const userConfirmed = window.confirm("Apakah anda yakin untuk memulai asesmen?"); + const [modalOpen, setModalOpen] = useState(false); + const [selectedAssessmentId, setSelectedAssessmentId] = useState(null); + + /** + * Fungsi untuk membuka modal konfirmasi mulai asesmen + * @param {string} assessmentId ID asesmen yang akan di mulai + */ + const handleOpenModal = (assessmentId: string) => { + if (!assessmentId) { + console.error("Assessment ID is missing"); + return; + } - if (userConfirmed) { - // Redirect ke URL baru di tab baru - const assessmentUrl = `/assessment?id=${assessmentId}`; - window.open(assessmentUrl, "_blank"); - } + setSelectedAssessmentId(assessmentId); + setModalOpen(true); }; - + + /** + * Fungsi untuk membuka halaman asesmen di tab baru + * @param {string} assessmentId ID asesmen yang akan di buka + */ + const handleStartAssessment = (assessmentId: string) => { + // Redirect ke URL baru di tab baru + const assessmentUrl = `/assessment?id=${assessmentId}`; + window.open(assessmentUrl, "_blank"); + setModalOpen(false); + }; + + /** + * Fungsi untuk membuka halaman hasil asesmen berdasarkan ID yang valid + * Digunakan ketika tombol "Lihat Hasil" diklik + * @param {string} assessmentId ID asesmen yang akan di buka + */ + const handleViewResult = (assessmentId: string) => { + // Make sure assessmentId is valid and not null + if (!assessmentId) { + console.error("Assessment ID is missing"); + return; + } + const resultUrl = `/assessmentResult/${assessmentId}`; + window.location.href = resultUrl; + }; + + return ( - ]} - columnDefs={[ - columnHelper.display({ - header: "No", - cell: (props) => props.row.index + 1, - }), - columnHelper.display({ - header: "Tanggal", - cell: (props) => - props.row.original.createdAt - ? new Intl.DateTimeFormat("ID", { - year: "numeric", - month: "long", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - hour12: true, - }).format(new Date(props.row.original.createdAt)) - : 'N/A', - }), + <> + ]} + columnDefs={[ + columnHelper.display({ + header: "No", + cell: (props) => props.row.index + 1, + }), + columnHelper.display({ + header: "Tanggal", + cell: (props) => + props.row.original.createdAt + ? new Intl.DateTimeFormat("ID", { + year: "numeric", + month: "long", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + hour12: true, + }).format(new Date(props.row.original.createdAt)) + : 'N/A', + }), + columnHelper.display({ + header: "Status", + cell: (props) => { + const status = props.row.original.status; + switch (status) { + case "menunggu konfirmasi": + // return Menunggu Konfirmasi; + return Menunggu Konfirmasi; + case "diterima": + return Diterima; + case "ditolak": + return Ditolak; + case "selesai": + return Selesai; + default: + return Tidak diketahui; + } + }, + }), + columnHelper.display({ + header: "Actions", + cell: (props) => { + const status = props.row.original.status; + const assessmentId = props.row.original.id; // Retrieve the assessmentId from the data row - columnHelper.display({ - header: "Status", - cell: (props) => { - const status = props.row.original.status; - switch (status) { - case "menunggu konfirmasi": - return Menunggu Konfirmasi; - case "diterima": - return Diterima; - case "ditolak": - return Ditolak; - case "selesai": - return Selesai; - default: - return Tidak diketahui; - } - }, - }), - - columnHelper.display({ - header: "Aksi", - cell: (props) => { - const status = props.row.original.status; - const assessmentId = props.row.original.id; - - - return ( - - {status === "selesai" ? ( - <> + return ( + + {/* Button Create Assessment */} + {status === "selesai" ? ( - - - ) : status === "diterima" ? ( - <> + ) : status === "diterima" ? ( - - - ) : status === "menunggu konfirmasi" || status === "ditolak" ? ( - <> + ) : ( + )} + + {/* Button View Result */} + {status === "selesai" ? ( + + ) : ( - - ) : null} - - ); - }, - }), - ]} - /> + )} + + ); + }, + }), + ]} + /> + + {/* Modal Konfirmasi Start Asessment */} + {selectedAssessmentId && ( + setModalOpen(false)} + onConfirm={handleStartAssessment} + /> + )} + ); -} \ No newline at end of file +} diff --git a/apps/frontend/src/shadcn/components/ui/badge.tsx b/apps/frontend/src/shadcn/components/ui/badge.tsx index f000e3e..d6abc25 100644 --- a/apps/frontend/src/shadcn/components/ui/badge.tsx +++ b/apps/frontend/src/shadcn/components/ui/badge.tsx @@ -15,6 +15,12 @@ const badgeVariants = cva( destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", outline: "text-foreground", + + // Custom variants for status + waiting: "border-transparent bg-yellow-500 text-white hover:bg-yellow-600", + accepted: "border-transparent bg-green-500 text-white hover:bg-green-600", + rejected: "border-transparent bg-red-500 text-white hover:bg-red-600", + completed: "border-transparent bg-blue-500 text-white hover:bg-blue-600", }, }, defaultVariants: { diff --git a/apps/frontend/src/shadcn/components/ui/button.tsx b/apps/frontend/src/shadcn/components/ui/button.tsx index 33ba732..d7a6de3 100644 --- a/apps/frontend/src/shadcn/components/ui/button.tsx +++ b/apps/frontend/src/shadcn/components/ui/button.tsx @@ -52,7 +52,7 @@ const Button = React.forwardRef( {...props} > {isLoading ? ( - // Tampilkan spinner saat loading + // Show spinner when loading ) : ( children )} From d1a9f9c1ffc9089a1a0310b384dfc754a2cb59cc Mon Sep 17 00:00:00 2001 From: percyfikri Date: Tue, 8 Oct 2024 10:07:21 +0700 Subject: [PATCH 08/12] Update : add comment --- .../src/routes/assessmentRequest/route.ts | 19 ++++++-------- .../modals/ConfirmModal.tsx | 2 +- .../modals/CreateAssessmentRequestModal.tsx | 22 ++++------------ .../assessmentRequest/index.lazy.tsx | 25 +++++++++---------- .../src/shadcn/components/ui/button.tsx | 2 +- 5 files changed, 26 insertions(+), 44 deletions(-) diff --git a/apps/backend/src/routes/assessmentRequest/route.ts b/apps/backend/src/routes/assessmentRequest/route.ts index efc8cdf..a048429 100644 --- a/apps/backend/src/routes/assessmentRequest/route.ts +++ b/apps/backend/src/routes/assessmentRequest/route.ts @@ -25,14 +25,14 @@ const assessmentRequestRoute = new Hono() requestValidator( "query", z.object({ - page: z.coerce.number().int().min(0).default(0), // Menambahkan pagination page - limit: z.coerce.number().int().min(1).max(1000).default(10), // Menambahkan pagination limit - q: z.string().optional(), // Kata kunci pencarian (search) + page: z.coerce.number().int().min(0).default(0), + limit: z.coerce.number().int().min(1).max(1000).default(10), + q: z.string().optional(), }) ), async (c) => { const currentUser = c.get("currentUser"); - const userId = currentUser?.id; // Mengambil userId dari currentUser yang disimpan di context + const userId = currentUser?.id; // Get user ID of the currently logged in currentUser if (!userId) { throw forbidden({ @@ -41,7 +41,7 @@ const assessmentRequestRoute = new Hono() } const { page, limit, q } = c.req.valid("query"); - // Query untuk menghitung total data + // Query to count total data const totalCountQuery = db .select({ count: sql`count(distinct ${assessments.id})`, @@ -59,19 +59,15 @@ const assessmentRequestRoute = new Hono() const totalCountResult = await totalCountQuery; const totalItems = totalCountResult[0]?.count || 0; - // Query untuk mendapatkan data assessment dengan pagination + // Query to get assessment data with pagination const queryResult = await db .select({ userId: users.id, - createdAt: assessments.createdAt, name: users.name, - code: rolesSchema.code, - id: assessments.id, + assessmentId: assessments.id, tanggal: assessments.createdAt, status: assessments.status, respondentId: respondents.id, - email: users.email, - username: users.username, }) .from(users) .leftJoin(rolesToUsers, eq(users.id, rolesToUsers.userId)) @@ -91,7 +87,6 @@ const assessmentRequestRoute = new Hono() if (!queryResult[0]) throw notFound(); - // Mengembalikan data dengan metadata pagination return c.json({ data: queryResult, _metadata: { diff --git a/apps/frontend/src/modules/assessmentRequestManagement/modals/ConfirmModal.tsx b/apps/frontend/src/modules/assessmentRequestManagement/modals/ConfirmModal.tsx index 3ef2248..a05220b 100644 --- a/apps/frontend/src/modules/assessmentRequestManagement/modals/ConfirmModal.tsx +++ b/apps/frontend/src/modules/assessmentRequestManagement/modals/ConfirmModal.tsx @@ -23,7 +23,7 @@ export default function StartAssessmentModal({ @@ -133,7 +133,7 @@ export default function UsersPage() { ) : ( )} - + ); }, }), From a54d3ca7977a5760aecb1d859ea095d039062935 Mon Sep 17 00:00:00 2001 From: percyfikri Date: Tue, 8 Oct 2024 11:56:06 +0700 Subject: [PATCH 11/12] Delete : import Flex from mantine --- .../src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx b/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx index 940b716..4d8ecbb 100644 --- a/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx +++ b/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx @@ -5,7 +5,6 @@ import { createLazyFileRoute } from "@tanstack/react-router"; import FormModal from "@/modules/assessmentRequestManagement/modals/CreateAssessmentRequestModal"; import ExtractQueryDataType from "@/types/ExtractQueryDataType"; import { createColumnHelper } from "@tanstack/react-table"; -import { Flex } from "@mantine/core"; import { Badge } from "@/shadcn/components/ui/badge"; import { Button } from "@/shadcn/components/ui/button"; import StartAssessmentModal from "@/modules/assessmentRequestManagement/modals/ConfirmModal"; From 0f8be788dfa0cf2baf291f8f050d9259ac929bfe Mon Sep 17 00:00:00 2001 From: percyfikri Date: Wed, 9 Oct 2024 09:23:16 +0700 Subject: [PATCH 12/12] Update : variants of action button --- .../_dashboardLayout/assessmentRequest/index.lazy.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx b/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx index 4d8ecbb..1a3503b 100644 --- a/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx +++ b/apps/frontend/src/routes/_dashboardLayout/assessmentRequest/index.lazy.tsx @@ -115,7 +115,7 @@ export default function UsersPage() {
{/* Button Create Assessment */} {status === "selesai" ? ( - + ) : status === "diterima" ? ( ) : ( - + )} {/* Button View Result */} {status === "selesai" ? ( - + ) : ( - + )}
);