refactor: update navigation links and search parameters across authenticated routes

This commit is contained in:
2025-08-29 16:51:22 -06:00
parent f8de39e6d1
commit 6c3dd54d5f
6 changed files with 41 additions and 26 deletions

View File

@@ -102,7 +102,7 @@ function Layout() {
</Sheet>
{/* Brand */}
<Link to={user.isAdmin ? "/dashboard" : "/planes"} className="hidden items-center gap-2 md:flex">
<Link to="/dashboard" className="hidden items-center gap-2 md:flex">
<span className="inline-flex h-8 w-25 items-center justify-center rounded-xl bg-primary/10 text-primary font-bold">
<img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS8C2SvZe281wTNc9werkudO9aJiX3dOZm9T3s5DAfS0OUOvDbgc_WC61U_esY8GE8bZoI&usqp=CAU" alt="" />
</span>
@@ -165,6 +165,7 @@ function Sidebar({ onNavigate }: { onNavigate?: () => void }) {
<Link
key={item.to}
to={item.to}
search={{ ...(item.to === '/planes' ? { plan: '' } : {}) }}
activeOptions={{ exact: true }}
activeProps={{ className: "bg-primary/10 text-foreground" }}
className="group inline-flex items-center gap-3 rounded-xl px-3 py-2 text-sm text-muted-foreground hover:bg-primary/10 hover:text-foreground"

View File

@@ -53,7 +53,7 @@ const planesKeys = {
all: () => [...planesKeys.root, 'all'] as const,
}
async function fetchPlanIdsByScope(search: Pick<SearchState, 'planId'|'carreraId'|'facultadId'>): Promise<string[] | null> {
async function fetchPlanIdsByScope(search: Pick<SearchState, 'planId' | 'carreraId' | 'facultadId'>): Promise<string[] | null> {
const { planId, carreraId, facultadId } = search
if (planId) return [planId]
if (carreraId) {
@@ -369,7 +369,7 @@ function RouteComponent() {
<Link
to="/planes"
className="hidden sm:inline-flex items-center gap-2 rounded-xl border px-3 py-2 text-sm hover:bg-neutral-50"
search={{ crear: 'asignatura' }}
search={{ plan: '' }}
>
<Icons.Plus className="w-4 h-4" /> Nueva asignatura
</Link>

View File

@@ -1,4 +1,4 @@
import { createFileRoute, Link, useRouter } from '@tanstack/react-router'
import { createFileRoute, Link, useNavigate, useRouter } from '@tanstack/react-router'
import { useSuspenseQuery, queryOptions, useQueryClient } from '@tanstack/react-query'
import { supabase, useSupabaseAuth } from '@/auth/supabase'
import * as Icons from 'lucide-react'
@@ -178,6 +178,8 @@ function RouteComponent() {
const isAdmin = !!auth.claims?.claims_admin
const role = auth.claims?.role as 'lci' | 'vicerrectoria' | 'secretario_academico' | 'jefe_carrera' | 'planeacion' | undefined
const navigate = useNavigate({ from: Route.fullPath })
// Mensaje contextual
const roleHint = useMemo(() => {
switch (role) {
@@ -227,7 +229,7 @@ function RouteComponent() {
if (e.key === 'Enter') {
const q = (e.target as HTMLInputElement).value.trim()
if (!q) return
router.navigate({ to: '/planes', search: { q } })
navigate({ to: '/planes', search: { plan: q } })
}
}}
/>
@@ -246,7 +248,7 @@ function RouteComponent() {
{/* Atajos rápidos (según rol) */}
<div className="flex flex-wrap gap-2">
<Link to="/planes" className="inline-flex items-center gap-2 rounded-xl bg-white/20 border border-white/30 px-3 py-1.5 text-sm hover:bg-white/30">
<Link to="/planes" search={{ plan: '' }} className="inline-flex items-center gap-2 rounded-xl bg-white/20 border border-white/30 px-3 py-1.5 text-sm hover:bg-white/30">
<Icons.ScrollText className="w-4 h-4" /> Nuevo plan
</Link>
<Link
@@ -267,10 +269,10 @@ function RouteComponent() {
{/* KPIs principales */}
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
<Tile to="/_authenticated/facultades" label="Facultades" value={kpis.facultades} Icon={Icons.Building2} />
<Tile to="/_authenticated/carreras" label="Carreras" value={kpis.carreras} Icon={Icons.GraduationCap} />
<Tile to="/_authenticated/planes" label="Planes de estudio" value={kpis.planes} Icon={Icons.ScrollText} />
<Tile to="/_authenticated/asignaturas" label="Asignaturas" value={kpis.asignaturas} Icon={Icons.BookOpen} />
<Tile to="/facultades" label="Facultades" value={kpis.facultades} Icon={Icons.Building2} />
<Tile to="/carreras" label="Carreras" value={kpis.carreras} Icon={Icons.GraduationCap} />
<Tile to="/planes" label="Planes de estudio" value={kpis.planes} Icon={Icons.ScrollText} />
<Tile to="/asignaturas" label="Asignaturas" value={kpis.asignaturas} Icon={Icons.BookOpen} />
</div>
{/* Calidad + Salud */}
@@ -344,8 +346,7 @@ function HealthRow({ label, value, to }: { label: string; value: number; to: str
return (
<Link
to={to}
className={`flex items-center justify-between rounded-xl px-4 py-3 ring-1 ${
warn ? 'ring-amber-300 bg-amber-50 text-amber-800 hover:bg-amber-100' : 'ring-neutral-200 hover:bg-neutral-50'
className={`flex items-center justify-between rounded-xl px-4 py-3 ring-1 ${warn ? 'ring-amber-300 bg-amber-50 text-amber-800 hover:bg-amber-100' : 'ring-neutral-200 hover:bg-neutral-50'
} transition-colors`}
>
<span className="text-sm">{label}</span>

View File

@@ -65,7 +65,7 @@ function RouteComponent() {
<div className="relative p-6 space-y-6">
<GradientMesh color={fac?.color} />
<nav className="relative text-sm text-neutral-500">
<Link to="/planes" className="hover:underline">Planes de estudio</Link>
<Link to="/planes" search={{ plan: '' }} className="hover:underline">Planes de estudio</Link>
<span className="mx-1">/</span>
<span className="text-primary">{plan.nombre}</span>
</nav>

View File

@@ -1,4 +1,4 @@
import { createFileRoute, useRouter, Link } from "@tanstack/react-router"
import { createFileRoute, useRouter, Link, useNavigate } from "@tanstack/react-router"
import { useMemo, useState } from "react"
import { supabase, useSupabaseAuth } from "@/auth/supabase"
import { Input } from "@/components/ui/input"
@@ -10,6 +10,8 @@ import { Plus, RefreshCcw, Building2 } from "lucide-react"
import { InfoChip } from "@/components/planes/InfoChip"
import { CreatePlanDialog } from "@/components/planes/CreatePlanDialog"
import { chipTint } from "@/components/planes/chipTint"
import { z } from 'zod'
export type PlanDeEstudios = {
@@ -23,6 +25,11 @@ type PlanRow = PlanDeEstudios & {
} | null
}
const planSearchSchema = z.object({
plan: z.string().nullable()
})
export const Route = createFileRoute("/_authenticated/planes")({
component: RouteComponent,
loader: async () => {
@@ -41,29 +48,31 @@ export const Route = createFileRoute("/_authenticated/planes")({
if (error) throw new Error(error.message)
return (data ?? []) as PlanRow[]
},
validateSearch: planSearchSchema,
})
function RouteComponent() {
const auth = useSupabaseAuth()
const [q, setQ] = useState("")
const { plan } = Route.useSearch()
const [openCreate, setOpenCreate] = useState(false)
const data = Route.useLoaderData() as PlanRow[]
const router = useRouter()
const navigate = useNavigate({ from: Route.fullPath })
const showFacultad = auth.claims?.role === "lci" || auth.claims?.role === "vicerrectoria"
const showCarrera = showFacultad || auth.claims?.role === "secretario_academico"
const filtered = useMemo(() => {
const term = q.trim().toLowerCase()
const term = plan?.trim().toLowerCase()
if (!term || !data) return data
return data.filter((p) =>
[p.nombre, p.nivel, p.estado, p.carreras?.nombre, p.carreras?.facultades?.nombre]
.filter(Boolean)
.some((v) => String(v).toLowerCase().includes(term))
)
}, [q, data])
}, [plan, data])
return (
<div className="space-y-4">
@@ -72,7 +81,11 @@ function RouteComponent() {
<CardTitle className="text-xl">Planes de estudio</CardTitle>
<div className="flex w-full items-center gap-2 md:w-auto">
<div className="relative w-full md:w-80">
<Input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Buscar por nombre, nivel, estado…" />
<Input
value={plan ?? ''}
onChange={e => navigate({ search: { plan: e.target.value } })}
placeholder="Buscar por nombre, nivel, estado…"
/>
</div>
<Button variant="outline" size="icon" onClick={() => router.invalidate()} title="Recargar">
<RefreshCcw className="h-4 w-4" />

View File

@@ -15,7 +15,7 @@ function App() {
<header className="flex items-center justify-between px-10 py-6 border-b border-slate-700/50">
<h1 className="text-2xl font-mono tracking-tight">Acad-IA</h1>
{isAuth ? (
<Link to="/planes">
<Link to="/dashboard">
<Button className="border-slate-500 hover:bg-slate-700/50 relative overflow-hidden">
<span className="-z-1 absolute inset-0 animate-aurora" />
@@ -23,7 +23,7 @@ function App() {
</Button>
</Link>
) : (
<Link to="/login" search={{ redirect: '/planes' }}>
<Link to="/login" search={{ redirect: '/dashboard' }}>
<Button variant="outline" className="border-slate-500 hover:bg-slate-700/50">
Iniciar sesión
</Button>