Refactor App component styles and remove highlights section

- Updated background gradient colors for a lighter theme.
- Changed button styles to improve visibility.
- Removed the highlights section to streamline the layout.
- Adjusted text colors for better contrast and readability.
This commit is contained in:
2025-08-26 15:24:53 -06:00
parent 602c5dbb31
commit 56b0dc8a62
12 changed files with 2240 additions and 1188 deletions

View File

@@ -250,6 +250,7 @@ function RouteComponent() {
<div className="academics">
<AcademicSections planId={plan.id} plan={plan} color={fac?.color} />
</div>
{/* ===== Asignaturas (preview cards) ===== */}
<Card className="border shadow-sm">
<CardHeader className="flex items-center justify-between gap-2">
<CardTitle className="text-base">Asignaturas ({asignaturasCount})</CardTitle>
@@ -258,32 +259,29 @@ function RouteComponent() {
<AddAsignaturaButton planId={plan.id} onAdded={() => router.invalidate()} />
<Link
to="/asignaturas/$planId"
search={{ q: "", planId: plan.id, carreraId: '', f: '', facultadId: '' }}
search={{ q: "", planId: plan.id, carreraId: "", f: "", facultadId: "" }}
params={{ planId: plan.id }}
className="inline-flex items-center gap-2 rounded-xl border px-3 py-2 text-sm hover:bg-neutral-50"
title="Ver todas las asignaturas"
>
<Icons.BookOpen className="w-4 h-4" /> Ver todas
<Icons.BookOpen className="w-4 h-4" /> Ver en página de Asignaturas
</Link>
</div>
</CardHeader>
<CardContent className="flex flex-wrap gap-2">
{asignaturasPreview.length === 0 && (
<CardContent>
{asignaturasPreview.length === 0 ? (
<div className="text-sm text-neutral-500">Sin asignaturas</div>
) : (
<div className="grid gap-3 sm:grid-cols-2">
{asignaturasPreview.map((a) => (
<AsignaturaPreviewCard key={a.id} asignatura={a} />
))}
</div>
)}
{asignaturasPreview.map(a => (
<Link
to="/asignatura/$asignaturaId"
params={{ asignaturaId: a.id }}
className="rounded-full border px-3 py-1 text-xs bg-white/70 hover:bg-white transition"
title={a.nombre}
>
{a.semestre ? `S${a.semestre} · ` : ''}{a.nombre}
</Link>
))}
</CardContent>
</Card>
</div>
)
}
@@ -404,9 +402,9 @@ function AdjustAIButton({ plan }: { plan: PlanFull }) {
async function apply() {
setLoading(true)
await fetch('https://genesis-engine.apps.lci.ulsa.mx/ajustar/plan', {
await fetch('https://genesis-engine.apps.lci.ulsa.mx/api/mejorar/plan', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt, plan }),
body: JSON.stringify({ prompt, plan_id: plan.id }),
}).catch(() => { })
setLoading(false)
setOpen(false)
@@ -629,3 +627,146 @@ function AddAsignaturaButton({
</>
)
}
function AsignaturaPreviewCard({
asignatura,
}: {
asignatura: { id: string; nombre: string; semestre: number | null; creditos: number | null }
}) {
const [extra, setExtra] = useState<{
tipo: string | null
horas_teoricas: number | null
horas_practicas: number | null
contenidos: Record<string, Record<string, string>> | null
} | null>(null)
// Carga perezosa de info extra para enriquecer la tarjeta (8 items máx: ok)
useEffect(() => {
let ignore = false
; (async () => {
const { data } = await supabase
.from("asignaturas")
.select("tipo, horas_teoricas, horas_practicas, contenidos")
.eq("id", asignatura.id)
.maybeSingle()
if (!ignore) setExtra((data as any) ?? null)
})()
return () => {
ignore = true
}
}, [asignatura.id])
const horasT = extra?.horas_teoricas ?? null
const horasP = extra?.horas_practicas ?? null
const horasTot = (horasT ?? 0) + (horasP ?? 0)
// Conteo rápido de unidades/temas si existen
const resumenContenidos = useMemo(() => {
const c = extra?.contenidos
if (!c) return { unidades: 0, temas: 0 }
const unidades = Object.keys(c).length
const temas = Object.values(c).reduce((acc, temasObj) => acc + Object.keys(temasObj || {}).length, 0)
return { unidades, temas }
}, [extra?.contenidos])
// estilo por tipo
const tipo = (extra?.tipo ?? "").toLowerCase()
const tipoChip =
tipo.includes("oblig")
? "bg-emerald-50 text-emerald-700 border-emerald-200"
: tipo.includes("opt")
? "bg-amber-50 text-amber-800 border-amber-200"
: tipo.includes("taller")
? "bg-indigo-50 text-indigo-700 border-indigo-200"
: tipo.includes("lab")
? "bg-sky-50 text-sky-700 border-sky-200"
: "bg-neutral-100 text-neutral-700 border-neutral-200"
return (
<article
className="group relative overflow-hidden rounded-2xl border bg-white/70 dark:bg-neutral-900/60 backdrop-blur p-4 shadow-sm hover:shadow-md transition-all hover:-translate-y-0.5"
role="region"
aria-label={asignatura.nombre}
>
{/* header */}
<div className="flex items-start gap-3">
<div className="h-9 w-9 rounded-xl grid place-items-center border bg-white/80">
<Icons.BookOpen className="h-4 w-4" />
</div>
<div className="min-w-0">
<div className="font-medium truncate" title={asignatura.nombre}>
{asignatura.nombre}
</div>
<div className="mt-1 flex flex-wrap items-center gap-1.5 text-[11px]">
{asignatura.semestre != null && (
<span className="inline-flex items-center gap-1 rounded-full border px-2 py-0.5">
<Icons.Calendar className="h-3 w-3" /> S{asignatura.semestre}
</span>
)}
{asignatura.creditos != null && (
<span className="inline-flex items-center gap-1 rounded-full border px-2 py-0.5">
<Icons.Coins className="h-3 w-3" /> {asignatura.creditos} cr
</span>
)}
{extra?.tipo && (
<span className={`inline-flex items-center gap-1 rounded-full border px-2 py-0.5 ${tipoChip}`}>
<Icons.Tag className="h-3 w-3" /> {extra.tipo}
</span>
)}
</div>
</div>
</div>
{/* cuerpo */}
<div className="mt-3 grid grid-cols-3 gap-2 text-[11px]">
<SmallStat icon={Icons.Clock} label="Horas" value={horasTot || "—"} />
<SmallStat icon={Icons.BookMarked} label="Unidades" value={resumenContenidos.unidades || "—"} />
<SmallStat icon={Icons.ListTree} label="Temas" value={resumenContenidos.temas || "—"} />
</div>
{/* footer */}
<div className="mt-3 flex items-center justify-between">
<div className="text-[11px] text-neutral-500">
{horasT != null || horasP != null ? (
<>H T/P: {horasT ?? "—"}/{horasP ?? "—"}</>
) : (
<span className="opacity-70">Resumen listo</span>
)}
</div>
<Link
to="/asignatura/$asignaturaId"
params={{ asignaturaId: asignatura.id }}
className="inline-flex items-center gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs hover:bg-neutral-50"
title="Ver detalle"
>
Ver detalle <Icons.ArrowRight className="h-3.5 w-3.5" />
</Link>
</div>
{/* glow sutil en hover */}
<div className="pointer-events-none absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity"
style={{ background: "radial-gradient(600px 120px at 20% -10%, rgba(0,0,0,.06), transparent 60%)" }} />
</article>
)
}
function SmallStat({
icon: Icon,
label,
value,
}: {
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>
label: string
value: string | number
}) {
return (
<div className="rounded-lg border bg-white/60 dark:bg-neutral-900/50 px-2.5 py-2">
<div className="flex items-center gap-1 text-[10px] text-neutral-500">
<Icon className="h-3.5 w-3.5" /> {label}
</div>
<div className="mt-0.5 font-medium tabular-nums">{value}</div>
</div>
)
}