Se agrega persistencia en tab de datos y mapa curricular
fix #42 fix #54
This commit is contained in:
@@ -165,7 +165,7 @@ export async function plan_asignaturas_list(
|
|||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from('asignaturas')
|
.from('asignaturas')
|
||||||
.select(
|
.select(
|
||||||
'id,plan_estudio_id,estructura_id,facultad_propietaria_id,codigo,nombre,tipo,creditos,horas_semana,numero_ciclo,linea_plan_id,orden_celda,datos,contenido_tematico,tipo_origen,meta_origen,creado_por,actualizado_por,creado_en,actualizado_en',
|
'id,plan_estudio_id,horas_academicas, horas_independientes,estructura_id,codigo,nombre,tipo,creditos,numero_ciclo,linea_plan_id,orden_celda,datos,contenido_tematico,tipo_origen,meta_origen,creado_por,actualizado_por,creado_en,actualizado_en',
|
||||||
)
|
)
|
||||||
.eq('plan_estudio_id', planId)
|
.eq('plan_estudio_id', planId)
|
||||||
.order('numero_ciclo', { ascending: true, nullsFirst: false })
|
.order('numero_ciclo', { ascending: true, nullsFirst: false })
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { supabaseBrowser } from '../supabase/client'
|
import { supabaseBrowser } from '../supabase/client'
|
||||||
import { invokeEdge } from '../supabase/invokeEdge'
|
import { invokeEdge } from '../supabase/invokeEdge'
|
||||||
|
|
||||||
import { throwIfError, requireData } from './_helpers'
|
import { throwIfError, requireData } from './_helpers'
|
||||||
|
|
||||||
|
import type { DocumentoResult } from './plans.api'
|
||||||
import type {
|
import type {
|
||||||
Asignatura,
|
Asignatura,
|
||||||
BibliografiaAsignatura,
|
BibliografiaAsignatura,
|
||||||
@@ -8,7 +11,6 @@ import type {
|
|||||||
TipoAsignatura,
|
TipoAsignatura,
|
||||||
UUID,
|
UUID,
|
||||||
} from '../types/domain'
|
} from '../types/domain'
|
||||||
import type { DocumentoResult } from './plans.api'
|
|
||||||
|
|
||||||
const EDGE = {
|
const EDGE = {
|
||||||
subjects_create_manual: 'subjects_create_manual',
|
subjects_create_manual: 'subjects_create_manual',
|
||||||
@@ -32,7 +34,7 @@ export async function subjects_get(subjectId: UUID): Promise<Asignatura> {
|
|||||||
.from('asignaturas')
|
.from('asignaturas')
|
||||||
.select(
|
.select(
|
||||||
`
|
`
|
||||||
id,plan_estudio_id,estructura_id,facultad_propietaria_id,codigo,nombre,tipo,creditos,horas_semana,numero_ciclo,linea_plan_id,orden_celda,datos,contenido_tematico,tipo_origen,meta_origen,creado_por,actualizado_por,creado_en,actualizado_en,
|
id,plan_estudio_id,estructura_id,codigo,nombre,tipo,creditos,numero_ciclo,linea_plan_id,orden_celda,datos,contenido_tematico,tipo_origen,meta_origen,creado_por,actualizado_por,creado_en,actualizado_en,
|
||||||
planes_estudio(
|
planes_estudio(
|
||||||
id,carrera_id,estructura_id,nombre,nivel,tipo_ciclo,numero_ciclos,datos,estado_actual_id,activo,tipo_origen,meta_origen,creado_por,actualizado_por,creado_en,actualizado_en,
|
id,carrera_id,estructura_id,nombre,nivel,tipo_ciclo,numero_ciclos,datos,estado_actual_id,activo,tipo_origen,meta_origen,creado_por,actualizado_por,creado_en,actualizado_en,
|
||||||
carreras(id,facultad_id,nombre,nombre_corto,clave_sep,activa, facultades(id,nombre,nombre_corto,color,icono))
|
carreras(id,facultad_id,nombre,nombre_corto,clave_sep,activa, facultades(id,nombre,nombre_corto,color,icono))
|
||||||
@@ -49,7 +51,7 @@ export async function subjects_get(subjectId: UUID): Promise<Asignatura> {
|
|||||||
|
|
||||||
export async function subjects_history(
|
export async function subjects_history(
|
||||||
subjectId: UUID,
|
subjectId: UUID,
|
||||||
): Promise<CambioAsignatura[]> {
|
): Promise<Array<CambioAsignatura>> {
|
||||||
const supabase = supabaseBrowser()
|
const supabase = supabaseBrowser()
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from('cambios_asignatura')
|
.from('cambios_asignatura')
|
||||||
@@ -65,7 +67,7 @@ export async function subjects_history(
|
|||||||
|
|
||||||
export async function subjects_bibliografia_list(
|
export async function subjects_bibliografia_list(
|
||||||
subjectId: UUID,
|
subjectId: UUID,
|
||||||
): Promise<BibliografiaAsignatura[]> {
|
): Promise<Array<BibliografiaAsignatura>> {
|
||||||
const supabase = supabaseBrowser()
|
const supabase = supabaseBrowser()
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from('bibliografia_asignatura')
|
.from('bibliografia_asignatura')
|
||||||
@@ -112,9 +114,9 @@ export async function ai_generate_subject(payload: {
|
|||||||
iaConfig: {
|
iaConfig: {
|
||||||
descripcionEnfoque: string
|
descripcionEnfoque: string
|
||||||
notasAdicionales?: string
|
notasAdicionales?: string
|
||||||
archivosExistentesIds?: UUID[]
|
archivosExistentesIds?: Array<UUID>
|
||||||
repositoriosIds?: UUID[]
|
repositoriosIds?: Array<UUID>
|
||||||
archivosAdhocIds?: UUID[]
|
archivosAdhocIds?: Array<UUID>
|
||||||
usarMCP?: boolean
|
usarMCP?: boolean
|
||||||
}
|
}
|
||||||
}): Promise<any> {
|
}): Promise<any> {
|
||||||
@@ -145,7 +147,7 @@ export async function subjects_clone_from_existing(payload: {
|
|||||||
export async function subjects_import_from_file(payload: {
|
export async function subjects_import_from_file(payload: {
|
||||||
planId: UUID
|
planId: UUID
|
||||||
archivoWordAsignaturaId: UUID
|
archivoWordAsignaturaId: UUID
|
||||||
archivosAdicionalesIds?: UUID[]
|
archivosAdicionalesIds?: Array<UUID>
|
||||||
}): Promise<Asignatura> {
|
}): Promise<Asignatura> {
|
||||||
return invokeEdge<Asignatura>(EDGE.subjects_import_from_file, payload)
|
return invokeEdge<Asignatura>(EDGE.subjects_import_from_file, payload)
|
||||||
}
|
}
|
||||||
@@ -175,7 +177,7 @@ export async function subjects_update_fields(
|
|||||||
|
|
||||||
export async function subjects_update_contenido(
|
export async function subjects_update_contenido(
|
||||||
subjectId: UUID,
|
subjectId: UUID,
|
||||||
unidades: any[],
|
unidades: Array<any>,
|
||||||
): Promise<Asignatura> {
|
): Promise<Asignatura> {
|
||||||
return invokeEdge<Asignatura>(EDGE.subjects_update_contenido, {
|
return invokeEdge<Asignatura>(EDGE.subjects_update_contenido, {
|
||||||
subjectId,
|
subjectId,
|
||||||
@@ -224,3 +226,20 @@ export async function subjects_get_document(
|
|||||||
subjectId,
|
subjectId,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function asignaturas_update(
|
||||||
|
asignaturaId: UUID,
|
||||||
|
patch: Partial<Asignatura>, // O tu tipo específico para el Patch de materias
|
||||||
|
): Promise<Asignatura> {
|
||||||
|
const supabase = supabaseBrowser()
|
||||||
|
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('asignaturas')
|
||||||
|
.update(patch)
|
||||||
|
.eq('id', asignaturaId)
|
||||||
|
.select() // Trae la materia actualizada
|
||||||
|
.single()
|
||||||
|
|
||||||
|
throwIfError(error)
|
||||||
|
return requireData(data, 'No se pudo actualizar la asignatura.')
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { qk } from '../query/keys'
|
|
||||||
import type { UUID } from '../types/domain'
|
|
||||||
import type {
|
|
||||||
BibliografiaUpsertInput,
|
|
||||||
SubjectsCreateManualInput,
|
|
||||||
SubjectsUpdateFieldsPatch,
|
|
||||||
} from '../api/subjects.api'
|
|
||||||
import {
|
import {
|
||||||
ai_generate_subject,
|
ai_generate_subject,
|
||||||
|
asignaturas_update,
|
||||||
subjects_bibliografia_list,
|
subjects_bibliografia_list,
|
||||||
subjects_clone_from_existing,
|
subjects_clone_from_existing,
|
||||||
subjects_create_manual,
|
subjects_create_manual,
|
||||||
@@ -21,6 +16,14 @@ import {
|
|||||||
subjects_update_contenido,
|
subjects_update_contenido,
|
||||||
subjects_update_fields,
|
subjects_update_fields,
|
||||||
} from '../api/subjects.api'
|
} from '../api/subjects.api'
|
||||||
|
import { qk } from '../query/keys'
|
||||||
|
|
||||||
|
import type {
|
||||||
|
BibliografiaUpsertInput,
|
||||||
|
SubjectsCreateManualInput,
|
||||||
|
SubjectsUpdateFieldsPatch,
|
||||||
|
} from '../api/subjects.api'
|
||||||
|
import type { UUID } from '../types/domain'
|
||||||
|
|
||||||
export function useSubject(subjectId: UUID | null | undefined) {
|
export function useSubject(subjectId: UUID | null | undefined) {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
@@ -159,7 +162,7 @@ export function useUpdateSubjectContenido() {
|
|||||||
const qc = useQueryClient()
|
const qc = useQueryClient()
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (vars: { subjectId: UUID; unidades: any[] }) =>
|
mutationFn: (vars: { subjectId: UUID; unidades: Array<any> }) =>
|
||||||
subjects_update_contenido(vars.subjectId, vars.unidades),
|
subjects_update_contenido(vars.subjectId, vars.unidades),
|
||||||
onSuccess: (updated) => {
|
onSuccess: (updated) => {
|
||||||
qc.setQueryData(qk.asignatura(updated.id), updated)
|
qc.setQueryData(qk.asignatura(updated.id), updated)
|
||||||
@@ -194,3 +197,28 @@ export function useGenerateSubjectDocumento() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useUpdateAsignatura() {
|
||||||
|
const qc = useQueryClient()
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (vars: {
|
||||||
|
asignaturaId: UUID
|
||||||
|
patch: Partial<SubjectsUpdateFieldsPatch>
|
||||||
|
}) => asignaturas_update(vars.asignaturaId, vars.patch),
|
||||||
|
|
||||||
|
onSuccess: (updated) => {
|
||||||
|
// 1. Actualizamos la materia específica en la caché si tienes un query de "detalle"
|
||||||
|
qc.setQueryData(['asignatura', updated.id], updated)
|
||||||
|
|
||||||
|
// 2. IMPORTANTÍSIMO: Invalidamos la lista de materias del plan
|
||||||
|
// para que el mapa curricular vea los cambios (créditos, horas, nombre, etc.)
|
||||||
|
qc.invalidateQueries({
|
||||||
|
queryKey: ['plan_asignaturas', updated.plan_estudio_id],
|
||||||
|
})
|
||||||
|
|
||||||
|
// 3. Si tienes una lista general de asignaturas, también la invalidamos
|
||||||
|
qc.invalidateQueries({ queryKey: ['asignaturas', 'list'] })
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select'
|
} from '@/components/ui/select'
|
||||||
import { usePlanAsignaturas, usePlanLineas } from '@/data'
|
import { usePlanAsignaturas, usePlanLineas, useUpdateAsignatura } from '@/data'
|
||||||
|
|
||||||
// --- Mapeadores (Fuera del componente para mayor limpieza) ---
|
// --- Mapeadores (Fuera del componente para mayor limpieza) ---
|
||||||
const mapLineasToLineaCurricular = (
|
const mapLineasToLineaCurricular = (
|
||||||
@@ -60,8 +60,9 @@ const mapAsignaturasToAsignaturas = (
|
|||||||
tipo: asig.tipo === 'OBLIGATORIA' ? 'obligatoria' : 'optativa',
|
tipo: asig.tipo === 'OBLIGATORIA' ? 'obligatoria' : 'optativa',
|
||||||
estado: 'borrador',
|
estado: 'borrador',
|
||||||
orden: asig.orden_celda ?? 0,
|
orden: asig.orden_celda ?? 0,
|
||||||
hd: Math.floor((asig.horas_semana ?? 0) / 2),
|
// Mapeo directo de los nuevos campos de la API
|
||||||
hi: Math.ceil((asig.horas_semana ?? 0) / 2),
|
hd: asig.horas_academicas ?? 0,
|
||||||
|
hi: asig.horas_independientes ?? 0,
|
||||||
prerrequisitos: [],
|
prerrequisitos: [],
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@@ -183,6 +184,7 @@ function MapaCurricularPage() {
|
|||||||
useState<Asignatura | null>(null)
|
useState<Asignatura | null>(null)
|
||||||
const [hasAreaComun, setHasAreaComun] = useState(false)
|
const [hasAreaComun, setHasAreaComun] = useState(false)
|
||||||
const [nombreNuevaLinea, setNombreNuevaLinea] = useState('') // Para el input de nombre personalizado
|
const [nombreNuevaLinea, setNombreNuevaLinea] = useState('') // Para el input de nombre personalizado
|
||||||
|
const { mutate: updateAsignatura, isPending } = useUpdateAsignatura()
|
||||||
|
|
||||||
const manejarAgregarLinea = (nombre: string) => {
|
const manejarAgregarLinea = (nombre: string) => {
|
||||||
const nombreNormalizado = nombre.trim()
|
const nombreNormalizado = nombre.trim()
|
||||||
@@ -268,13 +270,41 @@ function MapaCurricularPage() {
|
|||||||
setAsignaturas((prev) =>
|
setAsignaturas((prev) =>
|
||||||
prev.map((m) => (m.id === editingData.id ? { ...editingData } : m)),
|
prev.map((m) => (m.id === editingData.id ? { ...editingData } : m)),
|
||||||
)
|
)
|
||||||
|
// setIsEditModalOpen(false)
|
||||||
|
// Preparamos el patch con la estructura de tu tabla
|
||||||
|
const patch = {
|
||||||
|
nombre: editingData.nombre,
|
||||||
|
codigo: editingData.clave,
|
||||||
|
creditos: editingData.creditos,
|
||||||
|
horas_academicas: editingData.hd,
|
||||||
|
horas_independientes: editingData.hi,
|
||||||
|
numero_ciclo: editingData.ciclo,
|
||||||
|
linea_plan_id: editingData.lineaCurricularId,
|
||||||
|
tipo: editingData.tipo.toUpperCase(), // Asegurar que coincida con el ENUM (OBLIGATORIA/OPTATIVA)
|
||||||
|
// datos: editingData.datos, // Si editaste algo del JSONB
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAsignatura(
|
||||||
|
{ asignaturaId: editingData.id, patch },
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
setIsEditModalOpen(false)
|
setIsEditModalOpen(false)
|
||||||
|
// Opcional: Mostrar un toast de éxito
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.error('Error al guardar:', error)
|
||||||
|
alert('Hubo un error al guardar los cambios.')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. MODIFICACIÓN: Zona de soltado siempre visible
|
// 2. MODIFICACIÓN: Zona de soltado siempre visible
|
||||||
// Cambiamos la condición: Mostramos la sección si hay asignaturas sin asignar
|
// Cambiamos la condición: Mostramos la sección si hay asignaturas sin asignar
|
||||||
// O si simplemente queremos tener el "depósito" disponible.
|
// O si simplemente queremos tener el "depósito" disponible.
|
||||||
const unassignedAsignaturas = asignaturas.filter((m) => m.ciclo === null)
|
const unassignedAsignaturas = asignaturas.filter(
|
||||||
|
(m) => m.ciclo === null || m.lineaCurricularId === null,
|
||||||
|
)
|
||||||
|
|
||||||
// --- Lógica de Gestión ---
|
// --- Lógica de Gestión ---
|
||||||
const agregarLinea = (nombre: string) => {
|
const agregarLinea = (nombre: string) => {
|
||||||
@@ -309,7 +339,7 @@ function MapaCurricularPage() {
|
|||||||
|
|
||||||
const getSubtotalLinea = (lineaId: string) => {
|
const getSubtotalLinea = (lineaId: string) => {
|
||||||
return asignaturas
|
return asignaturas
|
||||||
.filter((m) => m.lineaCurricularId === lineaId && m.ciclo !== null)
|
.filter((m) => m.lineaCurricularId === lineaId && m.ciclo !== null) // Aseguramos que pertenezca a la línea Y tenga ciclo
|
||||||
.reduce(
|
.reduce(
|
||||||
(acc, m) => ({
|
(acc, m) => ({
|
||||||
cr: acc.cr + (m.creditos || 0),
|
cr: acc.cr + (m.creditos || 0),
|
||||||
@@ -332,6 +362,7 @@ function MapaCurricularPage() {
|
|||||||
) => {
|
) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (draggedAsignatura) {
|
if (draggedAsignatura) {
|
||||||
|
// 1. Actualización optimista del UI
|
||||||
setAsignaturas((prev) =>
|
setAsignaturas((prev) =>
|
||||||
prev.map((m) =>
|
prev.map((m) =>
|
||||||
m.id === draggedAsignatura
|
m.id === draggedAsignatura
|
||||||
@@ -339,6 +370,23 @@ function MapaCurricularPage() {
|
|||||||
: m,
|
: m,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 2. Persistir en la API
|
||||||
|
const patch = {
|
||||||
|
numero_ciclo: ciclo,
|
||||||
|
linea_plan_id: lineaId,
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAsignatura(
|
||||||
|
{ asignaturaId: draggedAsignatura, patch },
|
||||||
|
{
|
||||||
|
onError: (error) => {
|
||||||
|
console.error('Error al mover:', error)
|
||||||
|
// Opcional: Revertir el estado local si falla
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
setDraggedAsignatura(null)
|
setDraggedAsignatura(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -373,10 +421,15 @@ function MapaCurricularPage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
{asignaturas.filter((m) => !m.ciclo).length > 0 && (
|
{asignaturas.filter((m) => !m.ciclo || !m.lineaCurricularId).length >
|
||||||
|
0 && (
|
||||||
<Badge className="border-amber-100 bg-amber-50 text-amber-600 hover:bg-amber-50">
|
<Badge className="border-amber-100 bg-amber-50 text-amber-600 hover:bg-amber-50">
|
||||||
<AlertTriangle size={14} className="mr-1" />{' '}
|
<AlertTriangle size={14} className="mr-1" />{' '}
|
||||||
{asignaturas.filter((m) => !m.ciclo).length} sin asignar
|
{
|
||||||
|
asignaturas.filter((m) => !m.ciclo || !m.lineaCurricularId)
|
||||||
|
.length
|
||||||
|
}{' '}
|
||||||
|
sin asignar
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
@@ -482,6 +535,8 @@ function MapaCurricularPage() {
|
|||||||
<div
|
<div
|
||||||
key={ciclo}
|
key={ciclo}
|
||||||
onDragOver={handleDragOver}
|
onDragOver={handleDragOver}
|
||||||
|
// ANTES: onDrop={(e) => handleDrop(e, null, null)}
|
||||||
|
// AHORA: Usamos las variables 'ciclo' y 'linea.id' del map
|
||||||
onDrop={(e) => handleDrop(e, ciclo, linea.id)}
|
onDrop={(e) => handleDrop(e, ciclo, linea.id)}
|
||||||
className="min-h-[140px] space-y-2 rounded-xl border-2 border-dashed border-slate-100 bg-slate-50/20 p-2"
|
className="min-h-[140px] space-y-2 rounded-xl border-2 border-dashed border-slate-100 bg-slate-50/20 p-2"
|
||||||
>
|
>
|
||||||
@@ -593,7 +648,10 @@ function MapaCurricularPage() {
|
|||||||
|
|
||||||
{/* Modal de Edición */}
|
{/* Modal de Edición */}
|
||||||
<Dialog open={isEditModalOpen} onOpenChange={setIsEditModalOpen}>
|
<Dialog open={isEditModalOpen} onOpenChange={setIsEditModalOpen}>
|
||||||
<DialogContent className="sm:max-w-[550px]">
|
<DialogContent
|
||||||
|
className="sm:max-w-[550px]"
|
||||||
|
onInteractOutside={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="font-bold text-slate-700">
|
<DialogTitle className="font-bold text-slate-700">
|
||||||
Editar Asignatura
|
Editar Asignatura
|
||||||
@@ -739,32 +797,61 @@ function MapaCurricularPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Fila 4: Seriación (Igual a tu imagen) */}
|
{/* Fila 4: Seriación (Prerrequisitos) */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-xs font-bold text-slate-500 uppercase">
|
<label className="text-xs font-bold text-slate-500 uppercase">
|
||||||
Seriación (Prerrequisitos)
|
Seriación (Prerrequisitos)
|
||||||
</label>
|
</label>
|
||||||
<Select>
|
<Select
|
||||||
|
onValueChange={(val) => {
|
||||||
|
// Evitamos duplicados en la lista de prerrequisitos local
|
||||||
|
if (!editingData.prerrequisitos.includes(val)) {
|
||||||
|
setEditingData({
|
||||||
|
...editingData,
|
||||||
|
prerrequisitos: [...editingData.prerrequisitos, val],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Seleccionar asignatura..." />
|
<SelectValue placeholder="Seleccionar asignatura..." />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{asignaturas.map((m) => (
|
{/* FILTRO CLAVE:
|
||||||
|
Solo mostramos materias cuyo ID sea diferente al de la materia que estamos editando
|
||||||
|
*/}
|
||||||
|
{asignaturas
|
||||||
|
.filter((m) => m.id !== editingData.id)
|
||||||
|
.map((m) => (
|
||||||
<SelectItem key={m.id} value={m.clave}>
|
<SelectItem key={m.id} value={m.clave}>
|
||||||
{m.nombre}
|
{m.nombre} ({m.clave})
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<div className="mt-2 flex gap-2">
|
|
||||||
{/* Aquí usamos el array vacío que inicializamos en el mapeador */}
|
{/* Visualización de los prerrequisitos seleccionados */}
|
||||||
|
<div className="mt-2 flex flex-wrap gap-2">
|
||||||
{editingData.prerrequisitos.map((pre) => (
|
{editingData.prerrequisitos.map((pre) => (
|
||||||
<Badge
|
<Badge
|
||||||
key={pre}
|
key={pre}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="bg-slate-100 text-slate-600"
|
className="bg-slate-100 text-slate-600"
|
||||||
>
|
>
|
||||||
{pre} <span className="ml-1 cursor-pointer">×</span>
|
{pre}
|
||||||
|
<button
|
||||||
|
className="ml-1 hover:text-red-500"
|
||||||
|
onClick={() => {
|
||||||
|
setEditingData({
|
||||||
|
...editingData,
|
||||||
|
prerrequisitos: editingData.prerrequisitos.filter(
|
||||||
|
(p) => p !== pre,
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user