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:
@@ -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>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user