feat(student): added eligible and non eligible tabs

This commit is contained in:
Unchanted
2025-09-02 17:20:28 +05:30
parent 5586c34c3d
commit 578c968eb9
3 changed files with 133 additions and 25 deletions

View File

@@ -43,28 +43,33 @@ export type Job = InferSelectModel<typeof jobs> & {
export type Resume = typeof resumes.$inferSelect;
export default function JobsPage({
jobs,
eligibleJobs,
ineligibleJobs,
resumes,
studentId,
appliedJobIds = [],
}: {
jobs: Job[];
eligibleJobs: Job[];
ineligibleJobs: Job[];
resumes: Resume[];
studentId: number;
appliedJobIds?: number[];
}) {
const [filteredJobs, setFilteredJobs] = useState<Job[]>([]);
const [activeTab, setActiveTab] = useState<'eligible' | 'ineligible'>('eligible');
const [searchTerm, setSearchTerm] = useState('');
const [locationFilter, setLocationFilter] = useState('all');
const [jobTypeFilter, setJobTypeFilter] = useState('all');
const [showLoadMore, setShowLoadMore] = useState(false);
const allJobs = [...eligibleJobs, ...ineligibleJobs];
useEffect(() => {
filterJobs();
}, [jobs, searchTerm, locationFilter, jobTypeFilter]);
}, [eligibleJobs, ineligibleJobs, activeTab, searchTerm, locationFilter, jobTypeFilter]);
const filterJobs = () => {
let filtered = [...jobs];
let base = activeTab === 'eligible' ? eligibleJobs : ineligibleJobs;
let filtered = [...base];
// Search filter
if (searchTerm) {
@@ -180,7 +185,7 @@ export default function JobsPage({
Clear Filters
</Button>
<span className="text-sm text-gray-500">
{filteredJobs.length} of {jobs.length} jobs
{filteredJobs.length} of {allJobs.length} jobs
</span>
</div>
)}
@@ -194,7 +199,7 @@ export default function JobsPage({
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Total Jobs</p>
<p className="text-3xl font-bold text-gray-800">{jobs.length}</p>
<p className="text-3xl font-bold text-gray-800">{allJobs.length}</p>
</div>
<Briefcase className="w-8 h-8 text-blue-600" />
</div>
@@ -207,7 +212,7 @@ export default function JobsPage({
<div>
<p className="text-sm font-medium text-gray-600">Active Companies</p>
<p className="text-3xl font-bold text-gray-800">
{new Set(jobs.map((job) => job.company.name)).size}
{new Set(allJobs.map((job) => job.company.name)).size}
</p>
</div>
<Building2 className="w-8 h-8 text-green-600" />
@@ -221,7 +226,7 @@ export default function JobsPage({
<div>
<p className="text-sm font-medium text-gray-600">Remote Jobs</p>
<p className="text-3xl font-bold text-gray-800">
{jobs.filter((job) => job.location.toLowerCase().includes('remote')).length}
{allJobs.filter((job) => job.location.toLowerCase().includes('remote')).length}
</p>
</div>
<Users className="w-8 h-8 text-purple-600" />
@@ -242,6 +247,42 @@ export default function JobsPage({
</Card>
</div>
{/* Tabs */}
<Card className="bg-white shadow-sm mb-6">
<CardContent className="p-6">
<div className="text-center mb-3">
<h2 className="text-lg font-semibold text-gray-800">Select a category to view jobs</h2>
<p className="text-sm text-gray-500">Switch between eligible and not eligible jobs based on your profile</p>
</div>
<div className="flex items-center justify-center">
<div className="inline-flex rounded-full border border-gray-200 bg-gray-50 p-1">
<Button
variant={activeTab === 'eligible' ? 'default' : 'ghost'}
size="sm"
className={`rounded-full px-5 ${activeTab === 'eligible' ? '' : 'text-gray-700'}`}
onClick={() => setActiveTab('eligible')}
>
Eligible
<span className={`ml-2 inline-flex items-center justify-center rounded-full text-xs px-2 py-0.5 ${activeTab === 'eligible' ? 'bg-white text-gray-900' : 'bg-gray-200 text-gray-700'}`}>
{eligibleJobs.length}
</span>
</Button>
<Button
variant={activeTab === 'ineligible' ? 'default' : 'ghost'}
size="sm"
className={`rounded-full px-5 ${activeTab === 'ineligible' ? '' : 'text-gray-700'}`}
onClick={() => setActiveTab('ineligible')}
>
Not Eligible
<span className={`ml-2 inline-flex items-center justify-center rounded-full text-xs px-2 py-0.5 ${activeTab === 'ineligible' ? 'bg-white text-gray-900' : 'bg-gray-200 text-gray-700'}`}>
{ineligibleJobs.length}
</span>
</Button>
</div>
</div>
</CardContent>
</Card>
{/* Jobs Grid */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{displayedJobs.map((job) => (
@@ -298,13 +339,24 @@ export default function JobsPage({
<div className="flex items-center justify-between">
<div className="flex gap-2">
<JobApplicationModal
job={{ ...job, minCGPA: Number(job.minCGPA) }}
studentId={studentId} // Mock student ID - in real app this would come from auth
resumes={resumes}
/>
{activeTab === 'eligible' ? (
<JobApplicationModal
job={{ ...job, minCGPA: Number(job.minCGPA) }}
studentId={studentId}
resumes={resumes}
isApplied={appliedJobIds.includes(job.id)}
/>
) : (
<Button size="sm" variant="outline" disabled>
Not Eligible
</Button>
)}
{job.link && (
<Button size="sm" variant="outline">
<Button
size="sm"
variant="outline"
onClick={() => window.open(job.link as string, '_blank')}
>
<ExternalLink className="w-4 h-4 mr-2" />
View Details
</Button>

View File

@@ -1,6 +1,6 @@
import JobsClient from './JobClient';
import { auth } from '@/auth';
import { db, resumes } from '@workspace/db';
import { db, resumes, students } from '@workspace/db';
import { eq } from '@workspace/db/drizzle';
import { getStudentApplicationJobIds } from '../actions';
@@ -18,5 +18,49 @@ export default async function JobsPage() {
const { success, appliedJobIds } = await getStudentApplicationJobIds(studentId);
const studentAppliedJobIds = success ? appliedJobIds : [];
return <JobsClient jobs={jobs} resumes={reusmes} studentId={studentId} appliedJobIds={studentAppliedJobIds} />;
// Fetch student with grades for eligibility computation
const student = await db.query.students.findFirst({
where: eq(students.id, studentId),
with: { grades: true },
});
const studentSSC = Number((student as any)?.ssc ?? 0);
const studentHSC = Number((student as any)?.hsc ?? 0);
const grades = (student?.grades || []).map((g) => ({
sem: g.sem,
sgpi: Number(g.sgpi),
isKT: Boolean(g.isKT),
deadKT: Boolean(g.deadKT),
}));
const hasLiveKT = grades.some((g) => g.isKT);
const hasDeadKT = grades.some((g) => g.deadKT);
const avgCGPA = grades.length > 0 ? Number((grades.reduce((a, b) => a + (b.sgpi || 0), 0) / grades.length).toFixed(2)) : 0;
const isEligible = (job: typeof jobs[number]) => {
const minCGPA = Number(job.minCGPA || 0);
const minSSC = Number(job.minSSC || 0);
const minHSC = Number(job.minHSC || 0);
const allowDeadKT = Boolean(job.allowDeadKT);
const allowLiveKT = Boolean(job.allowLiveKT);
if (avgCGPA < minCGPA) return false;
if (studentSSC < minSSC) return false;
if (studentHSC < minHSC) return false;
if (!allowLiveKT && hasLiveKT) return false;
if (!allowDeadKT && hasDeadKT) return false;
return true;
};
const eligibleJobs = jobs.filter(isEligible);
const ineligibleJobs = jobs.filter((j) => !isEligible(j));
return (
<JobsClient
eligibleJobs={eligibleJobs as any}
ineligibleJobs={ineligibleJobs as any}
resumes={reusmes}
studentId={studentId}
appliedJobIds={studentAppliedJobIds}
/>
);
}

View File

@@ -69,18 +69,30 @@ export default function JobApplicationModal({ job, studentId, resumes, isApplied
}
};
const isDeadlinePassed = new Date() > job.applicationDeadline;
const isDeadlinePassed = new Date() > new Date(job.applicationDeadline as any);
const cannotApplyReason = isApplied
? 'You have already applied to this job'
: resumes.length === 0
? 'No resumes found. Please upload a resume first.'
: isDeadlinePassed
? 'Application deadline has passed'
: null;
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button
size="sm"
className="bg-blue-600 hover:bg-blue-700"
disabled={isDeadlinePassed}
>
Apply Now
</Button>
<div className="flex flex-col items-start">
<Button
size="sm"
className="bg-blue-600 hover:bg-blue-700"
disabled={Boolean(cannotApplyReason)}
>
{isApplied ? 'Applied' : 'Apply Now'}
</Button>
{cannotApplyReason && (
<span className="mt-1 text-xs text-red-600">{cannotApplyReason}</span>
)}
</div>
</DialogTrigger>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
@@ -180,7 +192,7 @@ export default function JobApplicationModal({ job, studentId, resumes, isApplied
<div className="flex gap-3 pt-4">
<Button
onClick={handleApply}
disabled={isApplying || resumes.length === 0}
disabled={isApplying || resumes.length === 0 || isApplied}
className="flex-1 bg-blue-600 hover:bg-blue-700"
>
{isApplying ? 'Submitting...' : 'Submit Application'}