This commit is contained in:
Om Lanke
2025-07-08 16:23:01 +05:30
parent e60e0cded7
commit 900b18da1a
3 changed files with 140 additions and 43 deletions

View File

@@ -0,0 +1,58 @@
'use client';
import { useState, useTransition } from 'react';
import {
Select,
SelectTrigger,
SelectContent,
SelectItem,
SelectValue,
} from '@workspace/ui/components/select';
const STATUS_OPTIONS = [
'in review',
'Online Assessment',
'Interview round',
'offer given',
'accepted',
'rejected',
];
interface StatusSelectProps {
applicationId: number;
initialStatus: string;
studentId: number;
}
export default function StatusSelect({
applicationId,
initialStatus,
studentId,
}: StatusSelectProps) {
const [status, setStatus] = useState(initialStatus);
const [isPending, startTransition] = useTransition();
const handleChange = (value: string) => {
setStatus(value); // Optimistic update
startTransition(async () => {
await fetch(`/api/applications/${applicationId}/status`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: value, studentId }),
});
});
};
return (
<Select value={status} onValueChange={handleChange} disabled={isPending}>
<SelectTrigger className="min-w-[160px]" />
<SelectContent>
{STATUS_OPTIONS.map((option) => (
<SelectItem key={option} value={option}>
{option}
</SelectItem>
))}
</SelectContent>
</Select>
);
}

View File

@@ -18,6 +18,7 @@ import {
Clock
} from 'lucide-react';
import Link from 'next/link';
import StatusSelect from './StatusSelect';
interface JobPageProps {
params: { jobId: string };
@@ -33,7 +34,11 @@ export default async function JobDetailPage({ params }: JobPageProps) {
if (jobRes.length === 0 || !jobRes[0]) notFound();
const job = jobRes[0];
const companyRes = await db.select().from(companies).where(eq(companies.id, job.companyId)).limit(1);
const companyRes = await db
.select()
.from(companies)
.where(eq(companies.id, job.companyId))
.limit(1);
const company = companyRes[0];
const applicants = await db
@@ -43,6 +48,7 @@ export default async function JobDetailPage({ params }: JobPageProps) {
firstName: students.firstName,
lastName: students.lastName,
email: students.email,
studentId: students.id,
})
.from(applications)
.leftJoin(students, eq(applications.studentId, students.id))
@@ -99,7 +105,7 @@ export default async function JobDetailPage({ params }: JobPageProps) {
</div>
</div>
<Badge
variant={job.active ? "default" : "secondary"}
variant={job.active ? 'default' : 'secondary'}
className={`${
job.active
? 'bg-green-100 text-green-700 hover:bg-green-200'
@@ -131,7 +137,9 @@ export default async function JobDetailPage({ params }: JobPageProps) {
<Calendar className="w-5 h-5 text-gray-500" />
<div>
<p className="text-sm font-medium text-gray-700">Application Deadline</p>
<p className="text-sm text-gray-600">{job.applicationDeadline.toLocaleDateString()}</p>
<p className="text-sm text-gray-600">
{job.applicationDeadline.toLocaleDateString()}
</p>
</div>
</div>
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg">
@@ -199,22 +207,30 @@ export default async function JobDetailPage({ params }: JobPageProps) {
</div>
</div>
<div className="mt-4 flex gap-4">
<div className={`flex items-center gap-2 px-3 py-2 rounded-lg ${
<div
className={`flex items-center gap-2 px-3 py-2 rounded-lg ${
job.allowDeadKT ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'
}`}>
<div className={`w-2 h-2 rounded-full ${
}`}
>
<div
className={`w-2 h-2 rounded-full ${
job.allowDeadKT ? 'bg-green-500' : 'bg-red-500'
}`} />
}`}
/>
<span className="text-sm font-medium">
Dead KT: {job.allowDeadKT ? 'Allowed' : 'Not Allowed'}
</span>
</div>
<div className={`flex items-center gap-2 px-3 py-2 rounded-lg ${
<div
className={`flex items-center gap-2 px-3 py-2 rounded-lg ${
job.allowLiveKT ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'
}`}>
<div className={`w-2 h-2 rounded-full ${
}`}
>
<div
className={`w-2 h-2 rounded-full ${
job.allowLiveKT ? 'bg-green-500' : 'bg-red-500'
}`} />
}`}
/>
<span className="text-sm font-medium">
Live KT: {job.allowLiveKT ? 'Allowed' : 'Not Allowed'}
</span>
@@ -277,7 +293,9 @@ export default async function JobDetailPage({ params }: JobPageProps) {
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-600">Application Deadline</span>
<span className="font-medium">{job.applicationDeadline.toLocaleDateString()}</span>
<span className="font-medium">
{job.applicationDeadline.toLocaleDateString()}
</span>
</div>
</CardContent>
</Card>
@@ -301,7 +319,9 @@ export default async function JobDetailPage({ params }: JobPageProps) {
<div className="text-center py-12">
<Users className="w-12 h-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">No applications yet</h3>
<p className="text-gray-600">Students will appear here once they apply for this job.</p>
<p className="text-gray-600">
Students will appear here once they apply for this job.
</p>
</div>
) : (
<div className="rounded-lg border border-gray-200 overflow-hidden">
@@ -317,22 +337,16 @@ export default async function JobDetailPage({ params }: JobPageProps) {
{applicants.map((applicant) => (
<TableRow key={applicant.applicationId} className="hover:bg-gray-50">
<TableCell className="font-medium">
{`${applicant.firstName ?? ''} ${applicant.lastName ?? ''}`.trim() || 'Unknown'}
{`${applicant.firstName ?? ''} ${applicant.lastName ?? ''}`.trim() ||
'Unknown'}
</TableCell>
<TableCell className="text-gray-600">{applicant.email}</TableCell>
<TableCell>
<Badge
variant="outline"
className={`${
applicant.status === 'approved'
? 'border-green-200 text-green-700 bg-green-50'
: applicant.status === 'rejected'
? 'border-red-200 text-red-700 bg-red-50'
: 'border-yellow-200 text-yellow-700 bg-yellow-50'
}`}
>
{applicant.status.charAt(0).toUpperCase() + applicant.status.slice(1)}
</Badge>
<StatusSelect
applicationId={applicant.applicationId}
initialStatus={applicant.status}
studentId={applicant.studentId ?? 0}
/>
</TableCell>
</TableRow>
))}

View File

@@ -0,0 +1,25 @@
import { db, applications } from '@workspace/db';
import { eq } from '@workspace/db/drizzle';
import { NextRequest, NextResponse } from 'next/server';
export async function PATCH(req: NextRequest, { params }: { params: { applicationId: string } }) {
const applicationId = Number(params.applicationId);
if (isNaN(applicationId)) {
return NextResponse.json({ error: 'Invalid applicationId' }, { status: 400 });
}
const { status } = await req.json();
if (!status) {
return NextResponse.json({ error: 'Missing status' }, { status: 400 });
}
const result = await db.update(applications)
.set({ status })
.where(eq(applications.id, applicationId));
if (result.rowCount === 0) {
return NextResponse.json({ error: 'Application not found' }, { status: 404 });
}
return NextResponse.json({ success: true });
}