Se corrige limite de 200 y wrap en titulo validaciones en modal de adeicion de matria en mapa

This commit is contained in:
2026-02-06 14:42:38 -06:00
parent b45aa4b59c
commit 31a47934e5
3 changed files with 51 additions and 24 deletions

View File

@@ -76,31 +76,43 @@ function RouteComponent() {
mutate({ planId, patch })
}
const MAX_CHARACTERS = 200
const handleKeyDown = (e: React.KeyboardEvent<HTMLSpanElement>) => {
// 1. Permitir teclas de control (Borrar, flechas, etc.) siempre
const isControlKey =
e.key === 'Backspace' ||
e.key === 'Delete' ||
e.key.includes('Arrow') ||
e.metaKey ||
e.ctrlKey
if (e.key === 'Enter') {
e.preventDefault()
e.currentTarget.blur() // Esto disparará el onBlur automáticamente
e.currentTarget.blur()
return
}
// 2. Bloquear si excede los 200 caracteres y no es una tecla de control
const currentText = e.currentTarget.textContent || ''
if (currentText.length >= MAX_CHARACTERS && !isControlKey) {
e.preventDefault()
}
}
const handleBlurNombre = (e: React.FocusEvent<HTMLSpanElement>) => {
const nuevoNombre = e.currentTarget.textContent || ''
setNombrePlan(nuevoNombre)
const handlePaste = (e: React.ClipboardEvent<HTMLSpanElement>) => {
e.preventDefault()
const text = e.clipboardData.getData('text/plain')
const currentText = e.currentTarget.textContent || ''
// Solo guardamos si el valor es realmente distinto al de la base de datos
if (nuevoNombre !== data?.nombre) {
persistChange({ nombre: nuevoNombre })
// Calcular cuánto espacio queda
const remainingSpace = MAX_CHARACTERS - currentText.length
if (remainingSpace > 0) {
const slicedText = text.slice(0, remainingSpace)
document.execCommand('insertText', false, slicedText)
}
}
const handleSelectNivel = (n: string) => {
setNivelPlan(n)
// Guardamos inmediatamente al seleccionar
if (n !== data?.nivel) {
persistChange({ nivel: n })
}
}
return (
<div className="min-h-screen bg-white">
{/* 1. Header Superior */}
@@ -127,8 +139,9 @@ function RouteComponent() {
) : (
<div className="flex flex-col items-start justify-between gap-4 md:flex-row">
<div>
<h1 className="flex items-baseline gap-2 text-3xl font-bold tracking-tight text-slate-900">
<span>{nivelPlan} en</span>
<h1 className="flex flex-wrap items-baseline gap-2 text-3xl leading-tight font-bold tracking-tight text-slate-900">
{/* El prefijo "Nivel en" lo mantenemos simple */}
<span className="shrink-0">{nivelPlan} en</span>
<span
role="textbox"
tabIndex={0}
@@ -136,14 +149,17 @@ function RouteComponent() {
suppressContentEditableWarning
spellCheck={false}
onKeyDown={handleKeyDown}
onPaste={handlePaste} // Añadido para controlar lo que pegan
onBlur={(e) => {
const nuevoNombre = e.currentTarget.textContent || ''
const nuevoNombre =
e.currentTarget.textContent?.trim() || ''
setNombrePlan(nuevoNombre)
if (nuevoNombre !== data?.nombre) {
mutate({ planId, patch: { nombre: nuevoNombre } })
}
}}
className="cursor-text border-b border-transparent transition-colors outline-none select-text hover:border-slate-300 focus:border-teal-500"
// Clases añadidas: break-words y whitespace-pre-wrap para el wrap
className="block w-full cursor-text border-b border-transparent break-words whitespace-pre-wrap transition-colors outline-none select-text hover:border-slate-300 focus:border-teal-500 sm:inline-block sm:w-auto"
style={{ textDecoration: 'none' }}
>
{nombrePlan}

View File

@@ -93,7 +93,6 @@ function DatosGeneralesPage() {
requerido: true,
// 👇 TIPO DE CAMPO
tipo: Array.isArray(schema?.enum)
? 'select'
: schema?.type === 'number'

View File

@@ -284,6 +284,16 @@ function MapaCurricularPage() {
const ciclosTotales = Number(ciclo)
const ciclosArray = Array.from({ length: ciclosTotales }, (_, i) => i + 1)
const [editingData, setEditingData] = useState<Asignatura | null>(null)
const handleIntegerChange = (value: string) => {
if (value === '') return value
// Solo números, máximo 3 cifras
const regex = /^\d{1,3}$/
if (!regex.test(value)) return null
return value
}
const handleDecimalChange = (value: string, max?: number): string | null => {
if (value === '') return ''
@@ -562,7 +572,6 @@ function MapaCurricularPage() {
>
{editingLineaId === linea.id ? (
<Input
autoFocus
className="h-7 bg-white text-xs"
value={tempNombreLinea}
onChange={(e) => setTempNombreLinea(e.target.value)}
@@ -572,6 +581,7 @@ function MapaCurricularPage() {
}
/>
) : (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<span
className="cursor-pointer text-xs font-bold hover:underline"
onClick={() => {
@@ -727,6 +737,7 @@ function MapaCurricularPage() {
Clave
</label>
<Input
maxLength={100}
value={editingData.clave}
onChange={(e) =>
setEditingData({ ...editingData, clave: e.target.value })
@@ -738,6 +749,7 @@ function MapaCurricularPage() {
Nombre
</label>
<Input
maxLength={200}
value={editingData.nombre}
onChange={(e) =>
setEditingData({ ...editingData, nombre: e.target.value })
@@ -775,7 +787,7 @@ function MapaCurricularPage() {
type="number"
value={editingData.hd}
onChange={(e) => {
const val = handleDecimalChange(e.target.value, 10)
const val = handleIntegerChange(e.target.value)
if (val !== null) {
setEditingData({
...editingData,
@@ -793,7 +805,7 @@ function MapaCurricularPage() {
type="number"
value={editingData.hi}
onChange={(e) => {
const val = handleDecimalChange(e.target.value, 10)
const val = handleIntegerChange(e.target.value)
if (val !== null) {
setEditingData({
...editingData,