Dashboard v1 y configuraciones adicionales de prettier
This commit is contained in:
@@ -8,10 +8,11 @@ export default function Header() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className="p-4 flex items-center bg-gray-800 text-white shadow-lg">
|
||||
{/* Cambiar el color de fondo por bg-background/40 y agregar backdrop-blur para efecto glassmorphism */}
|
||||
<header className="sticky top-0 z-50 flex w-full items-center bg-gray-800 p-4 text-white shadow-lg">
|
||||
<button
|
||||
onClick={() => setIsOpen(true)}
|
||||
className="p-2 hover:bg-gray-700 rounded-lg transition-colors"
|
||||
className="rounded-lg p-2 transition-colors hover:bg-gray-700"
|
||||
aria-label="Open menu"
|
||||
>
|
||||
<Menu size={24} />
|
||||
@@ -28,26 +29,26 @@ export default function Header() {
|
||||
</header>
|
||||
|
||||
<aside
|
||||
className={`fixed top-0 left-0 h-full w-80 bg-gray-900 text-white shadow-2xl z-50 transform transition-transform duration-300 ease-in-out flex flex-col ${
|
||||
className={`fixed top-0 left-0 z-50 flex h-full w-80 transform flex-col bg-gray-900 text-white shadow-2xl transition-transform duration-300 ease-in-out ${
|
||||
isOpen ? 'translate-x-0' : '-translate-x-full'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between p-4 border-b border-gray-700">
|
||||
<div className="flex items-center justify-between border-b border-gray-700 p-4">
|
||||
<h2 className="text-xl font-bold">Navigation</h2>
|
||||
<button
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="p-2 hover:bg-gray-800 rounded-lg transition-colors"
|
||||
className="rounded-lg p-2 transition-colors hover:bg-gray-800"
|
||||
aria-label="Close menu"
|
||||
>
|
||||
<X size={24} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav className="flex-1 p-4 overflow-y-auto">
|
||||
<nav className="flex-1 overflow-y-auto p-4">
|
||||
<Link
|
||||
to="/"
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-800 transition-colors mb-2"
|
||||
className="mb-2 flex items-center gap-3 rounded-lg p-3 transition-colors hover:bg-gray-800"
|
||||
activeProps={{
|
||||
className:
|
||||
'flex items-center gap-3 p-3 rounded-lg bg-cyan-600 hover:bg-cyan-700 transition-colors mb-2',
|
||||
@@ -62,7 +63,7 @@ export default function Header() {
|
||||
<Link
|
||||
to="/demo/tanstack-query"
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-800 transition-colors mb-2"
|
||||
className="mb-2 flex items-center gap-3 rounded-lg p-3 transition-colors hover:bg-gray-800"
|
||||
activeProps={{
|
||||
className:
|
||||
'flex items-center gap-3 p-3 rounded-lg bg-cyan-600 hover:bg-cyan-700 transition-colors mb-2',
|
||||
|
||||
84
src/components/dashboard/DashboardHeader.tsx
Normal file
84
src/components/dashboard/DashboardHeader.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
|
||||
interface DashboardHeaderProps {
|
||||
name: string
|
||||
role: string
|
||||
department: string
|
||||
greeting?: string
|
||||
}
|
||||
|
||||
export default function DashboardHeader({
|
||||
name,
|
||||
role,
|
||||
department,
|
||||
greeting = 'Buenas noches,',
|
||||
}: DashboardHeaderProps) {
|
||||
// Generamos la URL de DiceBear dinámicamente con el nombre
|
||||
const dicebearUrl = `https://api.dicebear.com/9.x/initials/svg?seed=${encodeURIComponent(name)}`
|
||||
|
||||
// Calculamos iniciales de respaldo por si falla la imagen
|
||||
const initials = name
|
||||
.split(' ')
|
||||
.map((n) => n[0])
|
||||
.slice(0, 2)
|
||||
.join('')
|
||||
.toUpperCase()
|
||||
|
||||
return (
|
||||
<div className="flex flex-col justify-between gap-4 rounded-xl border p-6 shadow-md md:flex-row md:items-center">
|
||||
<div className="flex flex-row items-center gap-4">
|
||||
{/* 1. Avatar de DiceBear usando el componente de Shadcn */}
|
||||
<Avatar className="border-background h-12 w-12 border-2 shadow-sm">
|
||||
<AvatarImage src={dicebearUrl} alt={name} />
|
||||
<AvatarFallback>{initials}</AvatarFallback>
|
||||
</Avatar>
|
||||
|
||||
<div className="flex flex-col">
|
||||
{/* Saludo con texto secundario */}
|
||||
<p className="text-muted-foreground text-sm">{greeting}</p>
|
||||
|
||||
{/* Nombre destacado */}
|
||||
<h2 className="text-foreground text-lg font-bold tracking-tight">
|
||||
{name}
|
||||
</h2>
|
||||
|
||||
<div className="mt-1 flex flex-wrap items-center gap-2">
|
||||
{/* 2. El "Banner" (Badge) para el puesto */}
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="rounded-md px-2 py-0 text-xs font-semibold"
|
||||
>
|
||||
{role}
|
||||
</Badge>
|
||||
|
||||
{/* Departamento */}
|
||||
<span className="text-muted-foreground text-xs font-medium">
|
||||
{department}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="xs:flex-nowrap flex flex-row flex-wrap gap-6">
|
||||
<div className="bg-muted flex flex-row items-center gap-3 rounded-lg px-4 py-2">
|
||||
<div className="bg-primary/10 flex h-10 w-10 items-center justify-center rounded-lg">
|
||||
Icono
|
||||
</div>
|
||||
<div>
|
||||
<p>4</p>
|
||||
<p>Planes activos</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-muted flex flex-row items-center gap-3 rounded-lg px-4 py-2">
|
||||
<div className="bg-primary/10 flex h-10 w-10 items-center justify-center rounded-sm">
|
||||
Icono
|
||||
</div>
|
||||
<div>
|
||||
<p>3</p>
|
||||
<p>Revisiones pendientes</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
86
src/components/plan_estudios/PlanEstudiosCard.tsx
Normal file
86
src/components/plan_estudios/PlanEstudiosCard.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import { ArrowRight, type LucideIcon } from 'lucide-react'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Card, CardContent, CardFooter, CardHeader } from '@/components/ui/card'
|
||||
import { cn } from '@/lib/utils' // Asegúrate de tener tu utilidad cn
|
||||
|
||||
interface PlanEstudiosCardProps {
|
||||
/** El componente del ícono importado de lucide-react (ej. BookOpen) */
|
||||
Icono: LucideIcon
|
||||
nombrePrograma: string
|
||||
nivel: string
|
||||
ciclos: string | number // Acepta "8" o "8 semestres"
|
||||
facultad: string
|
||||
estado: string
|
||||
/** Código hex o variable CSS (ej. "#ef4444" o "var(--primary)") */
|
||||
claseColorEstado?: string
|
||||
colorFacultad: string
|
||||
/** Opcional: para manejar el click en la tarjeta */
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
export default function PlanEstudiosCard({
|
||||
Icono,
|
||||
nombrePrograma,
|
||||
nivel,
|
||||
ciclos,
|
||||
facultad,
|
||||
estado,
|
||||
claseColorEstado = '',
|
||||
colorFacultad,
|
||||
onClick,
|
||||
}: PlanEstudiosCardProps) {
|
||||
return (
|
||||
<Card
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
'group relative flex h-full cursor-pointer flex-col justify-between overflow-hidden border-l-4 transition-all hover:shadow-lg',
|
||||
)}
|
||||
// Aplicamos el color de la facultad dinámicamente al borde y un fondo muy sutil
|
||||
style={{
|
||||
borderLeftColor: colorFacultad,
|
||||
backgroundColor: `color-mix(in srgb, ${colorFacultad}, transparent 95%)`, // Truco CSS moderno para fondo tintado
|
||||
}}
|
||||
>
|
||||
<CardHeader className="pb-2">
|
||||
{/* Ícono con el color de la facultad */}
|
||||
<div
|
||||
className="mb-2 w-fit rounded-md p-2"
|
||||
style={{
|
||||
backgroundColor: `color-mix(in srgb, ${colorFacultad}, transparent 85%)`,
|
||||
}}
|
||||
>
|
||||
<Icono size={24} style={{ color: colorFacultad }} />
|
||||
</div>
|
||||
|
||||
{/* Título del Programa */}
|
||||
<h4 className="line-clamp-2 text-lg leading-tight font-bold tracking-tight">
|
||||
{nombrePrograma}
|
||||
</h4>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="text-muted-foreground space-y-1 pb-4 text-sm">
|
||||
<p className="text-foreground font-medium">
|
||||
{nivel} • {ciclos}
|
||||
</p>
|
||||
<p>{facultad}</p>
|
||||
</CardContent>
|
||||
|
||||
<CardFooter className="bg-background/50 flex items-center justify-between border-t px-6 py-3 backdrop-blur-sm">
|
||||
<Badge className={`text-sm font-semibold ${claseColorEstado}`}>
|
||||
{estado}
|
||||
</Badge>
|
||||
{/* <span className="text-foreground/80 text-sm font-semibold">
|
||||
{estado}
|
||||
</span> */}
|
||||
|
||||
{/* Flecha animada */}
|
||||
<div
|
||||
className="rounded-full p-1 transition-transform duration-300 group-hover:translate-x-1"
|
||||
style={{ color: colorFacultad }}
|
||||
>
|
||||
<ArrowRight size={20} />
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
51
src/components/ui/avatar.tsx
Normal file
51
src/components/ui/avatar.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import * as React from "react"
|
||||
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Avatar({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
|
||||
return (
|
||||
<AvatarPrimitive.Root
|
||||
data-slot="avatar"
|
||||
className={cn(
|
||||
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function AvatarImage({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
||||
return (
|
||||
<AvatarPrimitive.Image
|
||||
data-slot="avatar-image"
|
||||
className={cn("aspect-square size-full", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function AvatarFallback({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
||||
return (
|
||||
<AvatarPrimitive.Fallback
|
||||
data-slot="avatar-fallback"
|
||||
className={cn(
|
||||
"bg-muted flex size-full items-center justify-center rounded-full",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Avatar, AvatarImage, AvatarFallback }
|
||||
46
src/components/ui/badge.tsx
Normal file
46
src/components/ui/badge.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
||||
secondary:
|
||||
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||
outline:
|
||||
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
function Badge({
|
||||
className,
|
||||
variant,
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"span"> &
|
||||
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
||||
const Comp = asChild ? Slot : "span"
|
||||
|
||||
return (
|
||||
<Comp
|
||||
data-slot="badge"
|
||||
className={cn(badgeVariants({ variant }), className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants }
|
||||
92
src/components/ui/card.tsx
Normal file
92
src/components/ui/card.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card"
|
||||
className={cn(
|
||||
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-header"
|
||||
className={cn(
|
||||
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-title"
|
||||
className={cn("leading-none font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-description"
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-action"
|
||||
className={cn(
|
||||
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-content"
|
||||
className={cn("px-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-footer"
|
||||
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardFooter,
|
||||
CardTitle,
|
||||
CardAction,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
}
|
||||
Reference in New Issue
Block a user