Merge remote-tracking branch 'origin/main' into feat/planes-vista
This commit is contained in:
27
src/components/layout/AppLayout.tsx
Normal file
27
src/components/layout/AppLayout.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import type { ReactNode } from 'react'
|
||||||
|
import { Sidebar } from './Sidebar'
|
||||||
|
import { Header } from './Header'
|
||||||
|
import { StatsGrid } from '../plans/StatsGrid'
|
||||||
|
|
||||||
|
export function AppLayout({ children }: { children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex bg-gray-100">
|
||||||
|
{/* <Sidebar /> */}
|
||||||
|
|
||||||
|
<div className="flex-1 flex flex-col">
|
||||||
|
<Header />
|
||||||
|
|
||||||
|
{/* Separación Header → Stats */}
|
||||||
|
<section className="mt-4 bg-white border-b">
|
||||||
|
<div className="px-6 py-6">
|
||||||
|
<StatsGrid />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<main className="flex-1 p-6">
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
18
src/components/layout/Header.tsx
Normal file
18
src/components/layout/Header.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export function Header() {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-4 bg-white border-b shadow-[0_1px_2px_rgba(0,0,0,0.05)] z-10 relative">
|
||||||
|
<div className="h-12 w-12 rounded-full bg-emerald-600 flex items-center justify-center text-white">
|
||||||
|
🎓
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h1 className="text-xl font-semibold text-gray-900">
|
||||||
|
Gestión Curricular
|
||||||
|
</h1>
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
Sistema de Planes de Estudio
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
32
src/components/layout/Sidebar.tsx
Normal file
32
src/components/layout/Sidebar.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { LayoutGrid, BookOpen } from 'lucide-react'
|
||||||
|
|
||||||
|
export function Sidebar() {
|
||||||
|
return (
|
||||||
|
<aside className="w-64 bg-white border-r px-4 py-6">
|
||||||
|
<h2 className="text-lg font-semibold mb-6">
|
||||||
|
Planes de Estudio
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<nav className="space-y-2">
|
||||||
|
<NavItem icon={LayoutGrid} label="Dashboard" active />
|
||||||
|
<NavItem icon={BookOpen} label="Planes" />
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function NavItem({ icon: Icon, label, active }: any) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`flex items-center gap-3 px-3 py-2 rounded-lg cursor-pointer
|
||||||
|
${
|
||||||
|
active
|
||||||
|
? 'bg-gray-100 text-gray-900'
|
||||||
|
: 'text-gray-500 hover:bg-gray-50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Icon size={18} />
|
||||||
|
<span className="text-sm font-medium">{label}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
26
src/components/plans/PlanCard.tsx
Normal file
26
src/components/plans/PlanCard.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { StatusBadge } from "./StatusBadge";
|
||||||
|
|
||||||
|
export function PlanCard({ plan }: any) {
|
||||||
|
return (
|
||||||
|
<div className="bg-[#eaf6fa] rounded-2xl p-6 border hover:shadow-md transition">
|
||||||
|
<div className="flex justify-between items-start mb-4">
|
||||||
|
<span className="text-sm text-gray-500">⚙ Ingeniería</span>
|
||||||
|
<StatusBadge status={plan.status} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 className="text-lg font-semibold text-gray-900">
|
||||||
|
{plan.title}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p className="text-sm text-gray-600 mb-6">
|
||||||
|
{plan.subtitle}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex justify-between text-sm text-gray-500">
|
||||||
|
<span>{plan.cycles} ciclos</span>
|
||||||
|
<span>{plan.credits} créditos</span>
|
||||||
|
<span>➜</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
38
src/components/plans/PlanGrid.tsx
Normal file
38
src/components/plans/PlanGrid.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { PlanCard } from './PlanCard'
|
||||||
|
|
||||||
|
const mockPlans = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Ingeniería en Sistemas',
|
||||||
|
level: 'Licenciatura',
|
||||||
|
status: 'Activo',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'Arquitectura',
|
||||||
|
level: 'Licenciatura',
|
||||||
|
status: 'Activo',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'Maestría en Educación',
|
||||||
|
level: 'Maestría',
|
||||||
|
status: 'Inactivo',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export function PlanGrid() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-semibold mb-4">
|
||||||
|
Planes disponibles
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{mockPlans.map(plan => (
|
||||||
|
<PlanCard key={plan.id} plan={plan} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
11
src/components/plans/StatCard.tsx
Normal file
11
src/components/plans/StatCard.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export function StatCard({ icon, value, label }: any) {
|
||||||
|
return (
|
||||||
|
<div className="bg-white rounded-xl border p-6">
|
||||||
|
<div className="flex items-center gap-3 mb-2">
|
||||||
|
<span className="text-xl">{icon}</span>
|
||||||
|
<span className="text-2xl font-semibold">{value}</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-gray-500">{label}</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
12
src/components/plans/StatsGrid.tsx
Normal file
12
src/components/plans/StatsGrid.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { StatCard } from "./StatCard";
|
||||||
|
|
||||||
|
export function StatsGrid() {
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||||
|
<StatCard icon="📘" value="12" label="Planes Activos" />
|
||||||
|
<StatCard icon="🕒" value="4" label="En Revisión" />
|
||||||
|
<StatCard icon="✅" value="8" label="Aprobados" />
|
||||||
|
<StatCard icon="👥" value="6" label="Carreras" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
19
src/components/plans/StatusBadge.tsx
Normal file
19
src/components/plans/StatusBadge.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
export function StatusBadge({ status }: { status: string }) {
|
||||||
|
const styles: any = {
|
||||||
|
revision: 'bg-blue-100 text-blue-700',
|
||||||
|
aprobado: 'bg-green-100 text-green-700',
|
||||||
|
borrador: 'bg-gray-200 text-gray-600',
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={`text-xs px-3 py-1 rounded-full font-medium ${styles[status]}`}
|
||||||
|
>
|
||||||
|
{status === 'revision'
|
||||||
|
? 'En Revisión'
|
||||||
|
: status === 'aprobado'
|
||||||
|
? 'Aprobado'
|
||||||
|
: 'Borrador'}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import { Route as PlanesRouteImport } from './routes/planes'
|
|||||||
import { Route as LoginRouteImport } from './routes/login'
|
import { Route as LoginRouteImport } from './routes/login'
|
||||||
import { Route as DashboardRouteImport } from './routes/dashboard'
|
import { Route as DashboardRouteImport } from './routes/dashboard'
|
||||||
import { Route as IndexRouteImport } from './routes/index'
|
import { Route as IndexRouteImport } from './routes/index'
|
||||||
|
import { Route as PlanesIndexRouteImport } from './routes/planes/index'
|
||||||
import { Route as DemoTanstackQueryRouteImport } from './routes/demo/tanstack-query'
|
import { Route as DemoTanstackQueryRouteImport } from './routes/demo/tanstack-query'
|
||||||
|
|
||||||
const PlanesRoute = PlanesRouteImport.update({
|
const PlanesRoute = PlanesRouteImport.update({
|
||||||
@@ -35,6 +36,11 @@ const IndexRoute = IndexRouteImport.update({
|
|||||||
path: '/',
|
path: '/',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
|
const PlanesIndexRoute = PlanesIndexRouteImport.update({
|
||||||
|
id: '/planes/',
|
||||||
|
path: '/planes/',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
const DemoTanstackQueryRoute = DemoTanstackQueryRouteImport.update({
|
const DemoTanstackQueryRoute = DemoTanstackQueryRouteImport.update({
|
||||||
id: '/demo/tanstack-query',
|
id: '/demo/tanstack-query',
|
||||||
path: '/demo/tanstack-query',
|
path: '/demo/tanstack-query',
|
||||||
@@ -47,6 +53,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/login': typeof LoginRoute
|
'/login': typeof LoginRoute
|
||||||
'/planes': typeof PlanesRoute
|
'/planes': typeof PlanesRoute
|
||||||
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
|
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
|
||||||
|
'/planes': typeof PlanesIndexRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
@@ -54,6 +61,7 @@ export interface FileRoutesByTo {
|
|||||||
'/login': typeof LoginRoute
|
'/login': typeof LoginRoute
|
||||||
'/planes': typeof PlanesRoute
|
'/planes': typeof PlanesRoute
|
||||||
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
|
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
|
||||||
|
'/planes': typeof PlanesIndexRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesById {
|
export interface FileRoutesById {
|
||||||
__root__: typeof rootRouteImport
|
__root__: typeof rootRouteImport
|
||||||
@@ -62,19 +70,31 @@ export interface FileRoutesById {
|
|||||||
'/login': typeof LoginRoute
|
'/login': typeof LoginRoute
|
||||||
'/planes': typeof PlanesRoute
|
'/planes': typeof PlanesRoute
|
||||||
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
|
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
|
||||||
|
'/planes/': typeof PlanesIndexRoute
|
||||||
}
|
}
|
||||||
export interface FileRouteTypes {
|
export interface FileRouteTypes {
|
||||||
fileRoutesByFullPath: FileRoutesByFullPath
|
fileRoutesByFullPath: FileRoutesByFullPath
|
||||||
|
<<<<<<< HEAD
|
||||||
fullPaths: '/' | '/dashboard' | '/login' | '/planes' | '/demo/tanstack-query'
|
fullPaths: '/' | '/dashboard' | '/login' | '/planes' | '/demo/tanstack-query'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to: '/' | '/dashboard' | '/login' | '/planes' | '/demo/tanstack-query'
|
to: '/' | '/dashboard' | '/login' | '/planes' | '/demo/tanstack-query'
|
||||||
|
=======
|
||||||
|
fullPaths: '/' | '/dashboard' | '/login' | '/demo/tanstack-query' | '/planes'
|
||||||
|
fileRoutesByTo: FileRoutesByTo
|
||||||
|
to: '/' | '/dashboard' | '/login' | '/demo/tanstack-query' | '/planes'
|
||||||
|
>>>>>>> origin/main
|
||||||
id:
|
id:
|
||||||
| '__root__'
|
| '__root__'
|
||||||
| '/'
|
| '/'
|
||||||
| '/dashboard'
|
| '/dashboard'
|
||||||
| '/login'
|
| '/login'
|
||||||
|
<<<<<<< HEAD
|
||||||
| '/planes'
|
| '/planes'
|
||||||
| '/demo/tanstack-query'
|
| '/demo/tanstack-query'
|
||||||
|
=======
|
||||||
|
| '/demo/tanstack-query'
|
||||||
|
| '/planes/'
|
||||||
|
>>>>>>> origin/main
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
}
|
}
|
||||||
export interface RootRouteChildren {
|
export interface RootRouteChildren {
|
||||||
@@ -83,6 +103,7 @@ export interface RootRouteChildren {
|
|||||||
LoginRoute: typeof LoginRoute
|
LoginRoute: typeof LoginRoute
|
||||||
PlanesRoute: typeof PlanesRoute
|
PlanesRoute: typeof PlanesRoute
|
||||||
DemoTanstackQueryRoute: typeof DemoTanstackQueryRoute
|
DemoTanstackQueryRoute: typeof DemoTanstackQueryRoute
|
||||||
|
PlanesIndexRoute: typeof PlanesIndexRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
declare module '@tanstack/react-router' {
|
||||||
@@ -115,6 +136,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof IndexRouteImport
|
preLoaderRoute: typeof IndexRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
|
'/planes/': {
|
||||||
|
id: '/planes/'
|
||||||
|
path: '/planes'
|
||||||
|
fullPath: '/planes'
|
||||||
|
preLoaderRoute: typeof PlanesIndexRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
'/demo/tanstack-query': {
|
'/demo/tanstack-query': {
|
||||||
id: '/demo/tanstack-query'
|
id: '/demo/tanstack-query'
|
||||||
path: '/demo/tanstack-query'
|
path: '/demo/tanstack-query'
|
||||||
@@ -131,6 +159,7 @@ const rootRouteChildren: RootRouteChildren = {
|
|||||||
LoginRoute: LoginRoute,
|
LoginRoute: LoginRoute,
|
||||||
PlanesRoute: PlanesRoute,
|
PlanesRoute: PlanesRoute,
|
||||||
DemoTanstackQueryRoute: DemoTanstackQueryRoute,
|
DemoTanstackQueryRoute: DemoTanstackQueryRoute,
|
||||||
|
PlanesIndexRoute: PlanesIndexRoute,
|
||||||
}
|
}
|
||||||
export const routeTree = rootRouteImport
|
export const routeTree = rootRouteImport
|
||||||
._addFileChildren(rootRouteChildren)
|
._addFileChildren(rootRouteChildren)
|
||||||
|
|||||||
15
src/routes/planes/index.tsx
Normal file
15
src/routes/planes/index.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
import { AppLayout } from '@/components/layout/AppLayout'
|
||||||
|
import { PlanGrid } from '@/components/plans/PlanGrid'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/planes/')({
|
||||||
|
component: PlanesPage,
|
||||||
|
})
|
||||||
|
|
||||||
|
function PlanesPage() {
|
||||||
|
return (
|
||||||
|
<AppLayout>
|
||||||
|
<PlanGrid />
|
||||||
|
</AppLayout>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user