Create : Integrasi FE & BE Fix GetAll By Id
This commit is contained in:
parent
282b4dfae3
commit
f7ea4ed632
|
|
@ -21,6 +21,13 @@ const sidebarMenus: SidebarMenu[] = [
|
||||||
link: "/questions",
|
link: "/questions",
|
||||||
color: "green",
|
color: "green",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Permohonan Asesmen",
|
||||||
|
icon: { tb: "TbChecklist" },
|
||||||
|
allowedPermissions: ["permissions.read"],
|
||||||
|
link: "/assessmentRequest",
|
||||||
|
color: "green",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default sidebarMenus;
|
export default sidebarMenus;
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ const authInfo = createMiddleware<HonoEnv>(async (c, next) => {
|
||||||
|
|
||||||
// Setting the currentUser with fetched data
|
// Setting the currentUser with fetched data
|
||||||
c.set("currentUser", {
|
c.set("currentUser", {
|
||||||
|
id: user[0].users.id, // Adding user ID here
|
||||||
name: user[0].users.name, // Assuming the first result is the user
|
name: user[0].users.name, // Assuming the first result is the user
|
||||||
permissions: Array.from(permissions),
|
permissions: Array.from(permissions),
|
||||||
roles: Array.from(roles),
|
roles: Array.from(roles),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { eq } from "drizzle-orm";
|
import { eq, sql, ilike, isNull, and } from "drizzle-orm";
|
||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import db from "../../drizzle";
|
import db from "../../drizzle";
|
||||||
|
|
@ -20,23 +20,53 @@ const assessmentRequestRoute = new Hono<HonoEnv>()
|
||||||
|
|
||||||
// Get assessment request by user ID
|
// Get assessment request by user ID
|
||||||
.get(
|
.get(
|
||||||
"/:id",
|
"/",
|
||||||
checkPermission("assessmentRequest.read"),
|
checkPermission("assessmentRequest.read"),
|
||||||
requestValidator(
|
requestValidator(
|
||||||
"query",
|
"query",
|
||||||
z.object({
|
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) => {
|
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<number>`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
|
const queryResult = await db
|
||||||
.select({
|
.select({
|
||||||
userId: users.id,
|
userId: users.id,
|
||||||
createdAt: assessments.createdAt,
|
createdAt: assessments.createdAt,
|
||||||
name: users.name,
|
name: users.name,
|
||||||
code: rolesSchema.code,
|
code: rolesSchema.code,
|
||||||
|
id: assessments.id,
|
||||||
|
tanggal: assessments.createdAt,
|
||||||
status: assessments.status,
|
status: assessments.status,
|
||||||
})
|
})
|
||||||
.from(users)
|
.from(users)
|
||||||
|
|
@ -44,21 +74,34 @@ const assessmentRequestRoute = new Hono<HonoEnv>()
|
||||||
.leftJoin(rolesSchema, eq(rolesToUsers.roleId, rolesSchema.id))
|
.leftJoin(rolesSchema, eq(rolesToUsers.roleId, rolesSchema.id))
|
||||||
.leftJoin(respondents, eq(users.id, respondents.userId))
|
.leftJoin(respondents, eq(users.id, respondents.userId))
|
||||||
.leftJoin(assessments, eq(respondents.id, assessments.respondentId))
|
.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();
|
if (!queryResult[0]) throw notFound();
|
||||||
|
|
||||||
const assessmentRequestData = {
|
// Mengembalikan data dengan metadata pagination
|
||||||
...queryResult,
|
return c.json({
|
||||||
};
|
data: queryResult,
|
||||||
|
_metadata: {
|
||||||
return c.json(assessmentRequestData);
|
currentPage: page,
|
||||||
|
totalPages: Math.ceil(totalItems / limit),
|
||||||
|
totalItems,
|
||||||
|
perPage: limit,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
// Post assessment request by user ID
|
// Post assessment request by user ID
|
||||||
.post(
|
.post(
|
||||||
"/:id",
|
"/",
|
||||||
checkPermission("assessmentRequest.create"),
|
checkPermission("assessmentRequest.create"),
|
||||||
requestValidator(
|
requestValidator(
|
||||||
"json",
|
"json",
|
||||||
|
|
@ -68,11 +111,12 @@ const assessmentRequestRoute = new Hono<HonoEnv>()
|
||||||
),
|
),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const { respondentId } = c.req.valid("json");
|
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
|
// Make sure the userId exists
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
throw new HTTPException(400, { message: "User ID is required." });
|
throw new HTTPException(400, { message: "User not authenticated" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate if respondent exists
|
// Validate if respondent exists
|
||||||
|
|
@ -101,5 +145,5 @@ const assessmentRequestRoute = new Hono<HonoEnv>()
|
||||||
return c.json({ message: "Successfully submitted the assessment request" }, 201);
|
return c.json({ message: "Successfully submitted the assessment request" }, 201);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export default assessmentRequestRoute;
|
export default assessmentRequestRoute;
|
||||||
1
apps/backend/src/types/HonoEnv.d.ts
vendored
1
apps/backend/src/types/HonoEnv.d.ts
vendored
|
|
@ -5,6 +5,7 @@ type HonoEnv = {
|
||||||
Variables: {
|
Variables: {
|
||||||
uid?: string;
|
uid?: string;
|
||||||
currentUser?: {
|
currentUser?: {
|
||||||
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
permissions: SpecificPermissionCode[];
|
permissions: SpecificPermissionCode[];
|
||||||
roles: RoleCode[];
|
roles: RoleCode[];
|
||||||
|
|
|
||||||
|
|
@ -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<typeof client.users.$post>["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: {},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -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<typeof assessmentRequestQueryOptions>;
|
||||||
|
|
||||||
|
const columnHelper = createColumnHelper<DataType>();
|
||||||
|
|
||||||
|
export default function UsersPage() {
|
||||||
|
return (
|
||||||
|
<PageTemplate
|
||||||
|
title="Users"
|
||||||
|
queryOptions={assessmentRequestQueryOptions}
|
||||||
|
modals={[<UserFormModal />, <UserDeleteModal />]}
|
||||||
|
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 <Badge color="yellow">Menunggu Konfirmasi</Badge>;
|
||||||
|
case "diterima":
|
||||||
|
return <Badge color="green">Diterima</Badge>;
|
||||||
|
case "ditolak":
|
||||||
|
return <Badge color="red">Ditolak</Badge>;
|
||||||
|
case "selesai":
|
||||||
|
return <Badge color="blue">Selesai</Badge>;
|
||||||
|
default:
|
||||||
|
return <Badge color="gray">Tidak diketahui</Badge>;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
columnHelper.display({
|
||||||
|
header: "Actions",
|
||||||
|
cell: (props) => {
|
||||||
|
const status = props.row.original.status;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex gap="xs">
|
||||||
|
{status === "selesai" ? (
|
||||||
|
<Button onClick={() => alert('Lihat Hasil')}>Lihat Hasil</Button>
|
||||||
|
) : status === "diterima" ? (
|
||||||
|
<Button onClick={() => alert('Mulai Asesmen')}>Mulai Asesmen</Button>
|
||||||
|
) : status === "menunggu konfirmasi" || status === "ditolak" ? (
|
||||||
|
<Button disabled>
|
||||||
|
Mulai Asesmen
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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));
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user