lms/resources/js/pages/job-circulars/show.tsx
2025-12-15 12:26:23 +01:00

253 lines
13 KiB
TypeScript

import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import LandingLayout from '@/layouts/landing-layout';
import { SharedData } from '@/types/global';
import { Head, usePage } from '@inertiajs/react';
import { Building2, Clock, Eye, Mail, MapPin, Zap } from 'lucide-react';
import { useState } from 'react';
import { Renderer } from 'richtor';
import 'richtor/styles';
interface JobCircularShowPageProps extends SharedData {
jobCircular: JobCircular;
}
const JobCircularShow = () => {
const { jobCircular, translate } = usePage<JobCircularShowPageProps>().props;
const { frontend, button, common } = translate;
const [showFullDescription, setShowFullDescription] = useState(false);
// Calculate days until deadline
const getDaysUntilDeadline = () => {
const deadline = new Date(jobCircular.application_deadline);
const today = new Date();
const diffTime = deadline.getTime() - today.getTime();
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
if (diffDays < 0) return common.expired;
if (diffDays === 0) return common.today;
if (diffDays === 1) return frontend.day_left;
return `${diffDays} ${frontend.days_left}`;
};
// Format salary display
const getFormattedSalary = () => {
const min = jobCircular.salary_min?.toLocaleString();
const max = jobCircular.salary_max?.toLocaleString();
const currency = jobCircular.salary_currency;
if (jobCircular.salary_negotiable) {
return `${currency} ${min} - ${max} (${frontend.negotiable})`;
}
return `${currency} ${min} - ${max}`;
};
// Get status badge color
const getStatusBadge = (status: string) => {
const statusConfig: Record<string, { label: string; variant: 'default' | 'secondary' | 'destructive' | 'outline' }> = {
active: { label: common.active, variant: 'default' },
draft: { label: common.draft, variant: 'secondary' },
closed: { label: frontend.closed, variant: 'destructive' },
expired: { label: common.expired, variant: 'outline' },
};
const config = statusConfig[status] || { label: status, variant: 'outline' };
return <Badge variant={config.variant}>{config.label}</Badge>;
};
return (
<LandingLayout customizable={false}>
<Head title={jobCircular.title} />
<div className="min-h-screen">
{/* Header */}
<div className="border-border border-b">
<div className="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
<div className="flex items-center justify-between">
<div>
<div className="flex items-center space-x-2">
<h1 className="text-2xl font-semibold">{jobCircular.title}</h1>
{getStatusBadge(jobCircular.status)}
</div>
<div className="text-muted-foreground mt-2 flex items-center space-x-4 text-sm">
<div className="flex items-center">
<Building2 className="mr-1 h-4 w-4" />
<span>{frontend.company_fallback}</span>
</div>
<div className="flex items-center">
<MapPin className="mr-1 h-4 w-4" />
<span>{jobCircular.location}</span>
</div>
<div className="flex items-center">
<Clock className="mr-1 h-4 w-4" />
<span>{getDaysUntilDeadline()}</span>
</div>
</div>
</div>
<div className="flex items-center space-x-3">
<Button size="sm" asChild>
<a href={`mailto:${jobCircular.contact_email}`}>
<Mail className="mr-2 h-4 w-4" />
{button.apply}
</a>
</Button>
</div>
</div>
</div>
</div>
<div className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 gap-8 lg:grid-cols-3">
{/* Main Content */}
<div className="space-y-6 lg:col-span-2">
{/* Job Overview Card */}
<Card className="!shadow-none">
<CardHeader className="pb-4">
<div className="flex items-start justify-between">
<CardTitle className="mb-2 text-xl font-semibold">{jobCircular.title}</CardTitle>
<div className="text-right">
<h6 className="text-secondary-foreground text-lg font-semibold">{getFormattedSalary()}</h6>
<div className="text-muted-foreground mt-1 text-sm">
{jobCircular.positions_available} {jobCircular.positions_available !== 1 ? frontend.positions : frontend.position}{' '}
{frontend.available}
</div>
</div>
</div>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 gap-4 md:grid-cols-4">
<div className="bg-muted rounded-lg p-4 text-center">
<div className="mb-2 text-2xl">🎯</div>
<div className="font-semibold capitalize">{jobCircular.experience_level}</div>
<div className="text-muted-foreground text-sm">{frontend.experience_level}</div>
</div>
<div className="bg-muted rounded-lg p-4 text-center">
<div className="mb-2 text-2xl">💼</div>
<div className="font-semibold capitalize">{jobCircular.job_type}</div>
<div className="text-muted-foreground text-sm">{frontend.job_type}</div>
</div>
<div className="bg-muted rounded-lg p-4 text-center">
<div className="mb-2 text-2xl">🏢</div>
<div className="font-semibold capitalize">{jobCircular.work_type}</div>
<div className="text-muted-foreground text-sm">{frontend.work_type}</div>
</div>
<div className="bg-muted rounded-lg p-4 text-center">
<div className="mb-2 text-2xl">📅</div>
<div className="font-semibold capitalize">{getDaysUntilDeadline()}</div>
<div className="text-muted-foreground text-sm">{frontend.application_deadline}</div>
</div>
</div>
</CardContent>
</Card>
{/* Job Description */}
<Card className="!shadow-none">
<CardHeader>
<CardTitle className="text-xl font-semibold">{frontend.job_description}</CardTitle>
</CardHeader>
<CardContent>
<div className={`${showFullDescription ? '' : 'max-h-96 overflow-hidden'}`}>
<Renderer value={jobCircular.description} />
</div>
{!showFullDescription && (
<div className="mt-4 text-center">
<Button variant="outline" onClick={() => setShowFullDescription(true)} className="w-full">
<Eye className="mr-2 h-4 w-4" />
{button.show_full}
</Button>
</div>
)}
{showFullDescription && (
<div className="mt-4 text-center">
<Button variant="outline" onClick={() => setShowFullDescription(false)} className="w-full">
<Eye className="mr-2 h-4 w-4" />
{button.show_less}
</Button>
</div>
)}
</CardContent>
</Card>
{/* Required Skills */}
<Card className="!shadow-none">
<CardHeader>
<CardTitle className="flex items-center text-xl font-semibold">
<Zap className="mr-2 h-5 w-5 text-yellow-500" />
{frontend.skills_required}
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex flex-wrap gap-2">
{jobCircular.skills_required.map((skill, index) => (
<Badge key={index} variant="secondary" className="px-3 py-1 text-sm">
{skill}
</Badge>
))}
</div>
</CardContent>
</Card>
</div>
{/* Sidebar */}
<div className="space-y-6">
{/* Quick Apply Card */}
<Card className="!shadow-none">
<CardHeader className="text-center">
<CardTitle className="text-xl font-semibold">{frontend.quick_apply}</CardTitle>
<CardDescription>{frontend.send_application}</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<Button className="w-full" asChild>
<a href={`mailto:${jobCircular.contact_email}`}>
<Mail className="mr-2 h-4 w-4" />
{frontend.apply_via_email}
</a>
</Button>
<div className="text-muted-foreground text-center text-sm">
<p>{frontend.application_deadline}:</p>
<p className="font-semibold">{new Date(jobCircular.application_deadline).toLocaleDateString()}</p>
</div>
</CardContent>
</Card>
{/* Job Stats */}
<Card className="!shadow-none">
<CardHeader>
<CardTitle className="text-lg font-semibold">{frontend.job_statistics}</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="bg-secondary text-secondary-foreground rounded-lg p-3 text-center">
<div className="text-2xl font-semibold">{jobCircular.positions_available}</div>
<div className="text-sm">{frontend.positions_available}</div>
</div>
<div className="bg-secondary text-secondary-foreground rounded-lg p-3 text-center">
<div className="text-2xl font-semibold">{getDaysUntilDeadline().includes('Expired') ? '0' : 'Active'}</div>
<div className="text-sm">{common.status}</div>
</div>
</div>
<div className="text-muted-foreground text-center text-sm">
<p>
{frontend.posted} {new Date(jobCircular.created_at).toLocaleDateString()}
</p>
<p>
{frontend.last_updated} {new Date(jobCircular.updated_at).toLocaleDateString()}
</p>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
</div>
</LandingLayout>
);
};
export default JobCircularShow;