AshameTheDestroyer commited on
Commit
9185393
·
1 Parent(s): fdaefd4

Displaying Applications.

Browse files
frontend/app/routes.ts CHANGED
@@ -5,6 +5,7 @@ export default [
5
  route("jobs", "routes/jobs.tsx"),
6
  route("jobs/:id", "routes/jobs.$id.tsx"),
7
  route("jobs/:jid/assessments/:id", "routes/jobs.$jid.assessments.$id.tsx"),
 
8
  route("dashboard", "routes/dashboard.tsx"),
9
  route("registration", "routes/registration.tsx"),
10
  ] satisfies RouteConfig;
 
5
  route("jobs", "routes/jobs.tsx"),
6
  route("jobs/:id", "routes/jobs.$id.tsx"),
7
  route("jobs/:jid/assessments/:id", "routes/jobs.$jid.assessments.$id.tsx"),
8
+ route("jobs/:jid/assessment/:aid/applications", "routes/jobs.$jid.assessment.$aid.applications.tsx"),
9
  route("dashboard", "routes/dashboard.tsx"),
10
  route("registration", "routes/registration.tsx"),
11
  ] satisfies RouteConfig;
frontend/app/routes/jobs.$jid.assessment.$aid.applications.tsx ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Avatar } from "radix-ui";
2
+ import { useParams } from "react-router";
3
+ import { Loader2Icon } from "lucide-react";
4
+ import { JobCard } from "~/components/job-card";
5
+ import { Paginator } from "~/components/paginator";
6
+ import { useGetJobByID } from "~/services/useGetJobsByID";
7
+ import { AssessmentCard } from "~/components/assessment-card";
8
+ import { useGetJobAssessmentByID } from "~/services/useGetJobAssessmentByID";
9
+ import type { Route } from "./+types/jobs.$jid.assessment.$aid.applications";
10
+ import { useGetJobAssessmentApplications } from "~/services/useGetJobAssessmentApplications";
11
+
12
+ export function meta({}: Route.MetaArgs) {
13
+ return [
14
+ { title: "Assessments Applications" },
15
+ {
16
+ name: "description",
17
+ content: "View applications for the selected job assessment.",
18
+ },
19
+ ];
20
+ }
21
+
22
+ export default function AssessmentDetailRoute() {
23
+ const { jid, aid } = useParams();
24
+ const { data: job, isLoading: isJobLoading, isError: isJobError, refetch: refetchJob } = useGetJobByID({ id: jid || "" });
25
+ const { data: jobAssessment, isLoading: isJobAssessmentLoading, isError: isJobAssessmentError, refetch: refetchJobAssessment } = useGetJobAssessmentByID({ jid: jid || "", id: aid || "" });
26
+ const { data: { data: applications, total } = { data: [] }, isLoading: isApplicationsLoading, isError: isApplicationsError, refetch: refetchApplications } = useGetJobAssessmentApplications({ jid: jid || "", aid: aid || "" });
27
+
28
+ const isError = isJobError || isJobAssessmentError || isApplicationsError;
29
+ const isLoading = isJobLoading || isJobAssessmentLoading || isApplicationsLoading;
30
+ const refetch = () => (refetchJob(), refetchJobAssessment(), refetchApplications());
31
+
32
+ if (isLoading) {
33
+ return (
34
+ <main className="container mx-auto p-4 flex flex-col gap-2 place-items-center">
35
+ <div className="flex flex-col gap-2 place-items-center">
36
+ <Loader2Icon className="animate-spin" />
37
+ <p>Loading Applications...</p>
38
+ </div>
39
+ </main>
40
+ );
41
+ }
42
+
43
+ if (isError) {
44
+ return (
45
+ <main className="container mx-auto p-4 flex flex-col gap-2">
46
+ <div className="bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-100 p-4 rounded flex flex-col gap-2 place-items-center">
47
+ <p className="text-center">Failed to load applications<br />Please try again</p>
48
+ <button
49
+ onClick={() => refetch()}
50
+ className="ml-4 px-3 py-1 cursor-pointer bg-red-500 text-white dark:bg-red-200 dark:text-red-700 rounded"
51
+ >
52
+ Retry
53
+ </button>
54
+ </div>
55
+ </main>
56
+ );
57
+ }
58
+
59
+ return (
60
+ <main className="container mx-auto p-4 flex flex-col gap-8">
61
+ <JobCard job={job} isStatic />
62
+ <AssessmentCard jid={jid || ""} assessment={jobAssessment} isStatic />
63
+ <section className="flex flex-col gap-4">
64
+ <h3 className="text-xl font-semibold">Assessment's Applications</h3>
65
+ <div className="grid grid-cols-[repeat(auto-fill,minmax(400px,1fr))] gap-4">
66
+ {applications.length === 0 ? (
67
+ <p>No applications found for this assessment.</p>
68
+ ) : applications.map(application => (
69
+ <div key={application.id} className="border p-4 rounded bg-indigo-50 dark:bg-gray-700 flex flex-wrap justify-evenly gap-4 place-items-center">
70
+ <div className="group-data-[collapsible=icon]:-mx-4 flex gap-2">
71
+ <Avatar.Avatar className="shrink-0 cursor-pointer" tabIndex={0}>
72
+ <Avatar.AvatarFallback className="rounded-full bg-gray-200 dark:bg-gray-800 size-10 group-data-[collapsible=icon]:size-8 flex items-center justify-center">
73
+ {application.user ? `${application.user.first_name[0]}${application.user.last_name[0]}` : "U"}
74
+ </Avatar.AvatarFallback>
75
+ </Avatar.Avatar>
76
+ <div className="overflow-hidden group-data-[collapsible=icon]:hidden">
77
+ <p className="font-bold whitespace-nowrap text-ellipsis overflow-hidden text-start">
78
+ {application.user.first_name} {application.user.last_name}
79
+ </p>
80
+ <p className="whitespace-nowrap text-ellipsis overflow-hidden">
81
+ {application.user.email}
82
+ </p>
83
+ </div>
84
+ </div>
85
+ <p>Score: {application.score}/{application.passing_score}</p>
86
+ </div>
87
+ ))}
88
+ </div>
89
+ {total && <Paginator total={total} />}
90
+ </section>
91
+ </main>
92
+ );
93
+ }
frontend/app/routes/jobs.$jid.assessments.$id.tsx CHANGED
@@ -1,17 +1,17 @@
1
  import { toast } from "react-toastify";
