feat: add Usuarios route and user management functionality

- Introduced a new route for user management under /usuarios.
- Implemented user listing with search and edit capabilities.
- Added role management with visual indicators for user roles.
- Created a modal for editing user details, including role and permissions.
- Integrated Supabase for user data retrieval and updates.
- Enhanced UI components for better user experience.
- Removed unused planes route and related components.
- Added a new plan detail modal for displaying plan information.
- Updated navigation to include new Usuarios link.
This commit is contained in:
2025-08-21 15:30:50 -06:00
parent fe471bcfc2
commit 02ad043ed6
16 changed files with 1542 additions and 97 deletions

View File

@@ -6,7 +6,7 @@ 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, ScrollText, BookOpen } from "lucide-react"
import { Plus, RefreshCcw, Building2 } from "lucide-react"
export type PlanDeEstudios = {
id: string; nombre: string; nivel: string | null; duracion: string | null;
@@ -48,15 +48,32 @@ function hexToRgb(hex?: string | null): [number, number, number] {
const n = parseInt(v, 16)
return [(n >> 16) & 255, (n >> 8) & 255, n & 255]
}
function softCardStyles(color?: string | null) {
/* ---------- helpers ---------- */
function chipTint(color?: string | null) {
const [r, g, b] = hexToRgb(color)
return {
// borde + velo muy sutil del color de la facultad
borderColor: `rgba(${r},${g},${b},.28)`,
background: `linear-gradient(180deg, rgba(${r},${g},${b},.15), rgba(${r},${g},${b},.02))`,
borderColor: `rgba(${r},${g},${b},.30)`,
background: `rgba(${r},${g},${b},.10)`,
} as React.CSSProperties
}
function InfoChip({
icon, label, tint,
}: { icon: React.ReactNode; label: string; tint?: string | null }) {
const style = tint ? chipTint(tint) : undefined
return (
<span
title={label}
className="inline-flex max-w-full items-center gap-1 rounded-lg border px-2.5 py-1 text-xs leading-none bg-white/70 text-neutral-800"
style={style}
>
{icon}
<span className="truncate">{label}</span>
</span>
)
}
function RouteComponent() {
const auth = useSupabaseAuth()
const [q, setQ] = useState("")
@@ -99,13 +116,14 @@ function RouteComponent() {
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{filtered?.map((p) => {
const fac = p.carreras?.facultades
const styles = softCardStyles(fac?.color)
const styles = chipTint(fac?.color)
const IconComp = (fac?.icon && (Icons as any)[fac.icon]) || Building2
return (
<Link
key={p.id}
to="/planes/$planId/modal"
to="/plan/$planId/modal"
mask={{ to: '/plan/$planId', params: { planId: p.id } }}
className="group relative overflow-hidden rounded-3xl bg-white shadow-sm ring-1 transition-all hover:shadow-md hover:-translate-y-0.5"
params={{ planId: p.id }}
style={styles}
@@ -124,23 +142,37 @@ function RouteComponent() {
</div>
</div>
<div className="flex items-center gap-2 text-xs">
{showCarrera && p.carreras?.nombre && (
<Badge variant="secondary" className="border text-neutral-700 bg-white/70 w-fit">
<ScrollText className="mr-1 h-3 w-3" /> {p.carreras?.nombre}
</Badge>
)}
{showFacultad && fac?.nombre && (
<Badge variant="outline" className="bg-white/60 w-fit" style={{ borderColor: styles.borderColor }}>
<BookOpen className="mr-1 h-3 w-3" /> {fac?.nombre}
</Badge>
)}
{/* Dentro del map de tarjetas, sustituye SOLO el footer inferior */}
<div className="mt-3 flex items-center gap-2">
{/* grupo izquierdo: chips (wrap si no caben) */}
<div className="min-w-0 flex-1 flex flex-wrap items-center gap-2">
{showCarrera && p.carreras?.nombre && (
<InfoChip
icon={<Icons.GraduationCap className="h-3 w-3" />}
label={p.carreras.nombre}
/>
)}
{showFacultad && fac?.nombre && (
<InfoChip
icon={<Icons.Building2 className="h-3 w-3" />}
label={fac.nombre}
tint={fac.color} // tinte sutil por facultad
/>
)}
</div>
{/* derecha: estado */}
{p.estado && (
<Badge variant="outline" className="ml-auto bg-white/60" style={{ borderColor: styles.borderColor }}>
<Badge
variant="outline"
className="bg-white/60"
style={{ borderColor: (chipTint(fac?.color).borderColor as string) }}
>
{p.estado}
</Badge>
)}
</div>
</div>
</Link>
)