Primera versión adecuada del listado de planes
This commit is contained in:
@@ -17,6 +17,11 @@ import {
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/components/ui/popover'
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export type Option = { value: string; label: string }
|
||||
@@ -28,6 +33,7 @@ type Props = {
|
||||
placeholder?: string
|
||||
className?: string
|
||||
ariaLabel?: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const Filtro: React.FC<Props> = ({
|
||||
@@ -37,6 +43,7 @@ const Filtro: React.FC<Props> = ({
|
||||
placeholder = 'Seleccionar…',
|
||||
className,
|
||||
ariaLabel,
|
||||
disabled,
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
@@ -46,18 +53,26 @@ const Filtro: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className={cn('w-full justify-between', className)}
|
||||
aria-label={ariaLabel ?? 'Filtro combobox'}
|
||||
>
|
||||
{label}
|
||||
<ChevronDown className="opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className={cn('w-full min-w-0 justify-between', className)}
|
||||
aria-label={ariaLabel ?? 'Filtro combobox'}
|
||||
disabled={disabled}
|
||||
>
|
||||
<span className="truncate">{label}</span>
|
||||
<ChevronDown className="shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{label}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<PopoverContent className="p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder="Buscar…" className="h-9" />
|
||||
|
||||
59
src/components/ui/tooltip.tsx
Normal file
59
src/components/ui/tooltip.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import * as React from "react"
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function TooltipProvider({
|
||||
delayDuration = 0,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
||||
return (
|
||||
<TooltipPrimitive.Provider
|
||||
data-slot="tooltip-provider"
|
||||
delayDuration={delayDuration}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function Tooltip({
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function TooltipTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
||||
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
||||
}
|
||||
|
||||
function TooltipContent({
|
||||
className,
|
||||
sideOffset = 0,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
||||
return (
|
||||
<TooltipPrimitive.Portal>
|
||||
<TooltipPrimitive.Content
|
||||
data-slot="tooltip-content"
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<TooltipPrimitive.Arrow className="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
|
||||
</TooltipPrimitive.Content>
|
||||
</TooltipPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
||||
@@ -1,16 +1,5 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import {
|
||||
Activity,
|
||||
BookOpenText,
|
||||
Calculator,
|
||||
FlaskConical,
|
||||
Laptop,
|
||||
PencilRuler,
|
||||
Plus,
|
||||
Scale,
|
||||
Stethoscope,
|
||||
X,
|
||||
} from 'lucide-react'
|
||||
import * as Icons from 'lucide-react'
|
||||
import { useMemo, useState } from 'react'
|
||||
|
||||
import type { Option } from '@/components/planes/Filtro'
|
||||
@@ -28,7 +17,7 @@ function RouteComponent() {
|
||||
type Carrera = { id: string; nombre: string; facultadId: string }
|
||||
type Plan = {
|
||||
id: string
|
||||
Icono: any
|
||||
icon: string
|
||||
nombrePrograma: string
|
||||
nivel: string
|
||||
ciclos: string
|
||||
@@ -88,7 +77,7 @@ function RouteComponent() {
|
||||
const planes: Array<Plan> = [
|
||||
{
|
||||
id: 'p1',
|
||||
Icono: Laptop,
|
||||
icon: 'Laptop',
|
||||
nombrePrograma: 'Ingeniería en Sistemas Computacionales',
|
||||
nivel: 'Licenciatura',
|
||||
ciclos: '8 semestres',
|
||||
@@ -99,7 +88,7 @@ function RouteComponent() {
|
||||
},
|
||||
{
|
||||
id: 'p2',
|
||||
Icono: Stethoscope,
|
||||
icon: 'Stethoscope',
|
||||
nombrePrograma: 'Médico Cirujano',
|
||||
nivel: 'Licenciatura',
|
||||
ciclos: '10 semestres',
|
||||
@@ -110,7 +99,7 @@ function RouteComponent() {
|
||||
},
|
||||
{
|
||||
id: 'p3',
|
||||
Icono: Calculator,
|
||||
icon: 'Calculator',
|
||||
nombrePrograma: 'Licenciatura en Actuaría',
|
||||
nivel: 'Licenciatura',
|
||||
ciclos: '9 semestres',
|
||||
@@ -121,7 +110,7 @@ function RouteComponent() {
|
||||
},
|
||||
{
|
||||
id: 'p4',
|
||||
Icono: PencilRuler,
|
||||
icon: 'PencilRuler',
|
||||
nombrePrograma: 'Licenciatura en Arquitectura',
|
||||
nivel: 'Licenciatura',
|
||||
ciclos: '10 semestres',
|
||||
@@ -132,7 +121,7 @@ function RouteComponent() {
|
||||
},
|
||||
{
|
||||
id: 'p5',
|
||||
Icono: Activity,
|
||||
icon: 'Activity',
|
||||
nombrePrograma: 'Licenciatura en Fisioterapia',
|
||||
nivel: 'Licenciatura',
|
||||
ciclos: '8 semestres',
|
||||
@@ -143,7 +132,7 @@ function RouteComponent() {
|
||||
},
|
||||
{
|
||||
id: 'p6',
|
||||
Icono: Scale,
|
||||
icon: 'Scale',
|
||||
nombrePrograma: 'Licenciatura en Derecho',
|
||||
nivel: 'Licenciatura',
|
||||
ciclos: '10 semestres',
|
||||
@@ -154,7 +143,7 @@ function RouteComponent() {
|
||||
},
|
||||
{
|
||||
id: 'p7',
|
||||
Icono: FlaskConical,
|
||||
icon: 'FlaskConical',
|
||||
nombrePrograma: 'Químico Farmacéutico Biólogo',
|
||||
nivel: 'Licenciatura',
|
||||
ciclos: '9 semestres',
|
||||
@@ -221,7 +210,7 @@ function RouteComponent() {
|
||||
<div className="flex flex-col items-stretch justify-between gap-4 sm:flex-row sm:items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="bg-primary/10 text-primary flex h-10 w-10 items-center justify-center rounded-xl">
|
||||
<BookOpenText className="h-5 w-5" strokeWidth={2} />
|
||||
<Icons.BookOpenText className="h-5 w-5" strokeWidth={2} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -242,7 +231,7 @@ function RouteComponent() {
|
||||
aria-label="Nuevo plan de estudios"
|
||||
title="Nuevo plan de estudios"
|
||||
>
|
||||
<Plus className="" />
|
||||
<Icons.Plus className="" />
|
||||
Nuevo plan de estudios
|
||||
</button>
|
||||
</div>
|
||||
@@ -276,6 +265,7 @@ function RouteComponent() {
|
||||
onChange={setCarreraSel}
|
||||
placeholder="Carrera"
|
||||
ariaLabel="Filtro por carrera"
|
||||
disabled={facultadSel === 'todas'}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full lg:w-44">
|
||||
@@ -297,7 +287,7 @@ function RouteComponent() {
|
||||
title="Reiniciar filtros"
|
||||
aria-label="Reiniciar filtros"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
<Icons.X className="h-4 w-4" />
|
||||
Limpiar
|
||||
</button>
|
||||
</div>
|
||||
@@ -305,10 +295,11 @@ function RouteComponent() {
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{filteredPlans.map((p) => {
|
||||
const fac = facultades.find((f) => f.id === p.facultadId)!
|
||||
const IconComp = (Icons as any)[p.icon] ?? Icons.BookOpenText
|
||||
return (
|
||||
<PlanEstudiosCard
|
||||
key={p.id}
|
||||
Icono={p.Icono}
|
||||
Icono={IconComp}
|
||||
nombrePrograma={p.nombrePrograma}
|
||||
nivel={p.nivel}
|
||||
ciclos={p.ciclos}
|
||||
|
||||
Reference in New Issue
Block a user