Fallback elegante de vista no encontrada
close #44: Se creó la NotFoundPage y se utiliza en __root con el notFoundComponent. Se agregó la lógica del loader tanto de plan de estudios como de asignaturas. Se agregó el NotFoundComponent para el detalle de plan de estudios y el de asignaturas
This commit is contained in:
@@ -204,7 +204,7 @@ function DatosGeneralesPage() {
|
||||
<Textarea
|
||||
value={editValue}
|
||||
onChange={(e) => setEditValue(e.target.value)}
|
||||
className="min-h-[120px]"
|
||||
className="min-h-30"
|
||||
placeholder={campo.holder}
|
||||
/>
|
||||
<div className="flex justify-end gap-2">
|
||||
@@ -225,7 +225,7 @@ function DatosGeneralesPage() {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="min-h-[100px]">
|
||||
<div className="min-h-25">
|
||||
{campo.value ? (
|
||||
<div className="text-sm leading-relaxed text-slate-600">
|
||||
{campo.tipo === 'lista' ? (
|
||||
|
||||
@@ -139,7 +139,7 @@ function MateriasPage() {
|
||||
|
||||
{/* Barra de Filtros Avanzada */}
|
||||
<div className="flex flex-wrap items-center gap-3 rounded-xl border bg-slate-50 p-4">
|
||||
<div className="relative min-w-[240px] flex-1">
|
||||
<div className="relative min-w-60 flex-1">
|
||||
<Search className="text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2" />
|
||||
<Input
|
||||
placeholder="Buscar por nombre o clave..."
|
||||
@@ -153,7 +153,7 @@ function MateriasPage() {
|
||||
<Filter className="text-muted-foreground mr-1 h-4 w-4" />
|
||||
|
||||
<Select value={filterTipo} onValueChange={setFilterTipo}>
|
||||
<SelectTrigger className="w-[140px] bg-white">
|
||||
<SelectTrigger className="w-35 bg-white">
|
||||
<SelectValue placeholder="Tipo" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -164,7 +164,7 @@ function MateriasPage() {
|
||||
</Select>
|
||||
|
||||
<Select value={filterEstado} onValueChange={setFilterEstado}>
|
||||
<SelectTrigger className="w-[140px] bg-white">
|
||||
<SelectTrigger className="w-35 bg-white">
|
||||
<SelectValue placeholder="Estado" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -176,7 +176,7 @@ function MateriasPage() {
|
||||
</Select>
|
||||
|
||||
<Select value={filterLinea} onValueChange={setFilterLinea}>
|
||||
<SelectTrigger className="w-[180px] bg-white">
|
||||
<SelectTrigger className="w-45 bg-white">
|
||||
<SelectValue placeholder="Línea" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -196,14 +196,14 @@ function MateriasPage() {
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="bg-slate-50/50">
|
||||
<TableHead className="w-[120px]">Clave</TableHead>
|
||||
<TableHead className="w-30">Clave</TableHead>
|
||||
<TableHead>Nombre</TableHead>
|
||||
<TableHead className="text-center">Créditos</TableHead>
|
||||
<TableHead className="text-center">Ciclo</TableHead>
|
||||
<TableHead>Línea Curricular</TableHead>
|
||||
<TableHead>Tipo</TableHead>
|
||||
<TableHead>Estado</TableHead>
|
||||
<TableHead className="w-[50px]"></TableHead>
|
||||
<TableHead className="w-12.5"></TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createFileRoute, Outlet, Link } from '@tanstack/react-router'
|
||||
import { createFileRoute, Outlet, Link, notFound } from '@tanstack/react-router'
|
||||
import {
|
||||
ChevronLeft,
|
||||
GraduationCap,
|
||||
@@ -17,10 +17,37 @@ import {
|
||||
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 (
|
||||
<NotFoundPage
|
||||
title="Plan de Estudios no encontrado"
|
||||
message="El plan de estudios que intentas consultar no existe o no tienes permisos para verlo."
|
||||
/>
|
||||
)
|
||||
},
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
@@ -86,11 +113,11 @@ function RouteComponent() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto max-w-[1600px] space-y-8 p-8">
|
||||
<div className="mx-auto max-w-400 space-y-8 p-8">
|
||||
{/* Header del Plan */}
|
||||
{isLoading ? (
|
||||
/* ===== SKELETON ===== */
|
||||
<div className="mx-auto max-w-[1600px] p-8">
|
||||
<div className="mx-auto max-w-400 p-8">
|
||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
{Array.from({ length: 6 }).map((_, i) => (
|
||||
<DatosGeneralesSkeleton key={i} />
|
||||
@@ -237,7 +264,7 @@ const InfoCard = forwardRef<
|
||||
<div
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={`flex h-[72px] w-full items-center gap-4 rounded-xl border border-slate-200/60 bg-slate-50/50 p-4 shadow-sm transition-all ${
|
||||
className={`flex h-18 w-full items-center gap-4 rounded-xl border border-slate-200/60 bg-slate-50/50 p-4 shadow-sm transition-all ${
|
||||
isEditable
|
||||
? 'cursor-pointer hover:border-teal-200 hover:bg-white focus:outline-none focus-visible:ring-2 focus-visible:ring-teal-500/40'
|
||||
: ''
|
||||
|
||||
@@ -1,6 +1,31 @@
|
||||
import { createFileRoute, Outlet } from '@tanstack/react-router'
|
||||
import { createFileRoute, Outlet, notFound } from '@tanstack/react-router'
|
||||
|
||||
import { NotFoundPage } from '@/components/ui/NotFoundPage'
|
||||
import { plans_get } from '@/data/api/plans.api'
|
||||
import { qk } from '@/data/query/keys'
|
||||
|
||||
export const Route = createFileRoute('/planes/$planId/asignaturas')({
|
||||
loader: async ({ context: { queryClient }, params: { planId } }) => {
|
||||
try {
|
||||
await queryClient.ensureQueryData({
|
||||
queryKey: qk.plan(planId),
|
||||
queryFn: () => plans_get(planId),
|
||||
})
|
||||
} catch (e: any) {
|
||||
if (e?.code === 'PGRST116') {
|
||||
throw notFound()
|
||||
}
|
||||
throw e
|
||||
}
|
||||
},
|
||||
notFoundComponent: () => {
|
||||
return (
|
||||
<NotFoundPage
|
||||
title="Plan de Estudios no encontrado"
|
||||
message="El plan de estudios que intentas consultar no existe o no tienes permisos para verlo."
|
||||
/>
|
||||
)
|
||||
},
|
||||
component: AsignaturasLayout,
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user