wip
This commit is contained in:
@@ -8,18 +8,24 @@ import {
|
||||
} from 'lucide-react'
|
||||
import { useState, useEffect, forwardRef } from 'react'
|
||||
|
||||
import type { Database } from '@/types/supabase'
|
||||
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { NotFoundPage } from '@/components/ui/NotFoundPage'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { plans_get } from '@/data/api/plans.api'
|
||||
import { usePlan, useUpdatePlanFields } from '@/data/hooks/usePlans'
|
||||
import { qk } from '@/data/query/keys'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
type NivelPlanEstudio = Database['public']['Enums']['nivel_plan_estudio']
|
||||
|
||||
export const Route = createFileRoute('/planes/$planId/_detalle')({
|
||||
loader: async ({ context: { queryClient }, params: { planId } }) => {
|
||||
@@ -54,28 +60,26 @@ function RouteComponent() {
|
||||
|
||||
// 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)
|
||||
const [nivelPlan, setNivelPlan] = useState<NivelPlanEstudio | undefined>(
|
||||
undefined,
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setNombrePlan(data.nombre || '')
|
||||
setNivelPlan(data.nivel || '')
|
||||
setNivelPlan(data.nivel)
|
||||
}
|
||||
}, [data])
|
||||
|
||||
const niveles = [
|
||||
const niveles: Array<NivelPlanEstudio> = [
|
||||
'Licenciatura',
|
||||
'Maestría',
|
||||
'Doctorado',
|
||||
'Diplomado',
|
||||
'Especialidad',
|
||||
'Diplomado',
|
||||
'Otro',
|
||||
]
|
||||
|
||||
const persistChange = (patch: any) => {
|
||||
mutate({ planId, patch })
|
||||
}
|
||||
|
||||
const MAX_CHARACTERS = 200
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLSpanElement>) => {
|
||||
@@ -148,18 +152,18 @@ function RouteComponent() {
|
||||
contentEditable
|
||||
suppressContentEditableWarning
|
||||
spellCheck={false}
|
||||
aria-label="Nombre del plan"
|
||||
title="Nombre del plan"
|
||||
onKeyDown={handleKeyDown}
|
||||
onPaste={handlePaste}
|
||||
onBlur={(e) => {
|
||||
const nuevoNombre =
|
||||
e.currentTarget.textContent?.trim() || ''
|
||||
const nuevoNombre = e.currentTarget.textContent.trim()
|
||||
setNombrePlan(nuevoNombre)
|
||||
if (nuevoNombre !== data?.nombre) {
|
||||
mutate({ planId, patch: { nombre: nuevoNombre } })
|
||||
}
|
||||
}}
|
||||
className="hover:border-input focus:border-primary block w-full cursor-text border-b border-transparent break-words whitespace-pre-wrap transition-colors outline-none select-text sm:inline-block sm:w-auto"
|
||||
style={{ textDecoration: 'none' }}
|
||||
className="hover:border-input focus:border-primary block w-full cursor-text border-b border-transparent wrap-break-word whitespace-pre-wrap no-underline transition-colors outline-none select-text sm:inline-block sm:w-auto"
|
||||
>
|
||||
{nombrePlan}
|
||||
</span>
|
||||
@@ -170,42 +174,68 @@ function RouteComponent() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Badge className="border-primary/20 bg-primary/10 text-primary hover:bg-primary/20 gap-1 px-3">
|
||||
{data?.estados_plan?.etiqueta}
|
||||
</Badge>
|
||||
</div>
|
||||
{(() => {
|
||||
const estadoColorHex = (data?.estados_plan as any)?.color as
|
||||
| string
|
||||
| undefined
|
||||
const badgeStyle = estadoColorHex
|
||||
? ({
|
||||
backgroundColor: estadoColorHex,
|
||||
borderColor: estadoColorHex,
|
||||
} as const)
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<Badge
|
||||
style={badgeStyle}
|
||||
className={cn(
|
||||
'text-sm font-semibold',
|
||||
!estadoColorHex &&
|
||||
'border-primary/20 bg-primary/10 text-primary hover:bg-primary/20',
|
||||
)}
|
||||
>
|
||||
<span className="text-white [text-shadow:1px_1px_0_#000,-1px_-1px_0_#000,1px_-1px_0_#000,-1px_1px_0_#000,0_1px_0_#000,0_-1px_0_#000,1px_0_0_#000,-1px_0_0_#000]">
|
||||
{data?.estados_plan?.etiqueta}
|
||||
</span>
|
||||
</Badge>
|
||||
)
|
||||
})()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 3. Cards de Información */}
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-4">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<InfoCard
|
||||
icon={<GraduationCap className="text-muted-foreground" />}
|
||||
label="Nivel"
|
||||
<div className="border-border/60 bg-muted/30 flex h-18 w-full items-center gap-4 rounded-xl border p-4 shadow-sm transition-all">
|
||||
<div className="bg-background flex h-10 w-10 shrink-0 items-center justify-center rounded-lg border shadow-sm">
|
||||
<GraduationCap className="text-muted-foreground" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-muted-foreground mb-0.5 truncate text-[10px] font-bold tracking-wider uppercase">
|
||||
Nivel
|
||||
</p>
|
||||
<Select
|
||||
value={nivelPlan}
|
||||
isEditable
|
||||
/>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent className="w-48">
|
||||
{niveles.map((n) => (
|
||||
<DropdownMenuItem
|
||||
key={n}
|
||||
onClick={() => {
|
||||
setNivelPlan(n)
|
||||
if (n !== data?.nivel) {
|
||||
mutate({ planId, patch: { nivel: n } })
|
||||
}
|
||||
}}
|
||||
>
|
||||
{n}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
onValueChange={(value) => {
|
||||
const nuevoNivel = value as NivelPlanEstudio
|
||||
setNivelPlan(nuevoNivel)
|
||||
if (nuevoNivel !== data?.nivel) {
|
||||
mutate({ planId, patch: { nivel: nuevoNivel } })
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-full" size="sm">
|
||||
<SelectValue placeholder="---" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{niveles.map((n) => (
|
||||
<SelectItem key={n} value={n}>
|
||||
{n}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<InfoCard
|
||||
icon={<Clock className="text-muted-foreground" />}
|
||||
@@ -220,7 +250,7 @@ function RouteComponent() {
|
||||
<InfoCard
|
||||
icon={<CalendarDays className="text-muted-foreground" />}
|
||||
label="Creación"
|
||||
value={data?.creado_en?.split('T')[0]}
|
||||
value={data?.creado_en.split('T')[0]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user