Se agregan detalles en modal de editar materia en mapa curricular
This commit is contained in:
@@ -1,7 +1,5 @@
|
|||||||
|
/* eslint-disable jsx-a11y/label-has-associated-control */
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
import { useMemo, useState, useEffect } from 'react'
|
|
||||||
import { Badge } from '@/components/ui/badge'
|
|
||||||
import { Input } from '@/components/ui/input'
|
|
||||||
import {
|
import {
|
||||||
Plus,
|
Plus,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
@@ -9,7 +7,11 @@ import {
|
|||||||
GripVertical,
|
GripVertical,
|
||||||
Trash2,
|
Trash2,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
import { useMemo, useState, useEffect } from 'react'
|
||||||
|
|
||||||
import type { Materia, LineaCurricular } from '@/types/plan'
|
import type { Materia, LineaCurricular } from '@/types/plan'
|
||||||
|
|
||||||
|
import { Badge } from '@/components/ui/badge'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -23,12 +25,20 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/components/ui/dropdown-menu'
|
} from '@/components/ui/dropdown-menu'
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select'
|
||||||
import { usePlanAsignaturas, usePlanLineas } from '@/data'
|
import { usePlanAsignaturas, usePlanLineas } from '@/data'
|
||||||
|
|
||||||
// --- Mapeadores (Fuera del componente para mayor limpieza) ---
|
// --- Mapeadores (Fuera del componente para mayor limpieza) ---
|
||||||
const mapLineasToLineaCurricular = (
|
const mapLineasToLineaCurricular = (
|
||||||
lineasApi: any[] = [],
|
lineasApi: Array<any> = [],
|
||||||
): LineaCurricular[] => {
|
): Array<LineaCurricular> => {
|
||||||
return lineasApi.map((linea) => ({
|
return lineasApi.map((linea) => ({
|
||||||
id: linea.id,
|
id: linea.id,
|
||||||
nombre: linea.nombre,
|
nombre: linea.nombre,
|
||||||
@@ -37,7 +47,7 @@ const mapLineasToLineaCurricular = (
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapAsignaturasToMaterias = (asigApi: any[] = []): Materia[] => {
|
const mapAsignaturasToMaterias = (asigApi: Array<any> = []): Array<Materia> => {
|
||||||
return asigApi.map((asig) => ({
|
return asigApi.map((asig) => ({
|
||||||
id: asig.id,
|
id: asig.id,
|
||||||
clave: asig.codigo,
|
clave: asig.codigo,
|
||||||
@@ -50,6 +60,7 @@ const mapAsignaturasToMaterias = (asigApi: any[] = []): Materia[] => {
|
|||||||
orden: asig.orden_celda ?? 0,
|
orden: asig.orden_celda ?? 0,
|
||||||
hd: Math.floor((asig.horas_semana ?? 0) / 2),
|
hd: Math.floor((asig.horas_semana ?? 0) / 2),
|
||||||
hi: Math.ceil((asig.horas_semana ?? 0) / 2),
|
hi: Math.ceil((asig.horas_semana ?? 0) / 2),
|
||||||
|
prerrequisitos: [],
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,18 +162,55 @@ function MapaCurricularPage() {
|
|||||||
|
|
||||||
// 1. Fetch de Datos
|
// 1. Fetch de Datos
|
||||||
const { data: asignaturasApi, isLoading: loadingAsig } = usePlanAsignaturas(
|
const { data: asignaturasApi, isLoading: loadingAsig } = usePlanAsignaturas(
|
||||||
/*planId*/ '0e0aea4d-b8b4-4e75-8279-6224c3ac769f',
|
/* planId*/ '0e0aea4d-b8b4-4e75-8279-6224c3ac769f',
|
||||||
)
|
)
|
||||||
const { data: lineasApi, isLoading: loadingLineas } = usePlanLineas(
|
const { data: lineasApi, isLoading: loadingLineas } = usePlanLineas(
|
||||||
/*planId*/ '0e0aea4d-b8b4-4e75-8279-6224c3ac769f',
|
/* planId*/ '0e0aea4d-b8b4-4e75-8279-6224c3ac769f',
|
||||||
)
|
)
|
||||||
|
|
||||||
// 2. Estado Local (Para interactividad)
|
// 2. Estado Local (Para interactividad)
|
||||||
const [materias, setMaterias] = useState<Materia[]>([])
|
const [materias, setMaterias] = useState<Array<Materia>>([])
|
||||||
const [lineas, setLineas] = useState<LineaCurricular[]>([])
|
const [lineas, setLineas] = useState<Array<LineaCurricular>>([])
|
||||||
const [draggedMateria, setDraggedMateria] = useState<string | null>(null)
|
const [draggedMateria, setDraggedMateria] = useState<string | null>(null)
|
||||||
const [isEditModalOpen, setIsEditModalOpen] = useState(false)
|
const [isEditModalOpen, setIsEditModalOpen] = useState(false)
|
||||||
const [selectedMateria, setSelectedMateria] = useState<Materia | null>(null)
|
const [selectedMateria, setSelectedMateria] = useState<Materia | null>(null)
|
||||||
|
const [hasAreaComun, setHasAreaComun] = useState(false)
|
||||||
|
const [nombreNuevaLinea, setNombreNuevaLinea] = useState('') // Para el input de nombre personalizado
|
||||||
|
|
||||||
|
const manejarAgregarLinea = (nombre: string) => {
|
||||||
|
const nombreNormalizado = nombre.trim()
|
||||||
|
|
||||||
|
// Validar si es Área Común (insensible a mayúsculas/minúsculas)
|
||||||
|
const esAreaComun =
|
||||||
|
nombreNormalizado.toLowerCase() === 'área común' ||
|
||||||
|
nombreNormalizado.toLowerCase() === 'area comun'
|
||||||
|
|
||||||
|
if (esAreaComun && hasAreaComun) {
|
||||||
|
alert('El Área Común ya ha sido agregada.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const nueva = {
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
nombre: nombreNormalizado,
|
||||||
|
orden: lineas.length + 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
setLineas([...lineas, nueva])
|
||||||
|
|
||||||
|
if (esAreaComun) {
|
||||||
|
setHasAreaComun(true)
|
||||||
|
}
|
||||||
|
setNombreNuevaLinea('') // Limpiar input
|
||||||
|
}
|
||||||
|
|
||||||
|
const tieneAreaComun = useMemo(() => {
|
||||||
|
return lineas.some(
|
||||||
|
(l) =>
|
||||||
|
l.nombre.toLowerCase() === 'área común' ||
|
||||||
|
l.nombre.toLowerCase() === 'area comun',
|
||||||
|
)
|
||||||
|
}, [lineas])
|
||||||
|
|
||||||
// 3. Sincronizar API -> Estado Local
|
// 3. Sincronizar API -> Estado Local
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -176,6 +224,25 @@ function MapaCurricularPage() {
|
|||||||
const ciclosTotales = 9
|
const ciclosTotales = 9
|
||||||
const ciclosArray = Array.from({ length: ciclosTotales }, (_, i) => i + 1)
|
const ciclosArray = Array.from({ length: ciclosTotales }, (_, i) => i + 1)
|
||||||
|
|
||||||
|
// Nuevo estado para controlar los datos temporales del modal de edición
|
||||||
|
const [editingData, setEditingData] = useState<Materia | null>(null)
|
||||||
|
|
||||||
|
// 1. FUNCION DE GUARDAR MODAL
|
||||||
|
const handleSaveChanges = () => {
|
||||||
|
if (!editingData) return
|
||||||
|
console.log(materias)
|
||||||
|
|
||||||
|
setMaterias((prev) =>
|
||||||
|
prev.map((m) => (m.id === editingData.id ? { ...editingData } : m)),
|
||||||
|
)
|
||||||
|
setIsEditModalOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. MODIFICACIÓN: Zona de soltado siempre visible
|
||||||
|
// Cambiamos la condición: Mostramos la sección si hay materias sin asignar
|
||||||
|
// O si simplemente queremos tener el "depósito" disponible.
|
||||||
|
const unassignedMaterias = materias.filter((m) => m.ciclo === null)
|
||||||
|
|
||||||
// --- Lógica de Gestión ---
|
// --- Lógica de Gestión ---
|
||||||
const agregarLinea = (nombre: string) => {
|
const agregarLinea = (nombre: string) => {
|
||||||
const nueva = { id: crypto.randomUUID(), nombre, orden: lineas.length + 1 }
|
const nueva = { id: crypto.randomUUID(), nombre, orden: lineas.length + 1 }
|
||||||
@@ -287,9 +354,39 @@ function MapaCurricularPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
<DropdownMenuItem onClick={() => agregarLinea('Nueva Línea')}>
|
{!tieneAreaComun && (
|
||||||
Nueva Línea Curricular
|
<>
|
||||||
</DropdownMenuItem>
|
<DropdownMenuItem
|
||||||
|
onClick={() => manejarAgregarLinea('Área Común')}
|
||||||
|
className="font-bold text-teal-700"
|
||||||
|
>
|
||||||
|
+ Agregar Área Común
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<div className="my-1 border-t border-slate-100" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{/* Input para nombre personalizado */}
|
||||||
|
<div className="p-2">
|
||||||
|
<label className="text-[10px] font-bold text-slate-400 uppercase">
|
||||||
|
Nombre de Línea
|
||||||
|
</label>
|
||||||
|
<div className="mt-1 flex gap-1">
|
||||||
|
<Input
|
||||||
|
value={nombreNuevaLinea}
|
||||||
|
onChange={(e) => setNombreNuevaLinea(e.target.value)}
|
||||||
|
placeholder="Ej: Optativas"
|
||||||
|
className="h-8 text-xs"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
className="h-8 px-2"
|
||||||
|
onClick={() => manejarAgregarLinea(nombreNuevaLinea)}
|
||||||
|
disabled={!nombreNuevaLinea.trim()}
|
||||||
|
>
|
||||||
|
<Plus size={14} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
@@ -367,7 +464,7 @@ function MapaCurricularPage() {
|
|||||||
isDragging={draggedMateria === m.id}
|
isDragging={draggedMateria === m.id}
|
||||||
onDragStart={handleDragStart}
|
onDragStart={handleDragStart}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedMateria(m)
|
setEditingData(m)
|
||||||
setIsEditModalOpen(true)
|
setIsEditModalOpen(true)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -416,77 +513,214 @@ function MapaCurricularPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Materias Sin Asignar */}
|
{/* Materias Sin Asignar */}
|
||||||
{materias.filter((m) => m.ciclo === null).length > 0 && (
|
{/* SECCIÓN DE MATERIAS SIN ASIGNAR (Mejorada para estar siempre disponible) */}
|
||||||
<div className="mt-10 rounded-2xl border border-slate-200 bg-slate-50 p-6">
|
<div className="mt-10 rounded-2xl border border-slate-200 bg-slate-50 p-6">
|
||||||
<div className="mb-4 flex items-center gap-2 text-amber-600">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
<AlertTriangle size={20} />
|
<div className="flex items-center gap-2 text-slate-600">
|
||||||
<h3 className="text-sm font-bold uppercase">
|
<h3 className="text-sm font-bold tracking-wider uppercase">
|
||||||
Materias pendientes (
|
Bandeja de Entrada / Materias sin asignar
|
||||||
{materias.filter((m) => m.ciclo === null).length})
|
|
||||||
</h3>
|
</h3>
|
||||||
|
<Badge variant="secondary">{unassignedMaterias.length}</Badge>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<p className="text-xs text-slate-400">
|
||||||
className="flex min-h-[100px] flex-wrap gap-4 rounded-xl border-2 border-dashed bg-white/50 p-4"
|
Arrastra una materia aquí para quitarla del mapa
|
||||||
onDragOver={handleDragOver}
|
</p>
|
||||||
onDrop={(e) => handleDrop(e, null, null)}
|
|
||||||
>
|
|
||||||
{materias
|
|
||||||
.filter((m) => m.ciclo === null)
|
|
||||||
.map((m) => (
|
|
||||||
<div key={m.id} className="w-[200px]">
|
|
||||||
<MateriaCardItem
|
|
||||||
materia={m}
|
|
||||||
isDragging={draggedMateria === m.id}
|
|
||||||
onDragStart={handleDragStart}
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedMateria(m)
|
|
||||||
setIsEditModalOpen(true)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
<div
|
||||||
|
className={`flex min-h-[120px] flex-wrap gap-4 rounded-xl border-2 border-dashed p-4 transition-colors ${
|
||||||
|
draggedMateria
|
||||||
|
? 'border-teal-300 bg-teal-50/50'
|
||||||
|
: 'border-slate-200 bg-white/50'
|
||||||
|
}`}
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
onDrop={(e) => handleDrop(e, null, null)} // Limpia ciclo y línea
|
||||||
|
>
|
||||||
|
{unassignedMaterias.map((m) => (
|
||||||
|
<div key={m.id} className="w-[200px]">
|
||||||
|
<MateriaCardItem
|
||||||
|
materia={m}
|
||||||
|
isDragging={draggedMateria === m.id}
|
||||||
|
onDragStart={handleDragStart}
|
||||||
|
onClick={() => {
|
||||||
|
setEditingData(m) // Cargamos los datos en el estado de edición
|
||||||
|
setIsEditModalOpen(true)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{unassignedMaterias.length === 0 && (
|
||||||
|
<div className="flex w-full items-center justify-center text-sm text-slate-400">
|
||||||
|
No hay materias pendientes. Arrastra una materia aquí para
|
||||||
|
desasignarla.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Modal de Edición */}
|
{/* Modal de Edición */}
|
||||||
<Dialog open={isEditModalOpen} onOpenChange={setIsEditModalOpen}>
|
<Dialog open={isEditModalOpen} onOpenChange={setIsEditModalOpen}>
|
||||||
<DialogContent className="sm:max-w-[500px]">
|
<DialogContent className="sm:max-w-[550px]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Editar Materia</DialogTitle>
|
<DialogTitle className="font-bold text-slate-700">
|
||||||
|
Editar Materia
|
||||||
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{selectedMateria && (
|
|
||||||
<div className="grid grid-cols-2 gap-4 py-4">
|
{/* Verificación de seguridad: solo renderiza si hay datos */}
|
||||||
<div className="space-y-2">
|
{editingData ? (
|
||||||
<label className="text-xs font-bold uppercase">Clave</label>
|
<div className="grid gap-4 py-4">
|
||||||
<Input defaultValue={selectedMateria.clave} />
|
{/* Fila 1: Clave y Nombre */}
|
||||||
</div>
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-xs font-bold uppercase">Nombre</label>
|
|
||||||
<Input defaultValue={selectedMateria.nombre} />
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-xs font-bold uppercase">Créditos</label>
|
|
||||||
<Input type="number" defaultValue={selectedMateria.creditos} />
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-xs font-bold uppercase">HD</label>
|
<label className="text-xs font-bold text-slate-500 uppercase">
|
||||||
<Input type="number" defaultValue={selectedMateria.hd} />
|
Clave
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
value={editingData.clave}
|
||||||
|
onChange={(e) =>
|
||||||
|
setEditingData({ ...editingData, clave: e.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-xs font-bold uppercase">HI</label>
|
<label className="text-xs font-bold text-slate-500 uppercase">
|
||||||
<Input type="number" defaultValue={selectedMateria.hi} />
|
Nombre
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
value={editingData.nombre}
|
||||||
|
onChange={(e) =>
|
||||||
|
setEditingData({ ...editingData, nombre: e.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Fila 2: Créditos y Horas */}
|
||||||
|
<div className="grid grid-cols-3 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-xs font-bold text-slate-500 uppercase">
|
||||||
|
Créditos
|
||||||
|
</label>
|
||||||
|
<Input type="number" value={editingData.creditos} />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-xs font-bold text-slate-500 uppercase">
|
||||||
|
HD (Horas Docente)
|
||||||
|
</label>
|
||||||
|
<Input type="number" value={editingData.hd} />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-xs font-bold text-slate-500 uppercase">
|
||||||
|
HI (Horas Indep.)
|
||||||
|
</label>
|
||||||
|
<Input type="number" value={editingData.hi} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Fila 3: Ciclo y Línea */}
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-xs font-bold text-slate-500 uppercase">
|
||||||
|
Ciclo
|
||||||
|
</label>
|
||||||
|
<Select value={editingData.ciclo?.toString() || 'null'}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{ciclosArray.map((n) => (
|
||||||
|
<SelectItem key={n} value={n.toString()}>
|
||||||
|
Ciclo {n}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-xs font-bold text-slate-500 uppercase">
|
||||||
|
Línea Curricular
|
||||||
|
</label>
|
||||||
|
<Select value={editingData.lineaCurricularId || 'null'}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{lineas.map((l) => (
|
||||||
|
<SelectItem key={l.id} value={l.id}>
|
||||||
|
{l.nombre}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Fila 4: Seriación (Igual a tu imagen) */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-xs font-bold text-slate-500 uppercase">
|
||||||
|
Seriación (Prerrequisitos)
|
||||||
|
</label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Seleccionar materia..." />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{materias.map((m) => (
|
||||||
|
<SelectItem key={m.id} value={m.clave}>
|
||||||
|
{m.nombre}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<div className="mt-2 flex gap-2">
|
||||||
|
{/* Aquí usamos el array vacío que inicializamos en el mapeador */}
|
||||||
|
{editingData.prerrequisitos.map((pre) => (
|
||||||
|
<Badge
|
||||||
|
key={pre}
|
||||||
|
variant="secondary"
|
||||||
|
className="bg-slate-100 text-slate-600"
|
||||||
|
>
|
||||||
|
{pre} <span className="ml-1 cursor-pointer">×</span>
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Fila 5: Tipo */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-xs font-bold text-slate-500 uppercase">
|
||||||
|
Tipo
|
||||||
|
</label>
|
||||||
|
<Select value={editingData.tipo}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="obligatoria">Obligatoria</SelectItem>
|
||||||
|
<SelectItem value="optativa">Optativa</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4 flex justify-end gap-3">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setIsEditModalOpen(false)}
|
||||||
|
>
|
||||||
|
Cancelar
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="bg-teal-700 text-white"
|
||||||
|
onClick={handleSaveChanges}
|
||||||
|
>
|
||||||
|
Guardar
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="py-20 text-center">No hay datos seleccionados</div>
|
||||||
)}
|
)}
|
||||||
<div className="mt-4 flex justify-end gap-3">
|
|
||||||
<Button variant="outline" onClick={() => setIsEditModalOpen(false)}>
|
|
||||||
Cancelar
|
|
||||||
</Button>
|
|
||||||
<Button className="bg-teal-700 text-white">Guardar Cambios</Button>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,102 +1,107 @@
|
|||||||
export type PlanStatus =
|
export type PlanStatus =
|
||||||
| 'borrador'
|
| 'borrador'
|
||||||
| 'revision'
|
| 'revision'
|
||||||
| 'expertos'
|
| 'expertos'
|
||||||
| 'consejo'
|
| 'consejo'
|
||||||
| 'aprobado'
|
| 'aprobado'
|
||||||
| 'rechazado';
|
| 'rechazado'
|
||||||
|
|
||||||
export type TipoPlan = 'Licenciatura' | 'Maestría' | 'Doctorado' | 'Especialidad';
|
export type TipoPlan =
|
||||||
|
| 'Licenciatura'
|
||||||
|
| 'Maestría'
|
||||||
|
| 'Doctorado'
|
||||||
|
| 'Especialidad'
|
||||||
|
|
||||||
export type TipoMateria = 'obligatoria' | 'optativa' | 'troncal';
|
export type TipoMateria = 'obligatoria' | 'optativa' | 'troncal'
|
||||||
|
|
||||||
export type MateriaStatus = 'borrador' | 'revisada' | 'aprobada';
|
export type MateriaStatus = 'borrador' | 'revisada' | 'aprobada'
|
||||||
|
|
||||||
export interface Facultad {
|
export interface Facultad {
|
||||||
id: string;
|
id: string
|
||||||
nombre: string;
|
nombre: string
|
||||||
color: string;
|
color: string
|
||||||
icono: string;
|
icono: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Carrera {
|
export interface Carrera {
|
||||||
id: string;
|
id: string
|
||||||
nombre: string;
|
nombre: string
|
||||||
facultadId: string;
|
facultadId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LineaCurricular {
|
export interface LineaCurricular {
|
||||||
id: string;
|
id: string
|
||||||
nombre: string;
|
nombre: string
|
||||||
orden: number;
|
orden: number
|
||||||
color?: string;
|
color?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Materia {
|
export interface Materia {
|
||||||
id: string;
|
id: string
|
||||||
clave: string;
|
clave: string
|
||||||
nombre: string;
|
nombre: string
|
||||||
creditos: number;
|
creditos: number
|
||||||
ciclo: number | null;
|
ciclo: number | null
|
||||||
lineaCurricularId: string | null;
|
lineaCurricularId: string | null
|
||||||
tipo: TipoMateria;
|
tipo: TipoMateria
|
||||||
estado: MateriaStatus;
|
estado: MateriaStatus
|
||||||
orden?: number;
|
orden?: number
|
||||||
hd: number; // <--- Añadir
|
hd: number // <--- Añadir
|
||||||
hi: number; // <--- Añadir
|
hi: number // <--- Añadir
|
||||||
|
prerrequisitos: Array<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Plan {
|
export interface Plan {
|
||||||
id: string;
|
id: string
|
||||||
nombre: string;
|
nombre: string
|
||||||
carrera: Carrera;
|
carrera: Carrera
|
||||||
facultad: Facultad;
|
facultad: Facultad
|
||||||
tipoPlan: TipoPlan;
|
tipoPlan: TipoPlan
|
||||||
nivel?: string;
|
nivel?: string
|
||||||
modalidad?: string;
|
modalidad?: string
|
||||||
duracionCiclos: number;
|
duracionCiclos: number
|
||||||
creditosTotales: number;
|
creditosTotales: number
|
||||||
fechaCreacion: string;
|
fechaCreacion: string
|
||||||
estadoActual: PlanStatus;
|
estadoActual: PlanStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DatosGeneralesField {
|
export interface DatosGeneralesField {
|
||||||
id: string;
|
id: string
|
||||||
label: string;
|
label: string
|
||||||
value: string;
|
value: string
|
||||||
tipo: 'texto' | 'lista' | 'parrafo';
|
tipo: 'texto' | 'lista' | 'parrafo'
|
||||||
requerido: boolean;
|
requerido: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CambioPlan {
|
export interface CambioPlan {
|
||||||
id: string;
|
id: string
|
||||||
fecha: string;
|
fecha: string
|
||||||
usuario: string;
|
usuario: string
|
||||||
tab: string;
|
tab: string
|
||||||
descripcion: string;
|
descripcion: string
|
||||||
detalle?: string;
|
detalle?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ComentarioFlujo {
|
export interface ComentarioFlujo {
|
||||||
id: string;
|
id: string
|
||||||
usuario: string;
|
usuario: string
|
||||||
fecha: string;
|
fecha: string
|
||||||
texto: string;
|
texto: string
|
||||||
fase: PlanStatus;
|
fase: PlanStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DocumentoPlan {
|
export interface DocumentoPlan {
|
||||||
id: string;
|
id: string
|
||||||
fechaGeneracion: string;
|
fechaGeneracion: string
|
||||||
version: number;
|
version: number
|
||||||
url?: string;
|
url?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PlanTab =
|
export type PlanTab =
|
||||||
| 'datos-generales'
|
| 'datos-generales'
|
||||||
| 'mapa-curricular'
|
| 'mapa-curricular'
|
||||||
| 'materias'
|
| 'materias'
|
||||||
| 'flujo'
|
| 'flujo'
|
||||||
| 'ia'
|
| 'ia'
|
||||||
| 'documento'
|
| 'documento'
|
||||||
| 'historial';
|
| 'historial'
|
||||||
|
|||||||
Reference in New Issue
Block a user