2
- import { useParams } from "react-router";
3
- import { Loader2Icon } from "lucide-react";
4
  import { useEffect, useState } from "react";
5
  import { Label, RadioGroup } from "radix-ui";
6
  import { Button } from "~/components/ui/button";
7
  import { Textarea } from "~/components/ui/textarea";
8
  import { Checkbox } from "~/components/ui/checkbox";
 
9
  import { RadioGroupItem } from "~/components/ui/radio-group";
10
  import { AssessmentCard } from "~/components/assessment-card";
11
  import type { Route } from "./+types/jobs.$jid.assessments.$id";
12
  import { useGetJobAssessmentByID } from "~/services/useGetJobAssessmentByID";
13
  import { usePostAssessmentApplication } from "~/services/usePostAssessmentApplication";
14
- import { useGetMyUser } from "~/services/useGetMyUser";
15
 
16
  export function meta({}: Route.MetaArgs) {
17
  return [
@@ -86,6 +86,10 @@ export default function AssessmentDetailRoute() {
86
  return (
87
  <main className="container mx-auto p-4 flex flex-col gap-8">
88
  <AssessmentCard jid={jid || ""} assessment={assessment} isStatic />
 
 
 
 
89
  <section className="flex flex-col gap-4">
90
  <h3 className="text-xl font-semibold">Assessment's Questions</h3>
91
  <div className="flex flex-col gap-4">
 
1
  import { toast } from "react-toastify";
2
+ import { Link, useParams } from "react-router";
3
+ import { ExternalLinkIcon, Loader2Icon } from "lucide-react";
4
  import { useEffect, useState } from "react";
5
  import { Label, RadioGroup } from "radix-ui";
6
  import { Button } from "~/components/ui/button";
7
  import { Textarea } from "~/components/ui/textarea";
8
  import { Checkbox } from "~/components/ui/checkbox";
9
+ import { useGetMyUser } from "~/services/useGetMyUser";
10
  import { RadioGroupItem } from "~/components/ui/radio-group";
11
  import { AssessmentCard } from "~/components/assessment-card";
12
  import type { Route } from "./+types/jobs.$jid.assessments.$id";
13
  import { useGetJobAssessmentByID } from "~/services/useGetJobAssessmentByID";
14
  import { usePostAssessmentApplication } from "~/services/usePostAssessmentApplication";
 
15
 
16
  export function meta({}: Route.MetaArgs) {
17
  return [
 
86
  return (
87
  <main className="container mx-auto p-4 flex flex-col gap-8">
88
  <AssessmentCard jid={jid || ""} assessment={assessment} isStatic />
89
+ <Link to={`/jobs/${jid}/assessment/${id}/applications`} className="text-indigo-600 hover:underline">
90
+ View Applications for this Assessment
91
+ <ExternalLinkIcon className="inline -translate-y-1 mx-2" />
92
+ </Link>
93
  <section className="flex flex-col gap-4">
94
  <h3 className="text-xl font-semibold">Assessment's Questions</h3>
95
  <div className="flex flex-col gap-4">
frontend/app/services/useGetJobAssessmentApplications.ts ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { User } from "./useGetMyUser";
2
+ import { useQuery } from "@tanstack/react-query";
3
+ import { HTTPManager } from "~/managers/HTTPManager";
4
+ import type { Pagination } from "~/types/pagination";
5
+ import { usePagination } from "~/hooks/use-pagination";
6
+
7
+ export const GET_JOB_ASSESSMENT_APPLICATION_KEY = "job-assessments-application";
8
+
9
+ export type Application = {
10
+ id: string;
11
+ user: User;
12
+ score: number;
13
+ passing_score: number;
14
+ };
15
+
16
+ export const useGetJobAssessmentApplications = ({ jid, aid }: { jid: string, aid: string }) => {
17
+ const { page, limit } = usePagination();
18
+ const searchParams = new URLSearchParams({ page: String(page), limit: String(limit) });
19
+ return useQuery({
20
+ queryKey: [GET_JOB_ASSESSMENT_APPLICATION_KEY, page, limit],
21
+ queryFn: async () => HTTPManager.get<Pagination<Application>>(`/applications/jobs/${jid}/assessments/${aid}?` + searchParams.toString()).then(response => response.data),
22
+ });
23
+ }
frontend/app/services/useGetMyUser.ts CHANGED
@@ -4,11 +4,11 @@ import { HTTPManager } from "~/managers/HTTPManager";
4
  export const GET_MY_USER_KEY = "my-user";
5
 
6
  export type User = {
7
- "id": string
8
- "email": string,
9
- "last_name": string,
10
- "first_name": string,
11
- "role": "hr" | "applicant",
12
  }
13
 
14
  export const useGetMyUser = () => useQuery({
 
4
  export const GET_MY_USER_KEY = "my-user";
5
 
6
  export type User = {
7
+ "id": string;
8
+ "email": string;
9
+ "last_name": string;
10
+ "first_name": string;
11
+ "role": "hr" | "applicant";
12
  }
13
 
14
  export const useGetMyUser = () => useQuery({