import { createFileRoute, Outlet, Link, notFound } from '@tanstack/react-router'
import {
ChevronLeft,
GraduationCap,
Clock,
Hash,
CalendarDays,
Save,
} from 'lucide-react'
import { useState, useEffect, forwardRef } from 'react'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { NotFoundPage } from '@/components/ui/NotFoundPage'
import { Skeleton } from '@/components/ui/skeleton'
import { plans_get } from '@/data/api/plans.api'
import { usePlan } from '@/data/hooks/usePlans'
import { qk } from '@/data/query/keys'
export const Route = createFileRoute('/planes/$planId/_detalle')({
loader: async ({ context: { queryClient }, params: { planId } }) => {
try {
console.log('loader')
await queryClient.ensureQueryData({
queryKey: qk.plan(planId),
queryFn: () => plans_get(planId),
})
} catch (e: any) {
// PGRST116: The result contains 0 rows
if (e?.code === 'PGRST116') {
throw notFound()
}
throw e
}
},
notFoundComponent: () => {
return (
)
},
component: RouteComponent,
})
function RouteComponent() {
const { planId } = Route.useParams()
const { data, isLoading } = usePlan(planId)
// Estados locales para manejar la edición "en vivo" antes de persistir
const [nombrePlan, setNombrePlan] = useState('')
const [nivelPlan, setNivelPlan] = useState('')
const [isDirty, setIsDirty] = useState(false)
useEffect(() => {
if (data) {
setNombrePlan(data.nombre || '')
setNivelPlan(data.nivel || '')
}
}, [data])
const niveles = [
'Licenciatura',
'Maestría',
'Doctorado',
'Diplomado',
'Especialidad',
]
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
e.preventDefault() // Evita el salto de línea
e.currentTarget.blur() // Quita el foco, lo que dispara el onBlur y "guarda" en el estado
}
}
const handleSave = () => {
console.log('Guardando en DB...', { nombrePlan, nivelPlan })
// Aquí iría tu mutation
setIsDirty(false)
}
return (
{/* Botón Flotante de Guardar */}
{isDirty && (
)}
{/* 1. Header Superior */}
{/* Header del Plan */}
{isLoading ? (
/* ===== SKELETON ===== */
{Array.from({ length: 6 }).map((_, i) => (
))}
) : (
<>
{nivelPlan} en
setNombrePlan(e.currentTarget.textContent || '')
}
className="cursor-text border-b border-transparent decoration-transparent transition-colors outline-none select-text hover:border-slate-300 focus:border-teal-500"
style={{
WebkitTextDecoration: 'none',
textDecoration: 'none',
}} // Doble seguridad contra subrayados
>
{nombrePlan}
{data?.carreras?.facultades?.nombre}{' '}
{data?.carreras?.nombre_corto}
{/*
{data?.estados_plan?.etiqueta}
*/}
{data?.estados_plan?.etiqueta}
>
)}
{/* 3. Cards de Información con Context Menu */}
}
label="Nivel"
value={nivelPlan}
isEditable
/>
{niveles.map((n) => (
{
setNivelPlan(n)
setIsDirty(true)
}}
>
{n}
))}
}
label="Duración"
value={`${data?.numero_ciclos || 0} Ciclos`}
/>
}
label="Créditos"
value="320"
/>
}
label="Creación"
value={data?.creado_en?.split('T')[0]} // Cortamos la fecha para que no sea tan larga
/>
{/* 4. Navegación de Tabs */}
)
}
const InfoCard = forwardRef<
HTMLDivElement,
{
icon: React.ReactNode
label: string
value: string | number | undefined
isEditable?: boolean
} & React.HTMLAttributes
>(function InfoCard(
{ icon, label, value, isEditable, className, ...props },
ref,
) {
return (
)
})
function Tab({
to,
params,
children,
}: {
to: string
params?: any
children: React.ReactNode
}) {
return (
{children}
)
}
function DatosGeneralesSkeleton() {
return (
{/* Header */}
{/* Content */}
)
}