Dashboard v1 y configuraciones adicionales de prettier
This commit is contained in:
@@ -12,6 +12,9 @@
|
||||
"check": "prettier --write . && eslint --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-avatar": "^1.1.11",
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@tailwindcss/vite": "^4.0.6",
|
||||
"@tanstack/react-devtools": "^0.7.0",
|
||||
"@tanstack/react-query": "^5.66.5",
|
||||
@@ -21,7 +24,7 @@
|
||||
"@tanstack/router-plugin": "^1.132.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.544.0",
|
||||
"lucide-react": "^0.561.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
@@ -37,8 +40,11 @@
|
||||
"@types/react": "^19.2.0",
|
||||
"@types/react-dom": "^19.2.0",
|
||||
"@vitejs/plugin-react": "^5.0.4",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"jsdom": "^27.0.0",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^7.1.7",
|
||||
"vitest": "^3.0.5",
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
const config = {
|
||||
semi: false,
|
||||
singleQuote: true,
|
||||
trailingComma: "all",
|
||||
};
|
||||
trailingComma: 'all',
|
||||
plugins: ['prettier-plugin-tailwindcss'],
|
||||
}
|
||||
|
||||
export default config;
|
||||
export default config
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -9,9 +9,15 @@
|
||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||
|
||||
import { Route as rootRouteImport } from './routes/__root'
|
||||
import { Route as DashboardRouteImport } from './routes/dashboard'
|
||||
import { Route as IndexRouteImport } from './routes/index'
|
||||
import { Route as DemoTanstackQueryRouteImport } from './routes/demo/tanstack-query'
|
||||
|
||||
const DashboardRoute = DashboardRouteImport.update({
|
||||
id: '/dashboard',
|
||||
path: '/dashboard',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const IndexRoute = IndexRouteImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
@@ -25,32 +31,43 @@ const DemoTanstackQueryRoute = DemoTanstackQueryRouteImport.update({
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute
|
||||
'/dashboard': typeof DashboardRoute
|
||||
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
|
||||
}
|
||||
export interface FileRoutesByTo {
|
||||
'/': typeof IndexRoute
|
||||
'/dashboard': typeof DashboardRoute
|
||||
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
|
||||
}
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRouteImport
|
||||
'/': typeof IndexRoute
|
||||
'/dashboard': typeof DashboardRoute
|
||||
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
|
||||
}
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths: '/' | '/demo/tanstack-query'
|
||||
fullPaths: '/' | '/dashboard' | '/demo/tanstack-query'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to: '/' | '/demo/tanstack-query'
|
||||
id: '__root__' | '/' | '/demo/tanstack-query'
|
||||
to: '/' | '/dashboard' | '/demo/tanstack-query'
|
||||
id: '__root__' | '/' | '/dashboard' | '/demo/tanstack-query'
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
DashboardRoute: typeof DashboardRoute
|
||||
DemoTanstackQueryRoute: typeof DemoTanstackQueryRoute
|
||||
}
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface FileRoutesByPath {
|
||||
'/dashboard': {
|
||||
id: '/dashboard'
|
||||
path: '/dashboard'
|
||||
fullPath: '/dashboard'
|
||||
preLoaderRoute: typeof DashboardRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/': {
|
||||
id: '/'
|
||||
path: '/'
|
||||
@@ -70,6 +87,7 @@ declare module '@tanstack/react-router' {
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
DashboardRoute: DashboardRoute,
|
||||
DemoTanstackQueryRoute: DemoTanstackQueryRoute,
|
||||
}
|
||||
export const routeTree = rootRouteImport
|
||||
|
||||
190
src/routes/dashboard.tsx
Normal file
190
src/routes/dashboard.tsx
Normal file
@@ -0,0 +1,190 @@
|
||||
import DashboardHeader from '@/components/dashboard/DashboardHeader'
|
||||
import {
|
||||
ArrowRight,
|
||||
BookOpenText,
|
||||
Laptop,
|
||||
Stethoscope,
|
||||
Scale,
|
||||
Calculator,
|
||||
FlaskConical,
|
||||
Activity,
|
||||
PencilRuler,
|
||||
ClipboardCheck,
|
||||
} from 'lucide-react'
|
||||
import PlanEstudiosCard from '@/components/plan_estudios/PlanEstudiosCard'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/dashboard')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
return (
|
||||
// 1. min-h-screen para asegurar que llene la pantalla verticalmente
|
||||
// 2. bg-background para asegurar consistencia con el tema
|
||||
<main className="bg-background min-h-screen w-full">
|
||||
{/* 1. max-w-7xl: El tope de anchura.
|
||||
2. w-full: Para que ocupe el 100% hasta llegar al tope.
|
||||
3. mx-auto: Para centrarse.
|
||||
4. px-4 md:px-6: Padding RESPONSIVO interno (seguro para móviles y desktop).
|
||||
5. py-6: Padding vertical (opcional, para separarse del header).
|
||||
*/}
|
||||
<div className="mx-auto flex w-full max-w-7xl flex-col gap-4 px-4 py-6 md:px-6 lg:px-8">
|
||||
<DashboardHeader
|
||||
name="Dr. Carlos Mendoza"
|
||||
role="Jefe de Carrera"
|
||||
department="Facultad de Ingeniería"
|
||||
/>
|
||||
|
||||
<div className="mt-6 grid gap-6 lg:grid-cols-3">
|
||||
{/* --- Sección de Mis Planes de Estudio --- */}
|
||||
<div className="flex flex-col gap-4 lg:col-span-2">
|
||||
{/* --- Título de sección y enlace a página --- */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="text-primary">
|
||||
<BookOpenText className="h-6 w-6" strokeWidth={2} />
|
||||
</div>
|
||||
|
||||
<h3 className="text-foreground text-xl font-bold tracking-tight">
|
||||
Mis Planes de Estudio
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{/* Usamos 'group' para animar la flecha al hacer hover en el texto */}
|
||||
<a
|
||||
href="/planes"
|
||||
className="group text-muted-foreground hover:text-primary flex items-center gap-1.5 text-sm font-medium transition-colors"
|
||||
>
|
||||
<span>Ver todos</span>
|
||||
{/* La flecha se mueve a la derecha al hacer hover en el grupo */}
|
||||
<ArrowRight className="h-4 w-4 transition-transform duration-300 group-hover:translate-x-1" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<PlanEstudiosCard
|
||||
Icono={Laptop}
|
||||
nombrePrograma="Ingeniería en Sistemas Computacionales"
|
||||
nivel="Licenciatura"
|
||||
ciclos="8 semestres"
|
||||
facultad="Facultad de Ingeniería"
|
||||
estado="Revisión expertos"
|
||||
claseColorEstado="bg-amber-600"
|
||||
colorFacultad="#2563eb"
|
||||
onClick={() => console.log('Navegar a Sistemas...')}
|
||||
/>
|
||||
|
||||
<PlanEstudiosCard
|
||||
Icono={Stethoscope}
|
||||
nombrePrograma="Médico Cirujano"
|
||||
nivel="Licenciatura"
|
||||
ciclos="10 semestres"
|
||||
facultad="Facultad de Medicina"
|
||||
estado="Aprobado"
|
||||
claseColorEstado="bg-emerald-600"
|
||||
colorFacultad="#dc2626"
|
||||
/>
|
||||
|
||||
<PlanEstudiosCard
|
||||
Icono={Calculator}
|
||||
nombrePrograma="Licenciatura en Actuaría"
|
||||
nivel="Licenciatura"
|
||||
ciclos="9 semestres"
|
||||
facultad="Facultad de Negocios"
|
||||
estado="Aprobado"
|
||||
claseColorEstado="bg-emerald-600"
|
||||
colorFacultad="#059669"
|
||||
onClick={() => console.log('Ver Actuaría')}
|
||||
/>
|
||||
|
||||
<PlanEstudiosCard
|
||||
Icono={PencilRuler}
|
||||
nombrePrograma="Licenciatura en Arquitectura"
|
||||
nivel="Licenciatura"
|
||||
ciclos="10 semestres"
|
||||
facultad="Facultad Mexicana de Arquitectura, Diseño y Comunicación"
|
||||
estado="En proceso"
|
||||
claseColorEstado="bg-orange-500"
|
||||
colorFacultad="#ea580c"
|
||||
onClick={() => console.log('Ver Arquitectura')}
|
||||
/>
|
||||
|
||||
<PlanEstudiosCard
|
||||
Icono={Activity}
|
||||
nombrePrograma="Licenciatura en Fisioterapia"
|
||||
nivel="Licenciatura"
|
||||
ciclos="8 semestres"
|
||||
facultad="Escuela de Altos Estudios en Salud"
|
||||
estado="Revisión expertos"
|
||||
claseColorEstado="bg-amber-600"
|
||||
colorFacultad="#0891b2"
|
||||
onClick={() => console.log('Ver Fisioterapia')}
|
||||
/>
|
||||
|
||||
<PlanEstudiosCard
|
||||
Icono={Scale}
|
||||
nombrePrograma="Licenciatura en Derecho"
|
||||
nivel="Licenciatura"
|
||||
ciclos="10 semestres"
|
||||
facultad="Facultad de Derecho"
|
||||
estado="Pendiente"
|
||||
claseColorEstado="bg-yellow-500"
|
||||
colorFacultad="#7c3aed"
|
||||
onClick={() => console.log('Ver Derecho')}
|
||||
/>
|
||||
|
||||
<PlanEstudiosCard
|
||||
Icono={FlaskConical}
|
||||
nombrePrograma="Químico Farmacéutico Biólogo"
|
||||
nivel="Licenciatura"
|
||||
ciclos="9 semestres"
|
||||
facultad="Facultad de Ciencias Químicas"
|
||||
estado="Actualización"
|
||||
claseColorEstado="bg-lime-600"
|
||||
colorFacultad="#65a30d"
|
||||
onClick={() => console.log('Ver QFB')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* --- Sección de Mis Revisiones --- */}
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* --- Título de sección y enlace a página --- */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="text-primary">
|
||||
<ClipboardCheck className="h-6 w-6" strokeWidth={2} />
|
||||
</div>
|
||||
|
||||
<h3 className="text-foreground text-xl font-bold tracking-tight">
|
||||
Mis Revisiones
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="/revisiones"
|
||||
className="group text-muted-foreground hover:text-primary flex items-center gap-1.5 text-sm font-medium transition-colors"
|
||||
>
|
||||
<span>Ver todas</span>
|
||||
<ArrowRight className="h-4 w-4 transition-transform duration-300 group-hover:translate-x-1" />
|
||||
</a>
|
||||
</div>
|
||||
{/* --- Lista de revisiones (simplificada para este ejemplo) --- */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="min-h-20 rounded-lg border p-4 shadow-md">
|
||||
Revision 1
|
||||
</div>
|
||||
<div className="min-h-20 rounded-lg border p-4 shadow-md">
|
||||
Revision 2
|
||||
</div>
|
||||
<div className="min-h-20 rounded-lg border p-4 shadow-md">
|
||||
Revision 3
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user