From bec6405c547637126043e7f82aba3f0a2f2de890 Mon Sep 17 00:00:00 2001 From: "Roberto.silva" Date: Wed, 29 Oct 2025 14:44:47 -0600 Subject: [PATCH] Se agrgan filtros --- src/routes/_authenticated/planes.tsx | 205 +++++++++++++++++++++------ 1 file changed, 161 insertions(+), 44 deletions(-) diff --git a/src/routes/_authenticated/planes.tsx b/src/routes/_authenticated/planes.tsx index 1073570..ef3526e 100644 --- a/src/routes/_authenticated/planes.tsx +++ b/src/routes/_authenticated/planes.tsx @@ -10,26 +10,44 @@ 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 { 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 + 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 + 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() + plan: z.string().nullable(), + facultad: z.string().nullable().optional(), + carrera: z.string().nullable().optional(), }) - export const Route = createFileRoute("/_authenticated/planes")({ component: RouteComponent, loader: async () => { @@ -45,93 +63,191 @@ export const Route = createFileRoute("/_authenticated/planes")({ `) .order("fecha_creacion", { ascending: false }) .limit(100) - console.log({ data, error }) if (error) throw new Error(error.message) return (data ?? []) as PlanRow[] }, validateSearch: planSearchSchema, }) - function RouteComponent() { const auth = useSupabaseAuth() - const { plan } = Route.useSearch() + 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" - 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() - 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)) - ) - }, [plan, data]) + 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 } })} + value={plan ?? ""} + onChange={(e) => + navigate({ search: { plan: e.target.value, facultad, carrera } }) + } placeholder="Buscar por nombre, nivel, estado…" />
- + + {/* ➕ Nuevo plan */} -
- {/* GRID de tarjetas con estilo suave por facultad */} + {/* 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 + const IconComp = + (fac?.icon && (Icons as any)[fac.icon]) || Building2 return (
- +
{p.nombre}
- {p.nivel ?? "—"} {p.duracion ? `· ${p.duracion}` : ""} + {p.nivel ?? "—"}{" "} + {p.duracion ? `· ${p.duracion}` : ""}
- {/* Dentro del map de tarjetas, sustituye SOLO el footer inferior */}
- {/* grupo izquierdo: chips (wrap si no caben) */}
{showCarrera && p.carreras?.nombre && ( - {/* derecha: estado */} {p.estado && ( - {p.estado && p.estado.length > 10 ? `${p.estado.slice(0, 10)}…` : p.estado} + {p.estado.length > 10 + ? `${p.estado.slice(0, 10)}…` + : p.estado} )}
-
) @@ -167,16 +286,14 @@ function RouteComponent() {
{!filtered?.length && ( -
Sin resultados
+
+ Sin resultados +
)} - - +
) }