'use client'
import * as React from 'react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Avatar, AvatarFallback } from '@/components/ui/avatar'
import { Separator } from '@/components/ui/separator'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Progress } from '@/components/ui/progress'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog'
import { Textarea } from '@/components/ui/textarea'
import { Label } from '@/components/ui/label'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import {
ArrowLeft,
Edit,
Clock,
Users,
MapPin,
Building2,
Calendar,
AlertTriangle,
CheckCircle2,
XCircle,
Send,
RefreshCw,
UserPlus,
FileText,
History,
MessageSquare,
Briefcase,
DollarSign,
Target,
ChevronRight,
Ban,
} from 'lucide-react'
import { cn } from '@/lib/utils'
import { requisitions, auditLog, users, recruiterCapacity } from '@/lib/mock-data'
import {
STATUS_CONFIG,
URGENCY_CONFIG,
STAGE_CONFIG,
type Requisition,
type CandidateStage
} from '@/lib/types'
import { toast } from 'sonner'
function getInitials(name: string): string {
return name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2)
}
function getDaysUntilDeadline(deadline: string): number {
const now = new Date()
const deadlineDate = new Date(deadline)
return Math.ceil((deadlineDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))
}
function formatDate(dateString: string): string {
return new Date(dateString).toLocaleDateString('es-ES', {
day: '2-digit',
month: 'long',
year: 'numeric',
})
}
function formatDateTime(dateString: string): string {
return new Date(dateString).toLocaleString('es-ES', {
day: '2-digit',
month: 'short',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
})
}
// Status flow visualization component
function StatusFlow({ currentStatus }: { currentStatus: Requisition['status'] }) {
const steps = [
{ key: 'borrador', label: 'Borrador', icon: FileText },
{ key: 'pendiente_rrhh', label: 'RR.HH.', icon: Users },
{ key: 'pendiente_admin', label: 'Admin', icon: Building2 },
{ key: 'reclutando', label: 'Reclutando', icon: Briefcase },
{ key: 'cerrado', label: 'Cerrado', icon: CheckCircle2 },
]
const currentIndex = steps.findIndex(s => s.key === currentStatus)
const isCancelled = currentStatus === 'cancelado'
const isCorrection = currentStatus === 'correccion_requerida'
return (
{steps.map((step, index) => {
const Icon = step.icon
const isCompleted = index < currentIndex && !isCancelled
const isCurrent = step.key === currentStatus
const isPending = index > currentIndex
return (
{isCompleted ? (
) : isCancelled ? (
) : (
)}
{step.label}
{index < steps.length - 1 && (
)}
)
})}
{isCorrection && (
Corrección requerida - Regresado a solicitante
)}
)
}
// Recruitment stages component
function RecruitmentStages({ currentStage }: { currentStage?: CandidateStage }) {
if (!currentStage) return null
const stages: CandidateStage[] = ['reclutando', 'primera_entrevista', 'segunda_entrevista', 'referencias', 'examenes_medicos', 'seleccionado']
const currentIndex = stages.indexOf(currentStage)
return (
{stages.map((stage, index) => {
const config = STAGE_CONFIG[stage]
const isCompleted = index < currentIndex
const isCurrent = stage === currentStage
return (
{isCompleted ? : index + 1}
{config.label}
)
})}
)
}
interface RequisitionDetailPageProps {
requisitionId: string
}
export function RequisitionDetailPage({ requisitionId }: RequisitionDetailPageProps) {
const router = useRouter()
const [cancelDialogOpen, setCancelDialogOpen] = React.useState(false)
const [assignDialogOpen, setAssignDialogOpen] = React.useState(false)
const [cancelReason, setCancelReason] = React.useState('')
const [selectedRecruiter, setSelectedRecruiter] = React.useState('')
const requisition = requisitions.find(r => r.id === requisitionId)
if (!requisition) {
return (
Requisición no encontrada
La requisición {requisitionId} no existe.
)
}
const status = STATUS_CONFIG[requisition.status]
const urgency = URGENCY_CONFIG[requisition.urgency]
const daysLeft = getDaysUntilDeadline(requisition.hardDeadline)
const isOverdue = daysLeft < 0
const isAtRisk = daysLeft >= 0 && daysLeft <= 7
const progress = (requisition.vacanciesFilled / requisition.quantity) * 100
const relatedAuditLog = auditLog.filter(log => log.entityId === requisitionId)
const recruiters = users.filter(u => u.role === 'reclutador' && u.active)
const handleCancel = () => {
toast.success('Requisición cancelada', {
description: `${requisition.id} ha sido cancelada.`,
})
setCancelDialogOpen(false)
router.push('/requisiciones')
}
const handleAssignRecruiter = () => {
const recruiter = recruiters.find(r => r.id === selectedRecruiter)
toast.success('Reclutador asignado', {
description: `${recruiter?.name} ha sido asignado a ${requisition.id}.`,
})
setAssignDialogOpen(false)
}
return (
{/* Header */}
{requisition.id}
{status.label}
{urgency.label}
{requisition.positionName} - {requisition.quantity} vacante{requisition.quantity > 1 ? 's' : ''}
{requisition.status === 'borrador' && (
<>
>
)}
{requisition.status === 'correccion_requerida' && (
)}
{requisition.status === 'reclutando' && !requisition.recruiterId && (
)}
{!['cerrado', 'cancelado'].includes(requisition.status) && (
)}
{/* Status Flow */}
Estado del Proceso
{/* Main Content */}
{/* Left Column - Details */}
{/* Rejection Comment */}
{requisition.rejectionComment && (
Corrección Requerida
{requisition.rejectionComment}
)}
{/* Info Tabs */}
Detalles
Historial
{requisition.status === 'reclutando' && (
Pipeline
)}
{/* Basic Info */}
Información de la Vacante
Posición
{requisition.positionName}
Departamento
{requisition.departmentName}
Ubicación
{requisition.cityName}, {requisition.countryName}
Modalidad
{requisition.modalidad}
Tipo de Contratación
{requisition.tipoContratacion}
Cantidad de Vacantes
{requisition.quantity}
{/* Justification */}
Justificación
{requisition.justification}
{/* Compensation (if visible) */}
{(requisition.salaryPackage || requisition.budget) && (
<>
Compensación
{requisition.salaryPackage && (
Paquete Salarial
{requisition.salaryPackage}
)}
{requisition.budget && (
Presupuesto
{requisition.currency} {requisition.budget.toLocaleString()}
)}
>
)}
{relatedAuditLog.map((log, index) => (
{log.action}
{formatDateTime(log.createdAt)}
Por {log.userName}
{log.oldValue && log.newValue && (
{log.oldValue}
{log.newValue}
)}
))}
{requisition.status === 'reclutando' && (
Etapas de Reclutamiento
Progreso del proceso de selección
)}
{/* Right Column - Summary */}
{/* SLA Card */}
SLA
Fecha límite
{formatDate(requisition.hardDeadline)}
Días restantes
{isOverdue ? `${Math.abs(daysLeft)} días vencido` : `${daysLeft} días`}
{(isOverdue || isAtRisk) && (
{isOverdue ? 'SLA vencido - Acción inmediata requerida' : 'SLA en riesgo - Priorizar'}
)}
{/* Progress Card */}
Progreso de Vacantes
{requisition.vacanciesFilled}
/ {requisition.quantity}
{Math.round(progress)}% completado
{requisition.quantity - requisition.vacanciesFilled} vacante{(requisition.quantity - requisition.vacanciesFilled) !== 1 ? 's' : ''} pendiente{(requisition.quantity - requisition.vacanciesFilled) !== 1 ? 's' : ''}
{/* Requester Card */}
Solicitante
{getInitials(requisition.requesterName)}
{requisition.requesterName}
{requisition.departmentName}
Fecha de creación
{formatDate(requisition.createdAt)}
{/* Recruiter Card */}
{requisition.recruiterId && (
Reclutador Asignado
{getInitials(requisition.recruiterName || '')}
{requisition.recruiterName}
Reclutador
)}
{/* Approvals Card */}
{(requisition.approvedByHrAt || requisition.approvedByAdminAt) && (
Aprobaciones
{requisition.approvedByHrAt && (
RR.HH. - {formatDate(requisition.approvedByHrAt)}
)}
{requisition.approvedByAdminAt && (
Administración - {formatDate(requisition.approvedByAdminAt)}
)}
)}
)
}