Pull Request branch dev-clone to main #1

Merged
gitea merged 429 commits from dev-clone into main 2024-12-23 09:31:34 +00:00
7 changed files with 243 additions and 17 deletions
Showing only changes of commit f7ea4ed632 - Show all commits

View File

@ -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;

View File

@ -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),

View File

@ -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

View File

@ -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[];

View File

@ -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: {},
})
);
};

View File

@ -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>
);
},
}),
]}
/>
);
}

View File

@ -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));
},
});