Skip to content

Commit

Permalink
FEAT/Added Job Management Page (#300)
Browse files Browse the repository at this point in the history
* inited job management feature

* delete feat with TypeSafe

* addded pagination

* deactivated delete functionality

* Resolved conflicts and Updated code

* new feat job approve

* made isVerfied field optional jobType

* fixed some ui bugs

* fixed conflicts_and_bugs

* made Datatable SSR

---------

Co-authored-by: Dev <[email protected]>
  • Loading branch information
dev13-suthar and Dev authored Oct 21, 2024
1 parent facca11 commit b195ae9
Show file tree
Hide file tree
Showing 18 changed files with 597 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Job" ADD COLUMN "deleted" BOOLEAN NOT NULL DEFAULT false;
1 change: 1 addition & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ model Job {
minExperience Int?
maxExperience Int?
isVerifiedJob Boolean @default(false) @map("is_verified_job")
deleted Boolean @default(false)
postedAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
Expand Down
79 changes: 73 additions & 6 deletions src/actions/job.action.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
'use server';
import prisma from '@/config/prisma.config';
import { withServerActionAsyncCatcher } from '@/lib/async-catch';
import { withSession } from '@/lib/session';
import { ErrorHandler } from '@/lib/error';
import { SuccessResponse } from '@/lib/success';
import {
ApproveJobSchema,
ApproveJobSchemaType,
deleteJobByIdSchema,
DeleteJobByIdSchemaType,
JobByIdSchema,
JobByIdSchemaType,
JobPostSchema,
Expand All @@ -24,11 +29,22 @@ import {
getAllRecommendedJobs,
getJobType,
} from '@/types/jobs.types';
import { withAdminServerAction } from '@/lib/admin';
import { revalidatePath } from 'next/cache';

type additional = {
isVerifiedJob: boolean;
};
export const createJob = withServerActionAsyncCatcher<

type deletedJob = {
deletedJobID: string;
}; //TODO: Convert it to generic type that returns JobID Only;

type ApprovedJobID = {
jobId: string;
};

export const createJob = withSession<
JobPostSchemaType,
ServerActionReturnType<additional>
>(async (data) => {
Expand Down Expand Up @@ -92,10 +108,10 @@ export const createJob = withServerActionAsyncCatcher<
return new SuccessResponse(message, 201, additonal).serialize();
});

export const getAllJobs = withServerActionAsyncCatcher<
export const getAllJobs = withSession<
JobQuerySchemaType,
ServerActionReturnType<getAllJobsAdditonalType>
>(async (data) => {
>(async (session, data) => {
if (data?.workmode && !Array.isArray(data?.workmode)) {
data.workmode = Array.of(data?.workmode);
}
Expand All @@ -109,14 +125,19 @@ export const getAllJobs = withServerActionAsyncCatcher<
data.city = Array.of(data?.city);
}
const result = JobQuerySchema.parse(data);
const isAdmin = session.user.role === 'ADMIN';
const { filterQueries, orderBy, pagination } = getJobFilters(result);
const queryJobsPromise = prisma.job.findMany({
...pagination,
orderBy: [orderBy],
where: {
isVerifiedJob: true,
expired: false,
...filterQueries,
...(isAdmin
? { ...filterQueries }
: {
isVerifiedJob: true,
...filterQueries,
expired: false,
}),
},
select: {
id: true,
Expand All @@ -139,6 +160,8 @@ export const getAllJobs = withServerActionAsyncCatcher<
maxSalary: true,
postedAt: true,
companyLogo: true,
isVerifiedJob: true,
deleted: true,
},
});
const totalJobsPromise = prisma.job.count({
Expand Down Expand Up @@ -194,6 +217,7 @@ export const getRecommendedJobs = withServerActionAsyncCatcher<
maxSalary: true,
postedAt: true,
skills: true,
isVerifiedJob: true,
companyLogo: true,
},
});
Expand Down Expand Up @@ -225,6 +249,7 @@ export const getRecommendedJobs = withServerActionAsyncCatcher<
companyLogo: true,
minExperience: true,
maxExperience: true,
isVerifiedJob: true,
category: true,
},
});
Expand Down Expand Up @@ -272,6 +297,7 @@ export const getJobById = withServerActionAsyncCatcher<
minSalary: true,
maxSalary: true,
postedAt: true,
isVerifiedJob: true,
application: true,
},
});
Expand Down Expand Up @@ -368,6 +394,47 @@ export const updateJob = withServerActionAsyncCatcher<
).serialize();
});

