refactor: rename Materia to Asignatura across the codebase
- Updated type definitions and interfaces to replace 'Materia' with 'Asignatura'. - Refactored components and routes to reflect the new naming convention. - Adjusted related types and constants for consistency. - Removed the old Materia type definition and added Asignatura type definition. - Ensured all references in UI components and logic are updated accordingly. fix #50
This commit is contained in:
@@ -1,137 +0,0 @@
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
import { Pencil, X } from 'lucide-react';
|
||||
export type Materia = {
|
||||
id: string;
|
||||
clave: string;
|
||||
nombre: string;
|
||||
creditos: number;
|
||||
hd: number; // Horas Docente
|
||||
hi: number; // Horas Independientes
|
||||
tipo: 'Obligatoria' | 'Optativa' | 'Especialidad';
|
||||
ciclo: number;
|
||||
linea: string;
|
||||
estado: string;
|
||||
};
|
||||
|
||||
interface MateriaCardProps {
|
||||
materia: Materia;
|
||||
}
|
||||
|
||||
export function MateriaCard({ materia }: MateriaCardProps) {
|
||||
return (
|
||||
<Dialog.Root>
|
||||
{/* Trigger: La tarjeta en sí misma */}
|
||||
<Dialog.Trigger asChild>
|
||||
<div className="group relative flex flex-col p-2 mb-2 rounded-lg border border-slate-200 bg-white hover:border-emerald-500 hover:shadow-md transition-all cursor-pointer select-none">
|
||||
{/* Header de la tarjeta */}
|
||||
<div className="flex justify-between items-start mb-1">
|
||||
<span className="text-[9px] font-mono font-bold text-slate-400 uppercase">{materia.clave}</span>
|
||||
<div className="flex gap-1">
|
||||
<span className="px-1.5 py-0.5 rounded-full bg-emerald-100 text-emerald-700 text-[8px] font-bold uppercase">
|
||||
{materia.tipo === 'Obligatoria' ? 'OB' : 'OP'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Nombre */}
|
||||
<h4 className="text-[11px] font-semibold text-slate-800 leading-tight mb-2 min-h-[2rem]">
|
||||
{materia.nombre}
|
||||
</h4>
|
||||
|
||||
{/* Footer de la tarjeta (Créditos y Horas) */}
|
||||
<div className="flex justify-between items-center text-[9px] text-slate-500 border-t pt-1 border-slate-50">
|
||||
<span>{materia.creditos} cr</span>
|
||||
<div className="flex gap-1">
|
||||
<span>HD:{materia.hd}</span>
|
||||
<span>HI:{materia.hi}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Overlay de Hover (Opcional: un iconito de editar) */}
|
||||
<div className="absolute top-1 right-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<Pencil className="w-3 h-3 text-emerald-600" />
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Trigger>
|
||||
|
||||
{/* Modal / Portal */}
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay className="fixed inset-0 bg-black/40 backdrop-blur-sm z-50 animate-in fade-in" />
|
||||
<Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-md bg-white rounded-xl shadow-2xl p-6 z-50 border border-slate-200 animate-in zoom-in-95">
|
||||
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<Dialog.Title className="text-lg font-bold text-slate-800">Editar Materia</Dialog.Title>
|
||||
<Dialog.Close className="text-slate-400 hover:text-slate-600 transition-colors">
|
||||
<X className="w-5 h-5" />
|
||||
</Dialog.Close>
|
||||
</div>
|
||||
|
||||
<form className="space-y-4">
|
||||
{/* Clave y Nombre */}
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-xs font-bold text-slate-600 uppercase">Clave</label>
|
||||
<input
|
||||
defaultValue={materia.clave}
|
||||
className="px-3 py-2 rounded-lg border border-slate-300 focus:ring-2 focus:ring-emerald-500 outline-none text-sm font-mono"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2 flex flex-col gap-1.5">
|
||||
<label className="text-xs font-bold text-slate-600 uppercase">Nombre</label>
|
||||
<input
|
||||
defaultValue={materia.nombre}
|
||||
className="px-3 py-2 rounded-lg border border-slate-300 focus:ring-2 focus:ring-emerald-500 outline-none text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Créditos y Horas */}
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-xs font-bold text-slate-600 uppercase italic">Créditos</label>
|
||||
<input type="number" defaultValue={materia.creditos} className="px-3 py-2 rounded-lg border border-slate-300 text-sm" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-xs font-bold text-slate-600 uppercase italic">HD (Hrs Docente)</label>
|
||||
<input type="number" defaultValue={materia.hd} className="px-3 py-2 rounded-lg border border-slate-300 text-sm" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-xs font-bold text-slate-600 uppercase italic">HI (Hrs Indep.)</label>
|
||||
<input type="number" defaultValue={materia.hi} className="px-3 py-2 rounded-lg border border-slate-300 text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Ciclo y Línea */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-xs font-bold text-slate-600 uppercase">Ciclo</label>
|
||||
<select className="px-3 py-2 rounded-lg border border-slate-300 text-sm bg-white">
|
||||
<option>Ciclo {materia.ciclo}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-xs font-bold text-slate-600 uppercase">Línea Curricular</label>
|
||||
<select className="px-3 py-2 rounded-lg border border-slate-300 text-sm bg-white">
|
||||
<option>{materia.linea}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Botones de acción */}
|
||||
<div className="flex justify-end gap-3 pt-6">
|
||||
<Dialog.Close className="px-4 py-2 rounded-lg text-sm font-semibold text-slate-600 hover:bg-slate-100 transition-colors">
|
||||
Cancelar
|
||||
</Dialog.Close>
|
||||
<button
|
||||
type="button"
|
||||
className="px-6 py-2 rounded-lg text-sm font-semibold bg-emerald-700 text-white hover:bg-emerald-800 transition-colors shadow-sm"
|
||||
>
|
||||
Guardar
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
);
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from 'lucide-react'
|
||||
import { useState, useMemo } from 'react'
|
||||
|
||||
import type { Materia } from '@/types/plan'
|
||||
import type { Asignatura } from '@/types/plan'
|
||||
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
@@ -46,7 +46,7 @@ const tipoConfig: Record<string, { label: string; className: string }> = {
|
||||
}
|
||||
|
||||
// --- Mapeadores de API ---
|
||||
const mapAsignaturas = (asigApi: Array<any> = []): Array<Materia> => {
|
||||
const mapAsignaturas = (asigApi: Array<any> = []): Array<Asignatura> => {
|
||||
return asigApi.map((asig) => ({
|
||||
id: asig.id,
|
||||
clave: asig.codigo,
|
||||
@@ -63,10 +63,10 @@ const mapAsignaturas = (asigApi: Array<any> = []): Array<Materia> => {
|
||||
}
|
||||
|
||||
export const Route = createFileRoute('/planes/$planId/_detalle/asignaturas')({
|
||||
component: MateriasPage,
|
||||
component: AsignaturasPage,
|
||||
})
|
||||
|
||||
function MateriasPage() {
|
||||
function AsignaturasPage() {
|
||||
const { planId } = Route.useParams()
|
||||
const navigate = useNavigate()
|
||||
|
||||
@@ -82,13 +82,13 @@ function MateriasPage() {
|
||||
const [filterLinea, setFilterLinea] = useState<string>('all')
|
||||
|
||||
// 3. Procesamiento de datos
|
||||
const materias = useMemo(
|
||||
const asignaturas = useMemo(
|
||||
() => mapAsignaturas(asignaturasApi),
|
||||
[asignaturasApi],
|
||||
)
|
||||
const lineas = useMemo(() => lineasApi || [], [lineasApi])
|
||||
|
||||
const filteredMaterias = materias.filter((m) => {
|
||||
const filteredAsignaturas = asignaturas.filter((m) => {
|
||||
const matchesSearch =
|
||||
m.nombre.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
m.clave.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
@@ -119,11 +119,11 @@ function MateriasPage() {
|
||||
<div className="flex flex-wrap items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 className="text-foreground text-xl font-bold">
|
||||
Materias del Plan
|
||||
Asignaturas del Plan
|
||||
</h2>
|
||||
<p className="text-muted-foreground mt-1 text-sm">
|
||||
{materias.length} materias en total • {filteredMaterias.length}{' '}
|
||||
filtradas
|
||||
{asignaturas.length} asignaturas en total •{' '}
|
||||
{filteredAsignaturas.length} filtradas
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -132,7 +132,7 @@ function MateriasPage() {
|
||||
<Copy className="mr-2 h-4 w-4" /> Clonar
|
||||
</Button>
|
||||
<Button className="bg-emerald-700 hover:bg-emerald-800">
|
||||
<Plus className="mr-2 h-4 w-4" /> Nueva Materia
|
||||
<Plus className="mr-2 h-4 w-4" /> Nueva Asignatura
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -207,12 +207,12 @@ function MateriasPage() {
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredMaterias.length === 0 ? (
|
||||
{filteredAsignaturas.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} className="h-40 text-center">
|
||||
<div className="text-muted-foreground flex flex-col items-center justify-center">
|
||||
<BookOpen className="mb-2 h-10 w-10 opacity-20" />
|
||||
<p className="font-medium">No se encontraron materias</p>
|
||||
<p className="font-medium">No se encontraron asignaturas</p>
|
||||
<p className="text-xs">
|
||||
Intenta cambiar los filtros de búsqueda
|
||||
</p>
|
||||
@@ -220,59 +220,59 @@ function MateriasPage() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
filteredMaterias.map((materia) => (
|
||||
filteredAsignaturas.map((asignatura) => (
|
||||
<TableRow
|
||||
key={materia.id}
|
||||
key={asignatura.id}
|
||||
className="group cursor-pointer transition-colors hover:bg-slate-50/80"
|
||||
onClick={() =>
|
||||
navigate({
|
||||
to: '/planes/$planId/asignaturas/$asignaturaId',
|
||||
params: {
|
||||
planId,
|
||||
asignaturaId: materia.id, // 👈 puede ser índice, consecutivo o slug
|
||||
asignaturaId: asignatura.id, // 👈 puede ser índice, consecutivo o slug
|
||||
},
|
||||
state: {
|
||||
realId: materia.id, // 👈 ID largo oculto
|
||||
asignaturaId: materia.id,
|
||||
realId: asignatura.id, // 👈 ID largo oculto
|
||||
asignaturaId: asignatura.id,
|
||||
} as any,
|
||||
})
|
||||
}
|
||||
>
|
||||
<TableCell className="font-mono text-xs font-bold text-slate-400">
|
||||
{materia.clave}
|
||||
{asignatura.clave}
|
||||
</TableCell>
|
||||
<TableCell className="font-semibold text-slate-700">
|
||||
{materia.nombre}
|
||||
{asignatura.nombre}
|
||||
</TableCell>
|
||||
<TableCell className="text-center font-medium">
|
||||
{materia.creditos}
|
||||
{asignatura.creditos}
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
{materia.ciclo ? (
|
||||
{asignatura.ciclo ? (
|
||||
<Badge variant="outline" className="font-normal">
|
||||
Ciclo {materia.ciclo}
|
||||
Ciclo {asignatura.ciclo}
|
||||
</Badge>
|
||||
) : (
|
||||
<span className="text-slate-300">—</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="text-sm text-slate-600">
|
||||
{getLineaNombre(materia.lineaCurricularId)}
|
||||
{getLineaNombre(asignatura.lineaCurricularId)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`capitalize shadow-sm ${tipoConfig[materia.tipo]?.className}`}
|
||||
className={`capitalize shadow-sm ${tipoConfig[asignatura.tipo]?.className}`}
|
||||
>
|
||||
{tipoConfig[materia.tipo]?.label}
|
||||
{tipoConfig[asignatura.tipo]?.label}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`capitalize shadow-sm ${statusConfig[materia.estado]?.className}`}
|
||||
className={`capitalize shadow-sm ${statusConfig[asignatura.estado]?.className}`}
|
||||
>
|
||||
{statusConfig[materia.estado]?.label}
|
||||
{statusConfig[asignatura.estado]?.label}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from 'lucide-react'
|
||||
import { useMemo, useState, useEffect } from 'react'
|
||||
|
||||
import type { Materia, LineaCurricular } from '@/types/plan'
|
||||
import type { Asignatura, LineaCurricular } from '@/types/plan'
|
||||
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
@@ -47,7 +47,9 @@ const mapLineasToLineaCurricular = (
|
||||
}))
|
||||
}
|
||||
|
||||
const mapAsignaturasToMaterias = (asigApi: Array<any> = []): Array<Materia> => {
|
||||
const mapAsignaturasToAsignaturas = (
|
||||
asigApi: Array<any> = [],
|
||||
): Array<Asignatura> => {
|
||||
return asigApi.map((asig) => ({
|
||||
id: asig.id,
|
||||
clave: asig.codigo,
|
||||
@@ -104,13 +106,13 @@ function StatItem({
|
||||
)
|
||||
}
|
||||
|
||||
function MateriaCardItem({
|
||||
materia,
|
||||
function AsignaturaCardItem({
|
||||
asignatura,
|
||||
onDragStart,
|
||||
isDragging,
|
||||
onClick,
|
||||
}: {
|
||||
materia: Materia
|
||||
asignatura: Asignatura
|
||||
onDragStart: (e: React.DragEvent, id: string) => void
|
||||
isDragging: boolean
|
||||
onClick: () => void
|
||||
@@ -118,7 +120,7 @@ function MateriaCardItem({
|
||||
return (
|
||||
<button
|
||||
draggable
|
||||
onDragStart={(e) => onDragStart(e, materia.id)}
|
||||
onDragStart={(e) => onDragStart(e, asignatura.id)}
|
||||
onClick={onClick}
|
||||
className={`group cursor-grab rounded-lg border bg-white p-3 shadow-sm transition-all active:cursor-grabbing ${
|
||||
isDragging
|
||||
@@ -128,21 +130,21 @@ function MateriaCardItem({
|
||||
>
|
||||
<div className="mb-1 flex items-start justify-between">
|
||||
<span className="font-mono text-[10px] font-bold text-slate-400">
|
||||
{materia.clave}
|
||||
{asignatura.clave}
|
||||
</span>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`px-1 py-0 text-[9px] uppercase ${statusBadge[materia.estado] || ''}`}
|
||||
className={`px-1 py-0 text-[9px] uppercase ${statusBadge[asignatura.estado] || ''}`}
|
||||
>
|
||||
{materia.estado}
|
||||
{asignatura.estado}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="mb-1 text-xs leading-tight font-bold text-slate-700">
|
||||
{materia.nombre}
|
||||
{asignatura.nombre}
|
||||
</p>
|
||||
<div className="mt-2 flex items-center justify-between">
|
||||
<span className="text-[10px] text-slate-500">
|
||||
{materia.creditos} CR • HD:{materia.hd} • HI:{materia.hi}
|
||||
{asignatura.creditos} CR • HD:{asignatura.hd} • HI:{asignatura.hi}
|
||||
</span>
|
||||
<GripVertical
|
||||
size={12}
|
||||
@@ -166,11 +168,14 @@ function MapaCurricularPage() {
|
||||
const { data: lineasApi, isLoading: loadingLineas } = usePlanLineas(planId)
|
||||
|
||||
// 2. Estado Local (Para interactividad)
|
||||
const [materias, setMaterias] = useState<Array<Materia>>([])
|
||||
const [asignaturas, setAsignaturas] = useState<Array<Asignatura>>([])
|
||||
const [lineas, setLineas] = useState<Array<LineaCurricular>>([])
|
||||
const [draggedMateria, setDraggedMateria] = useState<string | null>(null)
|
||||
const [draggedAsignatura, setDraggedAsignatura] = useState<string | null>(
|
||||
null,
|
||||
)
|
||||
const [isEditModalOpen, setIsEditModalOpen] = useState(false)
|
||||
const [selectedMateria, setSelectedMateria] = useState<Materia | null>(null)
|
||||
const [selectedAsignatura, setSelectedAsignatura] =
|
||||
useState<Asignatura | null>(null)
|
||||
const [hasAreaComun, setHasAreaComun] = useState(false)
|
||||
const [nombreNuevaLinea, setNombreNuevaLinea] = useState('') // Para el input de nombre personalizado
|
||||
|
||||
@@ -236,7 +241,8 @@ function MapaCurricularPage() {
|
||||
|
||||
// 3. Sincronizar API -> Estado Local
|
||||
useEffect(() => {
|
||||
if (asignaturasApi) setMaterias(mapAsignaturasToMaterias(asignaturasApi))
|
||||
if (asignaturasApi)
|
||||
setAsignaturas(mapAsignaturasToAsignaturas(asignaturasApi))
|
||||
}, [asignaturasApi])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -247,23 +253,23 @@ function MapaCurricularPage() {
|
||||
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)
|
||||
const [editingData, setEditingData] = useState<Asignatura | null>(null)
|
||||
|
||||
// 1. FUNCION DE GUARDAR MODAL
|
||||
const handleSaveChanges = () => {
|
||||
if (!editingData) return
|
||||
console.log(materias)
|
||||
console.log(asignaturas)
|
||||
|
||||
setMaterias((prev) =>
|
||||
setAsignaturas((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
|
||||
// Cambiamos la condición: Mostramos la sección si hay asignaturas sin asignar
|
||||
// O si simplemente queremos tener el "depósito" disponible.
|
||||
const unassignedMaterias = materias.filter((m) => m.ciclo === null)
|
||||
const unassignedAsignaturas = asignaturas.filter((m) => m.ciclo === null)
|
||||
|
||||
// --- Lógica de Gestión ---
|
||||
const agregarLinea = (nombre: string) => {
|
||||
@@ -272,7 +278,7 @@ function MapaCurricularPage() {
|
||||
}
|
||||
|
||||
const borrarLinea = (id: string) => {
|
||||
setMaterias((prev) =>
|
||||
setAsignaturas((prev) =>
|
||||
prev.map((m) =>
|
||||
m.lineaCurricularId === id
|
||||
? { ...m, ciclo: null, lineaCurricularId: null }
|
||||
@@ -284,7 +290,7 @@ function MapaCurricularPage() {
|
||||
|
||||
// --- Selectores/Cálculos ---
|
||||
const getTotalesCiclo = (ciclo: number) => {
|
||||
return materias
|
||||
return asignaturas
|
||||
.filter((m) => m.ciclo === ciclo)
|
||||
.reduce(
|
||||
(acc, m) => ({
|
||||
@@ -297,7 +303,7 @@ function MapaCurricularPage() {
|
||||
}
|
||||
|
||||
const getSubtotalLinea = (lineaId: string) => {
|
||||
return materias
|
||||
return asignaturas
|
||||
.filter((m) => m.lineaCurricularId === lineaId && m.ciclo !== null)
|
||||
.reduce(
|
||||
(acc, m) => ({
|
||||
@@ -310,7 +316,7 @@ function MapaCurricularPage() {
|
||||
}
|
||||
|
||||
const handleDragStart = (e: React.DragEvent, id: string) => {
|
||||
setDraggedMateria(id)
|
||||
setDraggedAsignatura(id)
|
||||
e.dataTransfer.effectAllowed = 'move'
|
||||
}
|
||||
const handleDragOver = (e: React.DragEvent) => e.preventDefault()
|
||||
@@ -320,21 +326,21 @@ function MapaCurricularPage() {
|
||||
lineaId: string | null,
|
||||
) => {
|
||||
e.preventDefault()
|
||||
if (draggedMateria) {
|
||||
setMaterias((prev) =>
|
||||
if (draggedAsignatura) {
|
||||
setAsignaturas((prev) =>
|
||||
prev.map((m) =>
|
||||
m.id === draggedMateria
|
||||
m.id === draggedAsignatura
|
||||
? { ...m, ciclo, lineaCurricularId: lineaId }
|
||||
: m,
|
||||
),
|
||||
)
|
||||
setDraggedMateria(null)
|
||||
setDraggedAsignatura(null)
|
||||
}
|
||||
}
|
||||
|
||||
const stats = useMemo(
|
||||
() =>
|
||||
materias.reduce(
|
||||
asignaturas.reduce(
|
||||
(acc, m) => {
|
||||
if (m.ciclo !== null) {
|
||||
acc.cr += m.creditos || 0
|
||||
@@ -345,7 +351,7 @@ function MapaCurricularPage() {
|
||||
},
|
||||
{ cr: 0, hd: 0, hi: 0 },
|
||||
),
|
||||
[materias],
|
||||
[asignaturas],
|
||||
)
|
||||
|
||||
if (loadingAsig || loadingLineas)
|
||||
@@ -358,14 +364,14 @@ function MapaCurricularPage() {
|
||||
<div>
|
||||
<h2 className="text-xl font-bold">Mapa Curricular</h2>
|
||||
<p className="text-sm text-slate-500">
|
||||
Organiza las materias de la petición por línea y ciclo
|
||||
Organiza las asignaturas de la petición por línea y ciclo
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
{materias.filter((m) => !m.ciclo).length > 0 && (
|
||||
{asignaturas.filter((m) => !m.ciclo).length > 0 && (
|
||||
<Badge className="border-amber-100 bg-amber-50 text-amber-600 hover:bg-amber-50">
|
||||
<AlertTriangle size={14} className="mr-1" />{' '}
|
||||
{materias.filter((m) => !m.ciclo).length} sin asignar
|
||||
{asignaturas.filter((m) => !m.ciclo).length} sin asignar
|
||||
</Badge>
|
||||
)}
|
||||
<DropdownMenu>
|
||||
@@ -474,16 +480,16 @@ function MapaCurricularPage() {
|
||||
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"
|
||||
>
|
||||
{materias
|
||||
{asignaturas
|
||||
.filter(
|
||||
(m) =>
|
||||
m.ciclo === ciclo && m.lineaCurricularId === linea.id,
|
||||
)
|
||||
.map((m) => (
|
||||
<MateriaCardItem
|
||||
<AsignaturaCardItem
|
||||
key={m.id}
|
||||
materia={m}
|
||||
isDragging={draggedMateria === m.id}
|
||||
asignatura={m}
|
||||
isDragging={draggedAsignatura === m.id}
|
||||
onDragStart={handleDragStart}
|
||||
onClick={() => {
|
||||
setEditingData(m)
|
||||
@@ -534,35 +540,35 @@ function MapaCurricularPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Materias Sin Asignar */}
|
||||
{/* Asignaturas Sin Asignar */}
|
||||
{/* 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="mb-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2 text-slate-600">
|
||||
<h3 className="text-sm font-bold tracking-wider uppercase">
|
||||
Bandeja de Entrada / Materias sin asignar
|
||||
Bandeja de Entrada / Asignaturas sin asignar
|
||||
</h3>
|
||||
<Badge variant="secondary">{unassignedMaterias.length}</Badge>
|
||||
<Badge variant="secondary">{unassignedAsignaturas.length}</Badge>
|
||||
</div>
|
||||
<p className="text-xs text-slate-400">
|
||||
Arrastra una materia aquí para quitarla del mapa
|
||||
Arrastra una asignatura aquí para quitarla del mapa
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`flex min-h-[120px] flex-wrap gap-4 rounded-xl border-2 border-dashed p-4 transition-colors ${
|
||||
draggedMateria
|
||||
draggedAsignatura
|
||||
? '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) => (
|
||||
{unassignedAsignaturas.map((m) => (
|
||||
<div key={m.id} className="w-[200px]">
|
||||
<MateriaCardItem
|
||||
materia={m}
|
||||
isDragging={draggedMateria === m.id}
|
||||
<AsignaturaCardItem
|
||||
asignatura={m}
|
||||
isDragging={draggedAsignatura === m.id}
|
||||
onDragStart={handleDragStart}
|
||||
onClick={() => {
|
||||
setEditingData(m) // Cargamos los datos en el estado de edición
|
||||
@@ -571,9 +577,9 @@ function MapaCurricularPage() {
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{unassignedMaterias.length === 0 && (
|
||||
{unassignedAsignaturas.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
|
||||
No hay asignaturas pendientes. Arrastra una asignatura aquí para
|
||||
desasignarla.
|
||||
</div>
|
||||
)}
|
||||
@@ -585,7 +591,7 @@ function MapaCurricularPage() {
|
||||
<DialogContent className="sm:max-w-[550px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="font-bold text-slate-700">
|
||||
Editar Materia
|
||||
Editar Asignatura
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -735,10 +741,10 @@ function MapaCurricularPage() {
|
||||
</label>
|
||||
<Select>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Seleccionar materia..." />
|
||||
<SelectValue placeholder="Seleccionar asignatura..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{materias.map((m) => (
|
||||
{asignaturas.map((m) => (
|
||||
<SelectItem key={m.id} value={m.clave}>
|
||||
{m.nombre}
|
||||
</SelectItem>
|
||||
|
||||
@@ -223,7 +223,7 @@ function RouteComponent() {
|
||||
Mapa Curricular
|
||||
</Tab>
|
||||
<Tab to="/planes/$planId/asignaturas" params={{ planId }}>
|
||||
Materias
|
||||
Asignaturas
|
||||
</Tab>
|
||||
<Tab to="/planes/$planId/flujo" params={{ planId }}>
|
||||
Flujo y Estados
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import MateriaDetailPage from '@/components/asignaturas/detalle/MateriaDetailPage'
|
||||
import AsignaturaDetailPage from '@/components/asignaturas/detalle/AsignaturaDetailPage'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute(
|
||||
'/planes/$planId/asignaturas/$asignaturaId'
|
||||
'/planes/$planId/asignaturas/$asignaturaId',
|
||||
)({
|
||||
component: RouteComponent,
|
||||
})
|
||||
@@ -12,7 +12,7 @@ function RouteComponent() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<MateriaDetailPage></MateriaDetailPage>
|
||||
<AsignaturaDetailPage></AsignaturaDetailPage>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user