feat: Implement faculty management routes and UI components
- Added a new route for managing faculties with a grid display of faculties. - Created a detailed view for each faculty including metrics and recent activities. - Introduced a new loader for fetching faculty data and associated plans and subjects. - Enhanced the existing plans route to include a modal for plan details. - Updated the login and index pages with improved UI and styling. - Integrated a progress ring component to visualize the quality of plans. - Applied a new font style across the application for consistency.
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import { createFileRoute, Link } from '@tanstack/react-router'
|
||||
import * as Icons from 'lucide-react'
|
||||
import { supabase } from '@/auth/supabase'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
type Facultad = {
|
||||
id: string
|
||||
nombre: string
|
||||
icon: string
|
||||
color?: string | null
|
||||
}
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/facultades')({
|
||||
component: RouteComponent,
|
||||
loader: async () => {
|
||||
const { data, error } = await supabase
|
||||
.from('facultades')
|
||||
.select('id, nombre, icon, color')
|
||||
.order('nombre')
|
||||
|
||||
if (error) {
|
||||
console.error(error)
|
||||
return { facultades: [] as Facultad[] }
|
||||
}
|
||||
return { facultades: (data ?? []) as Facultad[] }
|
||||
},
|
||||
})
|
||||
|
||||
function gradientFrom(color?: string | null) {
|
||||
const base = (color && /^#([0-9a-f]{6}|[0-9a-f]{3})$/i.test(color)) ? color : '#2563eb' // azul por defecto
|
||||
// degradado elegante con transparencia
|
||||
return `linear-gradient(135deg, ${base} 0%, ${base}CC 40%, ${base}99 70%, ${base}66 100%)`
|
||||
}
|
||||
|
||||
function RouteComponent() {
|
||||
const { facultades } = Route.useLoaderData() as { facultades: Facultad[] }
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{facultades.map((fac) => {
|
||||
const LucideIcon = (Icons as any)[fac.icon] || Icons.Building
|
||||
const bg = useMemo(() => ({ background: gradientFrom(fac.color) }), [fac.color])
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={fac.id}
|
||||
to="/facultad/$facultadId"
|
||||
params={{ facultadId: fac.id }}
|
||||
aria-label={`Administrar ${fac.nombre}`}
|
||||
className="group relative block rounded-3xl overflow-hidden shadow-xl focus:outline-none focus-visible:ring-4 ring-white/60"
|
||||
style={bg}
|
||||
>
|
||||
{/* capa brillo */}
|
||||
<div className="absolute inset-0 opacity-0 group-hover:opacity-15 transition-opacity" style={{
|
||||
background: 'radial-gradient(1200px 400px at 20% -20%, rgba(255,255,255,.45), transparent 60%)'
|
||||
}} />
|
||||
|
||||
{/* contenido */}
|
||||
<div className="relative h-56 sm:h-64 lg:h-72 p-6 flex flex-col justify-between text-white">
|
||||
<LucideIcon className="w-20 h-20 md:w-24 md:h-24 drop-shadow-md" />
|
||||
<div className="flex items-end justify-between">
|
||||
<h3 className="text-xl md:text-2xl font-bold drop-shadow-sm pr-2">
|
||||
{fac.nombre}
|
||||
</h3>
|
||||
<Icons.ArrowRight className="w-6 h-6 opacity-0 translate-x-2 group-hover:opacity-100 group-hover:translate-x-0 transition-all" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* borde dinámico al hover */}
|
||||
<div className="absolute inset-0 ring-0 group-hover:ring-4 group-active:ring-4 ring-white/40 transition-[ring-width]" />
|
||||
{/* animación sutil */}
|
||||
<div className="absolute inset-0 scale-100 group-hover:scale-[1.02] group-active:scale-[0.99] transition-transform duration-300" />
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user