ai-hackaton-frontend/app/vacancy/[id]/page.tsx
2025-09-08 16:08:40 +03:00

308 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { useParams, useRouter } from 'next/navigation'
import { useVacancy } from '@/hooks/useVacancy'
import { VacancyRead } from '@/types/api'
import {
ArrowLeft,
MapPin,
Clock,
Banknote,
Building,
Users,
Calendar,
Phone,
Mail,
Globe, FileText
} from 'lucide-react'
import ResumeUploadForm from '@/components/ResumeUploadForm'
export default function VacancyPage() {
const params = useParams()
const router = useRouter()
const vacancyId = parseInt(params.id as string)
const { data: vacancy, isLoading, error } = useVacancy(vacancyId)
const formatSalary = (vacancy: VacancyRead) => {
if (!vacancy.salary_from && !vacancy.salary_to) return 'Зарплата не указана'
const currency = vacancy.salary_currency === 'RUR' ? '₽' : vacancy.salary_currency
if (vacancy.salary_from && vacancy.salary_to) {
return `${ vacancy.salary_from.toLocaleString() } - ${ vacancy.salary_to.toLocaleString() } ${ currency }`
}
if (vacancy.salary_from) {
return `от ${ vacancy.salary_from.toLocaleString() } ${ currency }`
}
if (vacancy.salary_to) {
return `до ${ vacancy.salary_to.toLocaleString() } ${ currency }`
}
}
const getExperienceText = (experience: string) => {
const mapping = {
noExperience: 'Без опыта',
between1And3: '1-3 года',
between3And6: '3-6 лет',
moreThan6: 'Более 6 лет'
}
return mapping[experience as keyof typeof mapping] || experience
}
const getEmploymentText = (employment: string) => {
const mapping = {
full: 'Полная занятость',
part: 'Частичная занятость',
project: 'Проектная работа',
volunteer: 'Волонтерство',
probation: 'Стажировка'
}
return mapping[employment as keyof typeof mapping] || employment
}
const getScheduleText = (schedule: string) => {
const mapping = {
fullDay: 'Полный день',
shift: 'Сменный график',
flexible: 'Гибкий график',
remote: 'Удаленная работа',
flyInFlyOut: 'Вахтовый метод'
}
return mapping[schedule as keyof typeof mapping] || schedule
}
if (isLoading) {
return (
<div className="flex justify-center items-center min-h-[400px]">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600"></div>
</div>
)
}
if (error || !vacancy) {
return (
<div className="text-center py-12">
<div className="text-red-600 mb-4">
<p>Не удалось загрузить информацию о вакансии</p>
</div>
<button
onClick={ () => router.back() }
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-primary-600 hover:bg-primary-700"
>
<ArrowLeft className="h-4 w-4 mr-2"/>
Назад
</button>
</div>
)
}
return (
<div className="max-w-4xl mx-auto space-y-8">
{/* Header */ }
<div className="flex items-center justify-between">
<button
onClick={ () => router.back() }
className="inline-flex items-center text-gray-600 hover:text-gray-900"
>
<ArrowLeft className="h-5 w-5 mr-2"/>
Назад к вакансиям
</button>
{ vacancy.premium && (
<span className="px-3 py-1 bg-yellow-100 text-yellow-800 text-sm font-medium rounded-full">
Premium
</span>
) }
</div>
{/* Main Content */ }
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Left Column - Vacancy Details */ }
<div className="lg:col-span-2 space-y-6">
{/* Title and Company */ }
<div className="bg-white rounded-lg shadow-md p-6">
<h1 className="text-3xl font-bold text-gray-900 mb-4">
{ vacancy.title }
</h1>
<div className="flex items-center mb-6">
<Building className="h-5 w-5 text-gray-400 mr-2"/>
<span className="text-lg font-medium text-gray-900">
{vacancy.company_name || 'Не указано'}
</span>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 text-sm">
<div className="flex items-center text-gray-600">
<Banknote className="h-4 w-4 mr-2"/>
<span>{ formatSalary(vacancy) }</span>
</div>
<div className="flex items-center text-gray-600">
<MapPin className="h-4 w-4 mr-2" />
<span>{vacancy.area_name || 'Не указано'}</span>
</div>
<div className="flex items-center text-gray-600">
<Clock className="h-4 w-4 mr-2"/>
<span>{ getExperienceText(vacancy.experience) }</span>
</div>
<div className="flex items-center text-gray-600">
<Users className="h-4 w-4 mr-2"/>
<span>{ getEmploymentText(vacancy.employment_type) }</span>
</div>
<div className="flex items-center text-gray-600">
<Calendar className="h-4 w-4 mr-2"/>
<span>{ getScheduleText(vacancy.schedule) }</span>
</div>
{ vacancy.published_at && (
<div className="flex items-center text-gray-600">
<Clock className="h-4 w-4 mr-2"/>
<span>Опубликовано { new Date(vacancy.published_at).toLocaleDateString('ru-RU') }</span>
</div>
) }
</div>
</div>
{/* Description */ }
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-semibold text-gray-900 mb-4">
Описание вакансии
</h2>
<div className="prose prose-gray max-w-none">
<p className="whitespace-pre-line text-gray-700 leading-relaxed">
{ vacancy.description }
</p>
</div>
</div>
{/* Key Skills */ }
{ vacancy.key_skills && (
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-semibold text-gray-900 mb-4">
Ключевые навыки
</h2>
<div className="prose prose-gray max-w-none">
<p className="text-gray-700">{ vacancy.key_skills }</p>
</div>
</div>
) }
{/* Company Description */ }
{ vacancy.company_description && (
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-semibold text-gray-900 mb-4">
О компании
</h2>
<div className="prose prose-gray max-w-none">
<p className="whitespace-pre-line text-gray-700 leading-relaxed">
{ vacancy.company_description }
</p>
</div>
</div>
) }
{/* Location Details */ }
{ ( vacancy.address || vacancy.metro_stations ) && (
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-semibold text-gray-900 mb-4">
Местоположение
</h2>
<div className="space-y-2 text-gray-700">
{ vacancy.address && (
<div className="flex items-start">
<MapPin className="h-4 w-4 mr-2 mt-0.5 flex-shrink-0"/>
<span>{ vacancy.address }</span>
</div>
) }
{ vacancy.metro_stations && (
<div className="flex items-start">
<span className="text-sm font-medium mr-2">Метро:</span>
<span className="text-sm">{ vacancy.metro_stations }</span>
</div>
) }
</div>
</div>
) }
</div>
{/* Right Column - Application Form and Contact Info */ }
<div className="space-y-6">
{/* Contact Information */ }
{ ( vacancy.contacts_name || vacancy.contacts_email || vacancy.contacts_phone || vacancy.url ) && (
<div className="bg-white rounded-lg shadow-md p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">
Контактная информация
</h3>
<div className="space-y-3 text-sm">
{ vacancy.contacts_name && (
<div className="flex items-center text-gray-700">
<Users className="h-4 w-4 mr-2"/>
<span>{ vacancy.contacts_name }</span>
</div>
) }
{ vacancy.contacts_email && (
<div className="flex items-center text-gray-700">
<Mail className="h-4 w-4 mr-2"/>
<a
href={ `mailto:${ vacancy.contacts_email }` }
className="hover:text-primary-600"
>
{ vacancy.contacts_email }
</a>
</div>
) }
{ vacancy.contacts_phone && (
<div className="flex items-center text-gray-700">
<Phone className="h-4 w-4 mr-2"/>
<a
href={ `tel:${ vacancy.contacts_phone }` }
className="hover:text-primary-600"
>
{ vacancy.contacts_phone }
</a>
</div>
) }
{ vacancy.url && (
<div className="flex items-center text-gray-700">
<Globe className="h-4 w-4 mr-2"/>
<a
href={ vacancy.url }
target="_blank"
rel="noopener noreferrer"
className="hover:text-primary-600"
>
Перейти к вакансии
</a>
</div>
) }
</div>
</div>
) }
<div>
<a
href={`/vacancy/report/${vacancyId}`}
rel="noopener noreferrer"
className="w-full flex items-center justify-center px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-lg shadow hover:bg-indigo-700 transition"
>
Открыть отчеты
</a>
</div>
{/* Application Form */ }
<ResumeUploadForm
vacancyId={ vacancy.id }
vacancyTitle={ vacancy.title }
/>
</div>
</div>
</div>
)
}