Initial commit: Proyecto portal-requisicion-vacantes
This commit is contained in:
@@ -0,0 +1,705 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user