706 lines
29 KiB
TypeScript
706 lines
29 KiB
TypeScript
'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 { Button } from '@/components/ui/button'
|
|
import { Input } from '@/components/ui/input'
|
|
import { Label } from '@/components/ui/label'
|
|
import { Textarea } from '@/components/ui/textarea'
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from '@/components/ui/select'
|
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
|
|
import { Separator } from '@/components/ui/separator'
|
|
import {
|
|
ArrowLeft,
|
|
ArrowRight,
|
|
Save,
|
|
Send,
|
|
MapPin,
|
|
Building2,
|
|
Briefcase,
|
|
FileText,
|
|
DollarSign,
|
|
CheckCircle2,
|
|
AlertCircle,
|
|
} from 'lucide-react'
|
|
import { cn } from '@/lib/utils'
|
|
import { countries, cities, departments, positions, currentUser, getCitiesByCountry } from '@/lib/mock-data'
|
|
import { toast } from 'sonner'
|
|
|
|
type FormStep = 'ubicacion' | 'vacante' | 'justificacion' | 'compensacion' | 'revision'
|
|
|
|
const STEPS: { key: FormStep; label: string; icon: React.ElementType }[] = [
|
|
{ key: 'ubicacion', label: 'Ubicación', icon: MapPin },
|
|
{ key: 'vacante', label: 'Vacante', icon: Briefcase },
|
|
{ key: 'justificacion', label: 'Justificación', icon: FileText },
|
|
{ key: 'compensacion', label: 'Compensación', icon: DollarSign },
|
|
{ key: 'revision', label: 'Revisión', icon: CheckCircle2 },
|
|
]
|
|
|
|
interface FormData {
|
|
countryId: string
|
|
cityId: string
|
|
departmentId: string
|
|
positionId: string
|
|
quantity: number
|
|
modalidad: 'presencial' | 'remoto' | 'hibrido'
|
|
tipoContratacion: 'nuevo' | 'reemplazo' | 'temporal'
|
|
justification: string
|
|
urgency: 'normal' | 'alta' | 'critica'
|
|
salaryPackage: string
|
|
budget: string
|
|
currency: string
|
|
}
|
|
|
|
function StepIndicator({ currentStep }: { currentStep: FormStep }) {
|
|
const currentIndex = STEPS.findIndex(s => s.key === currentStep)
|
|
|
|
return (
|
|
<div className="flex items-center justify-between mb-8">
|
|
{STEPS.map((step, index) => {
|
|
const Icon = step.icon
|
|
const isCompleted = index < currentIndex
|
|
const isCurrent = step.key === currentStep
|
|
|
|
return (
|
|
<React.Fragment key={step.key}>
|
|
<div className="flex flex-col items-center gap-2">
|
|
<div className={cn(
|
|
'flex h-10 w-10 items-center justify-center rounded-full border-2 transition-colors',
|
|
isCompleted && 'border-emerald-500 bg-emerald-500 text-white',
|
|
isCurrent && 'border-primary bg-primary text-primary-foreground',
|
|
!isCompleted && !isCurrent && 'border-muted-foreground/30 bg-muted text-muted-foreground'
|
|
)}>
|
|
{isCompleted ? (
|
|
<CheckCircle2 className="h-5 w-5" />
|
|
) : (
|
|
<Icon className="h-5 w-5" />
|
|
)}
|
|
</div>
|
|
<span className={cn(
|
|
'text-xs font-medium hidden sm:block',
|
|
(isCompleted || isCurrent) && 'text-foreground',
|
|
!isCompleted && !isCurrent && 'text-muted-foreground'
|
|
)}>
|
|
{step.label}
|
|
</span>
|
|
</div>
|
|
{index < STEPS.length - 1 && (
|
|
<div className={cn(
|
|
'flex-1 h-0.5 mx-2',
|
|
index < currentIndex ? 'bg-emerald-500' : 'bg-muted-foreground/30'
|
|
)} />
|
|
)}
|
|
</React.Fragment>
|
|
)
|
|
})}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function NewRequisitionPage() {
|
|
const router = useRouter()
|
|
const [currentStep, setCurrentStep] = React.useState<FormStep>('ubicacion')
|
|
const [errors, setErrors] = React.useState<Partial<Record<keyof FormData, string>>>({})
|
|
const [formData, setFormData] = React.useState<FormData>({
|
|
countryId: '',
|
|
cityId: '',
|
|
departmentId: '',
|
|
positionId: '',
|
|
quantity: 1,
|
|
modalidad: 'presencial',
|
|
tipoContratacion: 'nuevo',
|
|
justification: '',
|
|
urgency: 'normal',
|
|
salaryPackage: '',
|
|
budget: '',
|
|
currency: 'USD',
|
|
})
|
|
|
|
const availableCities = formData.countryId ? getCitiesByCountry(formData.countryId) : []
|
|
|
|
const updateFormData = (field: keyof FormData, value: string | number) => {
|
|
setFormData(prev => ({ ...prev, [field]: value }))
|
|
if (errors[field]) {
|
|
setErrors(prev => ({ ...prev, [field]: undefined }))
|
|
}
|
|
// Reset city when country changes
|
|
if (field === 'countryId') {
|
|
setFormData(prev => ({ ...prev, cityId: '' }))
|
|
}
|
|
}
|
|
|
|
const validateStep = (step: FormStep): boolean => {
|
|
const newErrors: Partial<Record<keyof FormData, string>> = {}
|
|
|
|
if (step === 'ubicacion') {
|
|
if (!formData.countryId) newErrors.countryId = 'Selecciona un país'
|
|
if (!formData.cityId) newErrors.cityId = 'Selecciona una ciudad'
|
|
if (!formData.departmentId) newErrors.departmentId = 'Selecciona un departamento'
|
|
}
|
|
|
|
if (step === 'vacante') {
|
|
if (!formData.positionId) newErrors.positionId = 'Selecciona una posición'
|
|
if (formData.quantity < 1) newErrors.quantity = 'La cantidad debe ser al menos 1'
|
|
}
|
|
|
|
if (step === 'justificacion') {
|
|
if (!formData.justification.trim()) newErrors.justification = 'La justificación es obligatoria'
|
|
if (formData.justification.length < 20) newErrors.justification = 'Proporciona una justificación más detallada (mín. 20 caracteres)'
|
|
}
|
|
|
|
setErrors(newErrors)
|
|
return Object.keys(newErrors).length === 0
|
|
}
|
|
|
|
const goToNextStep = () => {
|
|
if (!validateStep(currentStep)) return
|
|
|
|
const currentIndex = STEPS.findIndex(s => s.key === currentStep)
|
|
if (currentIndex < STEPS.length - 1) {
|
|
setCurrentStep(STEPS[currentIndex + 1].key)
|
|
}
|
|
}
|
|
|
|
const goToPreviousStep = () => {
|
|
const currentIndex = STEPS.findIndex(s => s.key === currentStep)
|
|
if (currentIndex > 0) {
|
|
setCurrentStep(STEPS[currentIndex - 1].key)
|
|
}
|
|
}
|
|
|
|
const handleSaveDraft = () => {
|
|
toast.success('Borrador guardado', {
|
|
description: 'La requisición se ha guardado como borrador.',
|
|
})
|
|
}
|
|
|
|
const handleSubmit = () => {
|
|
toast.success('Requisición enviada', {
|
|
description: 'La requisición ha sido enviada para aprobación de RR.HH.',
|
|
})
|
|
router.push('/requisiciones')
|
|
}
|
|
|
|
const selectedCountry = countries.find(c => c.id === formData.countryId)
|
|
const selectedCity = cities.find(c => c.id === formData.cityId)
|
|
const selectedDepartment = departments.find(d => d.id === formData.departmentId)
|
|
const selectedPosition = positions.find(p => p.id === formData.positionId)
|
|
|
|
return (
|
|
<div className="p-6 max-w-4xl mx-auto">
|
|
{/* Header */}
|
|
<div className="flex items-center gap-4 mb-6">
|
|
<Button variant="ghost" size="icon" asChild>
|
|
<Link href="/requisiciones">
|
|
<ArrowLeft className="h-4 w-4" />
|
|
</Link>
|
|
</Button>
|
|
<div>
|
|
<h1 className="text-2xl font-bold tracking-tight">Nueva Requisición</h1>
|
|
<p className="text-muted-foreground">
|
|
Completa el formulario para solicitar una nueva vacante
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Step Indicator */}
|
|
<StepIndicator currentStep={currentStep} />
|
|
|
|
{/* Form Card */}
|
|
<Card>
|
|
<CardContent className="p-6">
|
|
{/* Step 1: Ubicación */}
|
|
{currentStep === 'ubicacion' && (
|
|
<div className="space-y-6">
|
|
<div>
|
|
<h2 className="text-lg font-semibold mb-1">Ubicación</h2>
|
|
<p className="text-sm text-muted-foreground">
|
|
Define el país, ciudad y departamento de la vacante
|
|
</p>
|
|
</div>
|
|
|
|
<div className="grid gap-6 sm:grid-cols-2">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="country">País *</Label>
|
|
<Select value={formData.countryId} onValueChange={(v) => updateFormData('countryId', v)}>
|
|
<SelectTrigger className={cn(errors.countryId && 'border-destructive')}>
|
|
<SelectValue placeholder="Seleccionar país" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{countries.map((country) => (
|
|
<SelectItem key={country.id} value={country.id}>
|
|
{country.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
{errors.countryId && (
|
|
<p className="text-xs text-destructive flex items-center gap-1">
|
|
<AlertCircle className="h-3 w-3" />
|
|
{errors.countryId}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="city">Ciudad *</Label>
|
|
<Select
|
|
value={formData.cityId}
|
|
onValueChange={(v) => updateFormData('cityId', v)}
|
|
disabled={!formData.countryId}
|
|
>
|
|
<SelectTrigger className={cn(errors.cityId && 'border-destructive')}>
|
|
<SelectValue placeholder={formData.countryId ? "Seleccionar ciudad" : "Primero selecciona un país"} />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{availableCities.map((city) => (
|
|
<SelectItem key={city.id} value={city.id}>
|
|
{city.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
{errors.cityId && (
|
|
<p className="text-xs text-destructive flex items-center gap-1">
|
|
<AlertCircle className="h-3 w-3" />
|
|
{errors.cityId}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="space-y-2 sm:col-span-2">
|
|
<Label htmlFor="department">Departamento *</Label>
|
|
<Select value={formData.departmentId} onValueChange={(v) => updateFormData('departmentId', v)}>
|
|
<SelectTrigger className={cn(errors.departmentId && 'border-destructive')}>
|
|
<SelectValue placeholder="Seleccionar departamento" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{departments.map((dept) => (
|
|
<SelectItem key={dept.id} value={dept.id}>
|
|
{dept.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
{errors.departmentId && (
|
|
<p className="text-xs text-destructive flex items-center gap-1">
|
|
<AlertCircle className="h-3 w-3" />
|
|
{errors.departmentId}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Requester Info */}
|
|
<div className="rounded-lg bg-muted/50 p-4">
|
|
<p className="text-sm text-muted-foreground mb-2">Solicitante</p>
|
|
<p className="font-medium">{currentUser.name}</p>
|
|
<p className="text-sm text-muted-foreground">{currentUser.email}</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Step 2: Vacante */}
|
|
{currentStep === 'vacante' && (
|
|
<div className="space-y-6">
|
|
<div>
|
|
<h2 className="text-lg font-semibold mb-1">Información de la Vacante</h2>
|
|
<p className="text-sm text-muted-foreground">
|
|
Define la posición, cantidad y tipo de contratación
|
|
</p>
|
|
</div>
|
|
|
|
<div className="grid gap-6 sm:grid-cols-2">
|
|
<div className="space-y-2 sm:col-span-2">
|
|
<Label htmlFor="position">Posición *</Label>
|
|
<Select value={formData.positionId} onValueChange={(v) => updateFormData('positionId', v)}>
|
|
<SelectTrigger className={cn(errors.positionId && 'border-destructive')}>
|
|
<SelectValue placeholder="Seleccionar posición" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{positions.map((pos) => (
|
|
<SelectItem key={pos.id} value={pos.id}>
|
|
{pos.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
{errors.positionId && (
|
|
<p className="text-xs text-destructive flex items-center gap-1">
|
|
<AlertCircle className="h-3 w-3" />
|
|
{errors.positionId}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="quantity">Cantidad de Vacantes *</Label>
|
|
<Input
|
|
id="quantity"
|
|
type="number"
|
|
min={1}
|
|
value={formData.quantity}
|
|
onChange={(e) => updateFormData('quantity', parseInt(e.target.value) || 1)}
|
|
className={cn(errors.quantity && 'border-destructive')}
|
|
/>
|
|
{errors.quantity && (
|
|
<p className="text-xs text-destructive flex items-center gap-1">
|
|
<AlertCircle className="h-3 w-3" />
|
|
{errors.quantity}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label>Modalidad *</Label>
|
|
<RadioGroup
|
|
value={formData.modalidad}
|
|
onValueChange={(v) => updateFormData('modalidad', v)}
|
|
className="flex gap-4"
|
|
>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="presencial" id="presencial" />
|
|
<Label htmlFor="presencial" className="font-normal">Presencial</Label>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="hibrido" id="hibrido" />
|
|
<Label htmlFor="hibrido" className="font-normal">Híbrido</Label>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="remoto" id="remoto" />
|
|
<Label htmlFor="remoto" className="font-normal">Remoto</Label>
|
|
</div>
|
|
</RadioGroup>
|
|
</div>
|
|
|
|
<div className="space-y-2 sm:col-span-2">
|
|
<Label>Tipo de Contratación *</Label>
|
|
<RadioGroup
|
|
value={formData.tipoContratacion}
|
|
onValueChange={(v) => updateFormData('tipoContratacion', v)}
|
|
className="flex gap-4"
|
|
>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="nuevo" id="nuevo" />
|
|
<Label htmlFor="nuevo" className="font-normal">Nueva posición</Label>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="reemplazo" id="reemplazo" />
|
|
<Label htmlFor="reemplazo" className="font-normal">Reemplazo</Label>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="temporal" id="temporal" />
|
|
<Label htmlFor="temporal" className="font-normal">Temporal</Label>
|
|
</div>
|
|
</RadioGroup>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Step 3: Justificación */}
|
|
{currentStep === 'justificacion' && (
|
|
<div className="space-y-6">
|
|
<div>
|
|
<h2 className="text-lg font-semibold mb-1">Justificación</h2>
|
|
<p className="text-sm text-muted-foreground">
|
|
Explica el motivo de la requisición y su nivel de urgencia
|
|
</p>
|
|
</div>
|
|
|
|
<div className="space-y-6">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="justification">Justificación de la Requisición *</Label>
|
|
<Textarea
|
|
id="justification"
|
|
value={formData.justification}
|
|
onChange={(e) => updateFormData('justification', e.target.value)}
|
|
placeholder="Describe el motivo por el cual se necesita esta contratación, el impacto en el negocio y cualquier contexto relevante..."
|
|
rows={5}
|
|
className={cn(errors.justification && 'border-destructive')}
|
|
/>
|
|
<div className="flex items-center justify-between">
|
|
{errors.justification ? (
|
|
<p className="text-xs text-destructive flex items-center gap-1">
|
|
<AlertCircle className="h-3 w-3" />
|
|
{errors.justification}
|
|
</p>
|
|
) : (
|
|
<p className="text-xs text-muted-foreground">
|
|
Mínimo 20 caracteres
|
|
</p>
|
|
)}
|
|
<p className="text-xs text-muted-foreground">
|
|
{formData.justification.length} caracteres
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label>Nivel de Urgencia *</Label>
|
|
<RadioGroup
|
|
value={formData.urgency}
|
|
onValueChange={(v) => updateFormData('urgency', v)}
|
|
className="grid gap-3"
|
|
>
|
|
<div className="flex items-start space-x-3 rounded-lg border p-4 hover:bg-muted/50 transition-colors">
|
|
<RadioGroupItem value="normal" id="normal" className="mt-0.5" />
|
|
<div>
|
|
<Label htmlFor="normal" className="font-medium">Normal</Label>
|
|
<p className="text-sm text-muted-foreground">
|
|
Proceso estándar de reclutamiento según SLA definido
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-start space-x-3 rounded-lg border border-amber-200 bg-amber-50/50 p-4 dark:border-amber-800 dark:bg-amber-950/30">
|
|
<RadioGroupItem value="alta" id="alta" className="mt-0.5" />
|
|
<div>
|
|
<Label htmlFor="alta" className="font-medium">Alta</Label>
|
|
<p className="text-sm text-muted-foreground">
|
|
Requiere atención prioritaria, impacto operativo significativo
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-start space-x-3 rounded-lg border border-red-200 bg-red-50/50 p-4 dark:border-red-800 dark:bg-red-950/30">
|
|
<RadioGroupItem value="critica" id="critica" className="mt-0.5" />
|
|
<div>
|
|
<Label htmlFor="critica" className="font-medium">Crítica</Label>
|
|
<p className="text-sm text-muted-foreground">
|
|
Urgencia máxima, afecta operaciones críticas del negocio
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</RadioGroup>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Step 4: Compensación */}
|
|
{currentStep === 'compensacion' && (
|
|
<div className="space-y-6">
|
|
<div>
|
|
<h2 className="text-lg font-semibold mb-1">Compensación</h2>
|
|
<p className="text-sm text-muted-foreground">
|
|
Define el paquete salarial y presupuesto (opcional, visible solo para roles autorizados)
|
|
</p>
|
|
</div>
|
|
|
|
<div className="grid gap-6 sm:grid-cols-2">
|
|
<div className="space-y-2 sm:col-span-2">
|
|
<Label htmlFor="salaryPackage">Paquete Salarial</Label>
|
|
<Input
|
|
id="salaryPackage"
|
|
value={formData.salaryPackage}
|
|
onChange={(e) => updateFormData('salaryPackage', e.target.value)}
|
|
placeholder="Ej: $15,000 - $20,000 MXN mensual"
|
|
/>
|
|
<p className="text-xs text-muted-foreground">
|
|
Rango salarial propuesto para la posición
|
|
</p>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="budget">Presupuesto Mensual</Label>
|
|
<Input
|
|
id="budget"
|
|
type="number"
|
|
value={formData.budget}
|
|
onChange={(e) => updateFormData('budget', e.target.value)}
|
|
placeholder="0"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="currency">Moneda</Label>
|
|
<Select value={formData.currency} onValueChange={(v) => updateFormData('currency', v)}>
|
|
<SelectTrigger>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="USD">USD - Dólar Estadounidense</SelectItem>
|
|
<SelectItem value="MXN">MXN - Peso Mexicano</SelectItem>
|
|
<SelectItem value="COP">COP - Peso Colombiano</SelectItem>
|
|
<SelectItem value="PEN">PEN - Sol Peruano</SelectItem>
|
|
<SelectItem value="CLP">CLP - Peso Chileno</SelectItem>
|
|
<SelectItem value="ARS">ARS - Peso Argentino</SelectItem>
|
|
<SelectItem value="DOP">DOP - Peso Dominicano</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="rounded-lg bg-muted/50 p-4 text-sm text-muted-foreground">
|
|
<p className="font-medium text-foreground mb-1">Nota de privacidad</p>
|
|
<p>
|
|
La información de compensación solo será visible para roles autorizados (RR.HH. y Administración).
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Step 5: Revisión */}
|
|
{currentStep === 'revision' && (
|
|
<div className="space-y-6">
|
|
<div>
|
|
<h2 className="text-lg font-semibold mb-1">Revisión Final</h2>
|
|
<p className="text-sm text-muted-foreground">
|
|
Verifica que toda la información sea correcta antes de enviar
|
|
</p>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
{/* Location Summary */}
|
|
<div className="rounded-lg border p-4">
|
|
<div className="flex items-center justify-between mb-3">
|
|
<h3 className="font-medium flex items-center gap-2">
|
|
<MapPin className="h-4 w-4 text-primary" />
|
|
Ubicación
|
|
</h3>
|
|
<Button variant="ghost" size="sm" onClick={() => setCurrentStep('ubicacion')}>
|
|
Editar
|
|
</Button>
|
|
</div>
|
|
<div className="grid gap-2 text-sm">
|
|
<div className="flex justify-between">
|
|
<span className="text-muted-foreground">País:</span>
|
|
<span>{selectedCountry?.name || '-'}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-muted-foreground">Ciudad:</span>
|
|
<span>{selectedCity?.name || '-'}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-muted-foreground">Departamento:</span>
|
|
<span>{selectedDepartment?.name || '-'}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Vacancy Summary */}
|
|
<div className="rounded-lg border p-4">
|
|
<div className="flex items-center justify-between mb-3">
|
|
<h3 className="font-medium flex items-center gap-2">
|
|
<Briefcase className="h-4 w-4 text-primary" />
|
|
Vacante
|
|
</h3>
|
|
<Button variant="ghost" size="sm" onClick={() => setCurrentStep('vacante')}>
|
|
Editar
|
|
</Button>
|
|
</div>
|
|
<div className="grid gap-2 text-sm">
|
|
<div className="flex justify-between">
|
|
<span className="text-muted-foreground">Posición:</span>
|
|
<span>{selectedPosition?.name || '-'}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-muted-foreground">Cantidad:</span>
|
|
<span>{formData.quantity}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-muted-foreground">Modalidad:</span>
|
|
<span className="capitalize">{formData.modalidad}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-muted-foreground">Tipo:</span>
|
|
<span className="capitalize">{formData.tipoContratacion}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Justification Summary */}
|
|
<div className="rounded-lg border p-4">
|
|
<div className="flex items-center justify-between mb-3">
|
|
<h3 className="font-medium flex items-center gap-2">
|
|
<FileText className="h-4 w-4 text-primary" />
|
|
Justificación
|
|
</h3>
|
|
<Button variant="ghost" size="sm" onClick={() => setCurrentStep('justificacion')}>
|
|
Editar
|
|
</Button>
|
|
</div>
|
|
<div className="space-y-2 text-sm">
|
|
<div className="flex justify-between">
|
|
<span className="text-muted-foreground">Urgencia:</span>
|
|
<span className="capitalize">{formData.urgency}</span>
|
|
</div>
|
|
<p className="text-muted-foreground mt-2">{formData.justification || '-'}</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Compensation Summary */}
|
|
{(formData.salaryPackage || formData.budget) && (
|
|
<div className="rounded-lg border p-4">
|
|
<div className="flex items-center justify-between mb-3">
|
|
<h3 className="font-medium flex items-center gap-2">
|
|
<DollarSign className="h-4 w-4 text-primary" />
|
|
Compensación
|
|
</h3>
|
|
<Button variant="ghost" size="sm" onClick={() => setCurrentStep('compensacion')}>
|
|
Editar
|
|
</Button>
|
|
</div>
|
|
<div className="grid gap-2 text-sm">
|
|
{formData.salaryPackage && (
|
|
<div className="flex justify-between">
|
|
<span className="text-muted-foreground">Paquete:</span>
|
|
<span>{formData.salaryPackage}</span>
|
|
</div>
|
|
)}
|
|
{formData.budget && (
|
|
<div className="flex justify-between">
|
|
<span className="text-muted-foreground">Presupuesto:</span>
|
|
<span>{formData.currency} {parseInt(formData.budget).toLocaleString()}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Navigation Buttons */}
|
|
<Separator className="my-6" />
|
|
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
{currentStep !== 'ubicacion' && (
|
|
<Button variant="outline" onClick={goToPreviousStep}>
|
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
Anterior
|
|
</Button>
|
|
)}
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Button variant="outline" onClick={handleSaveDraft}>
|
|
<Save className="mr-2 h-4 w-4" />
|
|
Guardar Borrador
|
|
</Button>
|
|
{currentStep !== 'revision' ? (
|
|
<Button onClick={goToNextStep}>
|
|
Siguiente
|
|
<ArrowRight className="ml-2 h-4 w-4" />
|
|
</Button>
|
|
) : (
|
|
<Button onClick={handleSubmit}>
|
|
<Send className="mr-2 h-4 w-4" />
|
|
Enviar a Aprobación
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
)
|
|
}
|