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" import { Button } from "@/components/ui/button" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import * as Icons from "lucide-react" 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" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" export type PlanDeEstudios = { id: string nombre: string nivel: string | null duracion: string | null total_creditos: number | null estado: string | null fecha_creacion: string | null carrera_id: string | null } type PlanRow = PlanDeEstudios & { carreras: { id: string nombre: string facultades?: { id: string nombre: string color?: string | null icon?: string | null } | null } | null } const planSearchSchema = z.object({ plan: z.string().nullable(), facultad: z.string().nullable().optional(), carrera: z.string().nullable().optional(), }) export const Route = createFileRoute("/_authenticated/planes")({ component: RouteComponent, loader: async () => { const { data, error } = await supabase .from("plan_estudios") .select(` *, carreras ( id, nombre, facultades:facultades ( id, nombre, color, icon ) ) `) .order("fecha_creacion", { ascending: false }) .limit(100) if (error) throw new Error(error.message) return (data ?? []) as PlanRow[] }, validateSearch: planSearchSchema, }) function RouteComponent() { const auth = useSupabaseAuth() const { plan, facultad, carrera } = 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" // 🟣 Lista única de facultades const facultadesList = useMemo(() => { const unique = new Map() data?.forEach((p) => { const fac = p.carreras?.facultades if (fac?.id && fac?.nombre) unique.set(fac.id, fac.nombre) }) return Array.from(unique.entries()) }, [data]) // 🎓 Lista de carreras según facultad seleccionada const carrerasList = useMemo(() => { const unique = new Map() data?.forEach((p) => { if ( p.carreras?.id && p.carreras?.nombre && (!facultad || p.carreras?.facultades?.id === facultad) ) { unique.set(p.carreras.id, p.carreras.nombre) } }) return Array.from(unique.entries()) }, [data, facultad]) // 🧩 Filtrado general const filtered = useMemo(() => { const term = plan?.trim().toLowerCase() let results = data ?? [] if (term) { results = results.filter((p) => [p.nombre, p.nivel, p.estado, p.carreras?.nombre, p.carreras?.facultades?.nombre] .filter(Boolean) .some((v) => String(v).toLowerCase().includes(term)) ) } if (facultad && facultad !== "todas") { results = results.filter((p) => p.carreras?.facultades?.id === facultad) } if (carrera && carrera !== "todas") { results = results.filter((p) => p.carreras?.id === carrera) } return results }, [plan, facultad, carrera, data]) return (
Planes de estudio
{/* 🔍 Buscador */}
navigate({ search: { plan: e.target.value, facultad, carrera } }) } placeholder="Buscar por nombre, nivel, estado…" />
{/* 🏛️ Filtro por facultad */} {/* 🎓 Filtro por carrera (según facultad) */} {facultad && facultad !== "todas" && ( )} {/* 🔁 Recargar */} {/* ➕ Nuevo plan */}
{/* GRID de tarjetas */}
{filtered?.map((p) => { const fac = p.carreras?.facultades const styles = chipTint(fac?.color) const IconComp = (fac?.icon && (Icons as any)[fac.icon]) || Building2 return (
{p.nombre}
{p.nivel ?? "—"}{" "} {p.duracion ? `· ${p.duracion}` : ""}
{showCarrera && p.carreras?.nombre && ( } label={p.carreras.nombre} /> )} {showFacultad && fac?.nombre && ( } label={fac.nombre} tint={fac.color} /> )}
{p.estado && ( {p.estado.length > 10 ? `${p.estado.slice(0, 10)}…` : p.estado} )}
) })}
{!filtered?.length && (
Sin resultados
)}
) }