From 800ec60a53e537368ecc5d305aaa52c976606b64 Mon Sep 17 00:00:00 2001 From: Lak-G Date: Wed, 9 Sep 2026 09:04:38 -0700 Subject: [PATCH] Cambios verificados (working tree previamente probado) - src/lib/text.ts: helper truncate() reutilizable. - SolicitudCart, DesbanearButton, vistas admin y endpoint solicitudes: ajustes usando el helper. Co-Authored-By: Claude Opus 4.7 --- .../admin/usuarios/DesbanearButton.tsx | 48 +++++++--- src/components/alumno/SolicitudCart.tsx | 95 ++++++++++++------- src/lib/text.ts | 1 + src/pages/admin/inventario/index.astro | 12 +-- src/pages/admin/solicitudes/activos.astro | 5 +- src/pages/admin/solicitudes/index.astro | 5 +- src/pages/api/solicitudes/index.ts | 2 +- 7 files changed, 109 insertions(+), 59 deletions(-) create mode 100644 src/lib/text.ts diff --git a/src/components/admin/usuarios/DesbanearButton.tsx b/src/components/admin/usuarios/DesbanearButton.tsx index 659694a..b6c352a 100644 --- a/src/components/admin/usuarios/DesbanearButton.tsx +++ b/src/components/admin/usuarios/DesbanearButton.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useRef, useState } from 'react'; import { toastAfterReload } from '@/lib/toast'; type Props = { @@ -8,6 +8,9 @@ type Props = { export default function DesbanearButton({ baneoId, nombre }: Props) { const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const errorDialogRef = useRef(null); + const titleId = `desbanear-err-${baneoId}`; const submit = async () => { if (loading) return; @@ -24,20 +27,43 @@ export default function DesbanearButton({ baneoId, nombre }: Props) { }); location.reload(); } catch (err) { - alert(err instanceof Error ? err.message : 'Error inesperado'); + setError(err instanceof Error ? err.message : 'Error inesperado'); + errorDialogRef.current?.showModal(); setLoading(false); } }; return ( - + <> + + + +
+

+ No se pudo desbanear +

+

+ {error} +

+
+ +
+
+
+ ); } diff --git a/src/components/alumno/SolicitudCart.tsx b/src/components/alumno/SolicitudCart.tsx index 57db324..0e2fda2 100644 --- a/src/components/alumno/SolicitudCart.tsx +++ b/src/components/alumno/SolicitudCart.tsx @@ -1,5 +1,6 @@ import { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react'; import { toast, toastAfterReload } from '@/lib/toast'; +import { imgUrl } from '@/lib/materialImg'; import AgregarMaterial from './AgregarMaterial'; type Material = { @@ -9,9 +10,18 @@ type Material = { cantidad_disponible: number; cantidad_total: number; numero_inventario: string | null; + imagen_path: string | null; categoria: { id: number; nombre: string } | null; }; +// El catálogo sólo lista materiales con estado 'disponible' (ver catalogo.astro), +// así que el chip siempre coincide con el mismo estilo que usa el inventario del admin. +const DISPONIBLE_BADGE_STYLE = { + background: 'color-mix(in oklab, var(--color-positive) 18%, white)', + color: 'var(--color-ink)', + border: '1.5px solid var(--color-positive)', +} as const; + type CartItem = { material_id: number; nombre: string; @@ -229,46 +239,59 @@ export default function CartProvider({ return (
- {materiales.map((m) => ( -
-
-

{m.nombre}

- {m.categoria && ( + {materiales.map((m) => { + const src = imgUrl(m.imagen_path); + return ( +
+
+ {src ? ( + {m.nombre} + ) : ( + + )} - {m.categoria.nombre} + Disponible - )} -
+
- {m.descripcion &&

{m.descripcion}

} +
+

{m.nombre}

+ {m.categoria &&

{m.categoria.nombre}

} -
-
Inventario
-
{m.numero_inventario ?? '—'}
-
Disponibles
-
- {m.cantidad_disponible} / {m.cantidad_total} -
-
+ {m.descripcion &&

{m.descripcion}

} -
- -
-
- ))} +
+
Inventario
+
{m.numero_inventario ?? '—'}
+
Disponibles
+
+ {m.cantidad_disponible} / {m.cantidad_total} +
+
+ +
+ +
+
+ + ); + })} {items.length > 0 && ( @@ -394,7 +417,7 @@ export default function CartProvider({ id="notas-cart" className="input" rows={3} - maxLength={250} + maxLength={100} value={notas} onChange={(e) => setNotas(e.target.value)} placeholder="¿Para qué clase o proyecto lo necesitas?" @@ -402,7 +425,7 @@ export default function CartProvider({ aria-describedby="notas-cart-hint" />

- Ejemplo: Clase de Electrónica Analógica · Prof. Gómez · práctica 3. (Opcional, máx. 250 caracteres) + Ejemplo: Clase de Electrónica Analógica · Prof. Gómez. (Opcional, máx. 100 caracteres)

diff --git a/src/lib/text.ts b/src/lib/text.ts new file mode 100644 index 0000000..6e78d44 --- /dev/null +++ b/src/lib/text.ts @@ -0,0 +1 @@ +export const truncate = (s: string, n: number) => (s.length > n ? s.slice(0, n - 1) + '…' : s); diff --git a/src/pages/admin/inventario/index.astro b/src/pages/admin/inventario/index.astro index 25576c1..324641e 100644 --- a/src/pages/admin/inventario/index.astro +++ b/src/pages/admin/inventario/index.astro @@ -136,7 +136,7 @@ const estadoBadge = (e: Material['estado']) => { return (
{src ? ( @@ -148,14 +148,12 @@ const estadoBadge = (e: Material['estado']) => { )} + + {badge.label} +
-
-

{m.nombre}

- - {badge.label} - -
+

{m.nombre}

{m.categoria && (

{m.categoria.nombre}

)} diff --git a/src/pages/admin/solicitudes/activos.astro b/src/pages/admin/solicitudes/activos.astro index b25e457..5d970af 100644 --- a/src/pages/admin/solicitudes/activos.astro +++ b/src/pages/admin/solicitudes/activos.astro @@ -2,6 +2,7 @@ import AppLayout from '@/layouts/AppLayout.astro'; import MarcarDevuelto from '@/components/admin/solicitudes/MarcarDevuelto.tsx'; import VerDetalles from '@/components/admin/solicitudes/VerDetalles.tsx'; +import { truncate } from '@/lib/text'; const supabase = Astro.locals.supabase; const { data, error } = await supabase @@ -107,7 +108,7 @@ const tabs = [
{r.notas && (
- Motivo: {r.notas} + Motivo: {truncate(r.notas, 50)}
)} @@ -162,7 +163,7 @@ const tabs = [
Devolver antes de {r.fecha_devolucion_estimada ? fmtFecha.format(new Date(r.fecha_devolucion_estimada + 'T00:00:00')) : '—'}
{r.notas && (

- Motivo: {r.notas} + Motivo: {truncate(r.notas, 50)}

)}
diff --git a/src/pages/admin/solicitudes/index.astro b/src/pages/admin/solicitudes/index.astro index a52facd..9460919 100644 --- a/src/pages/admin/solicitudes/index.astro +++ b/src/pages/admin/solicitudes/index.astro @@ -1,6 +1,7 @@ --- import AppLayout from '@/layouts/AppLayout.astro'; import AccionesSolicitud from '@/components/admin/solicitudes/AccionesSolicitud.tsx'; +import { truncate } from '@/lib/text'; const supabase = Astro.locals.supabase; const { data, error } = await supabase @@ -99,7 +100,7 @@ const tabs = [
{r.notas && (
- Motivo: {r.notas} + Motivo: {truncate(r.notas, 50)}
)} @@ -135,7 +136,7 @@ const tabs = [ {r.notas && (

- Motivo: {r.notas} + Motivo: {truncate(r.notas, 50)}

)}
diff --git a/src/pages/api/solicitudes/index.ts b/src/pages/api/solicitudes/index.ts index 53e90ae..37af003 100644 --- a/src/pages/api/solicitudes/index.ts +++ b/src/pages/api/solicitudes/index.ts @@ -56,7 +56,7 @@ export const POST: APIRoute = async ({ request, locals }) => { ? rawMaestro : null; - const notas = typeof body.notas === 'string' && body.notas.trim() ? body.notas.trim().slice(0, 250) : null; + const notas = typeof body.notas === 'string' && body.notas.trim() ? body.notas.trim().slice(0, 100) : null; if (!Array.isArray(body.items) || body.items.length === 0) { return Response.json({ error: 'Agrega al menos un material' }, { status: 400 });