export const deleteJobById = withServerActionAsyncCatcher<
DeleteJobByIdSchemaType,
ServerActionReturnType<deletedJob>
>(async (data) => {
const result = deleteJobByIdSchema.parse(data);
const { id } = result;
const deletedJob = await prisma.job.update({
where: {
id: id,
},
data: {
deleted: true,
},
});
const deletedJobID = deletedJob.id;
revalidatePath('/manage');
return new SuccessResponse('Job Deleted successfully', 200, {
deletedJobID,
}).serialize();
});

export const approveJob = withAdminServerAction<
ApproveJobSchemaType,
ServerActionReturnType<ApprovedJobID>
>(async (session, data) => {
const result = ApproveJobSchema.safeParse(data);
if (!result.success) {
throw new Error(result.error.errors.toLocaleString());
}
const { id } = result.data;
await prisma.job.update({
where: {
id: id,
},
data: {
isVerifiedJob: true,
},
});
revalidatePath('/manage');
return new SuccessResponse('Job Approved', 200, { jobId: id }).serialize();
});
export async function updateExpiredJobs() {
const currentDate = new Date();

Expand Down
2 changes: 1 addition & 1 deletion src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ html.dark.ql-toolbar.ql-snow {
border-bottom: 1px solid #374151 !important;
}

.ql-toolbar.ql-snow{
.ql-toolbar.ql-snow {
border: none !important;
border-bottom: 1px solid #a9aaac !important;
}
Expand Down
35 changes: 35 additions & 0 deletions src/app/manage/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import JobManagement from '@/components/JobManagement';
import { options } from '@/lib/auth';
import {
JobQuerySchema,
JobQuerySchemaType,
} from '@/lib/validators/jobs.validator';
import { getServerSession } from 'next-auth';
import { redirect } from 'next/navigation';
import React from 'react';

const ManageJob = async ({
searchParams,
}: {
searchParams: JobQuerySchemaType;
}) => {
const parsedData = JobQuerySchema.safeParse(searchParams);
const server = await getServerSession(options);
if (!server?.user) {
redirect('/api/auth/signin');
} else if (server.user.role !== 'ADMIN') {
redirect('/jobs');
}
if (!(parsedData.success && parsedData.data)) {
console.error(parsedData.error);
redirect('/jobs');
}
const searchParamss = parsedData.data;
return (
<div className="container">
<JobManagement searchParams={searchParamss} />
</div>
);
};

export default ManageJob;
57 changes: 57 additions & 0 deletions src/components/ApproveJobDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use client';
import React from 'react';
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from './ui/dialog';
import { Button } from './ui/button';

const ApproveJobDialog = ({
title,
description,
handleClick,
}: {
title: string;
description: string;
handleClick: () => void;
}) => {
return (
<>
<Dialog>
<DialogTrigger>
<span
role="button"
className="p-1 py-1 px-3 bg-primary text-primary-foreground rounded-md cursor-pointer tracking-wide"
>
Approve Now
</span>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose>
<Button
className="mt-2"
variant={'secondary'}
onClick={handleClick}
>
Approve
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
</>
);
};

export default ApproveJobDialog;
23 changes: 23 additions & 0 deletions src/components/JobManagement.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import { getAllJobs } from '@/actions/job.action';
import JobManagementHeader from './JobManagementHeader';
import JobManagementTable from './JobManagementTable';
import { JobQuerySchemaType } from '@/lib/validators/jobs.validator';

const JobManagement = async ({
searchParams,
}: {
searchParams: JobQuerySchemaType;
}) => {
const jobs = await getAllJobs(searchParams);
if (!jobs.status) {
return <div>Error {jobs.message}</div>;
}
return (
<div className="pt-2 px-6 mt-10">
<JobManagementHeader />
<JobManagementTable jobs={jobs} searchParams={searchParams} />
</div>
);
};
export default JobManagement;
25 changes: 25 additions & 0 deletions src/components/JobManagementHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import Link from 'next/link';
import { Button } from './ui/button';

const JobManagementHeader = () => {
return (
<>
<header className="pt-4 px-6 flex justify-between items-center">
<div>
<p className="text-2xl font-semibold">Active Job Posting</p>
<span className="text-slate-500">
View and Manage all active job posting.
</span>
</div>
<div>
<Button>
<Link href={'/create'}>Post new Job</Link>
</Button>
</div>
</header>
</>
);
};

export default JobManagementHeader;
Loading

0 comments on commit b195ae9

Please sign in to comment.