Improve responsive UX/UI consistency across all frontend screens.
This polish pass unifies mobile navigation, spacing, typography hierarchy, and CTA behavior so all core exam workflows remain clear and fully usable on both mobile and desktop.
This commit is contained in:
@@ -10,26 +10,41 @@ export default function Navbar() {
|
||||
const { user, logout } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const [confirmOut, setConfirmOut] = useState(false);
|
||||
const [mobileOpen, setMobileOpen] = useState(false);
|
||||
|
||||
const doLogout = () => {
|
||||
logout();
|
||||
navigate("/login");
|
||||
};
|
||||
|
||||
const closeMobileMenu = () => setMobileOpen(false);
|
||||
|
||||
return (
|
||||
<header className="navbar">
|
||||
<div className="navbar-inner">
|
||||
<Link to="/" className="brand">
|
||||
<Link to="/" className="brand" onClick={closeMobileMenu}>
|
||||
<span className="brand-logo">
|
||||
<Icon name="document" size={18} />
|
||||
</span>
|
||||
GenExámenes IA
|
||||
<span className="brand-text">GenExámenes IA</span>
|
||||
</Link>
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="nav-mobile-toggle"
|
||||
aria-label={mobileOpen ? "Cerrar menú" : "Abrir menú"}
|
||||
onClick={() => setMobileOpen((open) => !open)}
|
||||
>
|
||||
<Icon name={mobileOpen ? "close" : "listChecks"} size={18} />
|
||||
</Button>
|
||||
|
||||
<div className={`nav-collapse ${mobileOpen ? "open" : ""}`}>
|
||||
<nav className="nav-links">
|
||||
<NavLink to="/" end className="nav-link">
|
||||
<NavLink to="/" end className="nav-link" onClick={closeMobileMenu}>
|
||||
Mis exámenes
|
||||
</NavLink>
|
||||
<NavLink to="/plantillas/nueva" className="nav-link">
|
||||
<NavLink to="/plantillas/nueva" className="nav-link" onClick={closeMobileMenu}>
|
||||
Crear examen
|
||||
</NavLink>
|
||||
</nav>
|
||||
@@ -38,11 +53,19 @@ export default function Navbar() {
|
||||
<div className="avatar" title={user?.email}>
|
||||
{initials(user?.full_name || user?.email)}
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" onClick={() => setConfirmOut(true)}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
closeMobileMenu();
|
||||
setConfirmOut(true);
|
||||
}}
|
||||
>
|
||||
Salir
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
open={confirmOut}
|
||||
|
||||
+342
-26
@@ -31,6 +31,10 @@
|
||||
|
||||
--font: "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;
|
||||
--maxw: 1180px;
|
||||
--h1: 30px;
|
||||
--h2: 24px;
|
||||
--h3: 19px;
|
||||
--h4: 16px;
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -48,7 +52,7 @@ body {
|
||||
background: var(--c-bg);
|
||||
color: var(--c-text);
|
||||
font-size: 15px;
|
||||
line-height: 1.55;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
@@ -64,10 +68,22 @@ h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
margin: 0 0 0.4em;
|
||||
margin: 0 0 0.45em;
|
||||
line-height: 1.25;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.01em;
|
||||
letter-spacing: -0.012em;
|
||||
}
|
||||
h1 {
|
||||
font-size: var(--h1);
|
||||
}
|
||||
h2 {
|
||||
font-size: var(--h2);
|
||||
}
|
||||
h3 {
|
||||
font-size: var(--h3);
|
||||
}
|
||||
h4 {
|
||||
font-size: var(--h4);
|
||||
}
|
||||
|
||||
button {
|
||||
@@ -82,6 +98,12 @@ button {
|
||||
.icon-inline {
|
||||
margin-right: 6px;
|
||||
}
|
||||
.card-head .icon,
|
||||
.tab .icon,
|
||||
.btn .icon,
|
||||
.badge .icon {
|
||||
stroke-width: 1.9;
|
||||
}
|
||||
.icon-lg {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
@@ -181,7 +203,7 @@ button {
|
||||
.nav-link {
|
||||
padding: 8px 14px;
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--c-text-soft);
|
||||
color: #4e566c;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -228,16 +250,16 @@ button {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
margin-bottom: 26px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.page-header h1 {
|
||||
font-size: 26px;
|
||||
font-size: clamp(24px, 3vw, 30px);
|
||||
margin: 0;
|
||||
}
|
||||
.page-header p {
|
||||
margin: 4px 0 0;
|
||||
color: var(--c-text-soft);
|
||||
margin: 7px 0 0;
|
||||
color: #5a6277;
|
||||
}
|
||||
.page-header-actions {
|
||||
margin-left: auto;
|
||||
@@ -245,6 +267,23 @@ button {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.page-lead {
|
||||
max-width: 68ch;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: var(--c-text);
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.section-subtle {
|
||||
font-size: 13px;
|
||||
color: var(--c-text-faint);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ---------- Cards ---------- */
|
||||
.card {
|
||||
background: var(--c-surface);
|
||||
@@ -253,10 +292,10 @@ button {
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
.card-pad {
|
||||
padding: 22px;
|
||||
padding: 20px;
|
||||
}
|
||||
.card-head {
|
||||
padding: 18px 22px;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid var(--c-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -265,9 +304,10 @@ button {
|
||||
.card-head h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.card-body {
|
||||
padding: 22px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
@@ -365,12 +405,12 @@ button {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--c-text);
|
||||
color: #2a3143;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
.field-hint {
|
||||
font-size: 12.5px;
|
||||
color: var(--c-text-faint);
|
||||
color: #78809a;
|
||||
margin-top: 6px;
|
||||
}
|
||||
.field-error {
|
||||
@@ -440,12 +480,12 @@ button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 3px 10px;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
background: var(--c-surface-2);
|
||||
color: var(--c-text-soft);
|
||||
color: #505a74;
|
||||
}
|
||||
.badge-primary {
|
||||
background: var(--c-primary-soft);
|
||||
@@ -477,10 +517,10 @@ button {
|
||||
overflow-x: auto;
|
||||
}
|
||||
.tab {
|
||||
padding: 11px 16px;
|
||||
padding: 11px 15px;
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--c-text-soft);
|
||||
color: #556079;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
@@ -492,7 +532,7 @@ button {
|
||||
gap: 7px;
|
||||
}
|
||||
.tab:hover {
|
||||
color: var(--c-text);
|
||||
color: #252c3f;
|
||||
}
|
||||
.tab.active {
|
||||
color: var(--c-primary);
|
||||
@@ -627,7 +667,7 @@ button {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
color: var(--c-text-soft);
|
||||
color: #586178;
|
||||
font-size: 13px;
|
||||
}
|
||||
.meta-row span {
|
||||
@@ -668,13 +708,17 @@ button {
|
||||
}
|
||||
.list-item-sub {
|
||||
font-size: 13px;
|
||||
color: var(--c-text-faint);
|
||||
color: #7a839c;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 56px 24px;
|
||||
color: var(--c-text-soft);
|
||||
color: #5c657a;
|
||||
}
|
||||
.empty-state p {
|
||||
margin: 6px auto 0;
|
||||
max-width: 56ch;
|
||||
}
|
||||
.empty-state-icon {
|
||||
margin: 0 auto 12px;
|
||||
@@ -813,15 +857,15 @@ button {
|
||||
}
|
||||
.toast-content {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
font-size: 13.5px;
|
||||
}
|
||||
.toast-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.toast-msg {
|
||||
color: var(--c-text-soft);
|
||||
font-size: 13px;
|
||||
color: #5b6378;
|
||||
font-size: 12.5px;
|
||||
}
|
||||
.toast-close {
|
||||
background: none;
|
||||
@@ -863,14 +907,17 @@ button {
|
||||
.mt-lg {
|
||||
margin-top: 26px;
|
||||
}
|
||||
.mb-sm {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.mb {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.text-soft {
|
||||
color: var(--c-text-soft);
|
||||
color: #5a6379;
|
||||
}
|
||||
.text-faint {
|
||||
color: var(--c-text-faint);
|
||||
color: #7b849c;
|
||||
}
|
||||
.text-sm {
|
||||
font-size: 13px;
|
||||
@@ -933,3 +980,272 @@ button {
|
||||
border: 1px solid var(--c-border);
|
||||
background: var(--c-surface-2);
|
||||
}
|
||||
|
||||
.layout-split {
|
||||
grid-template-columns: minmax(0, 1fr) 320px;
|
||||
}
|
||||
|
||||
.layout-split-wide {
|
||||
grid-template-columns: minmax(0, 1.4fr) minmax(260px, 1fr);
|
||||
}
|
||||
|
||||
.tabs-select-wrap {
|
||||
display: none;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
/* ---------- Responsive ---------- */
|
||||
@media (max-width: 1080px) {
|
||||
.navbar-inner {
|
||||
padding: 0 16px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.page {
|
||||
padding: 24px 16px 42px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.list-item {
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.list-item-actions {
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.layout-split,
|
||||
.layout-split-wide {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
:root {
|
||||
--radius: 10px;
|
||||
--radius-lg: 14px;
|
||||
--h1: 26px;
|
||||
--h2: 22px;
|
||||
--h3: 18px;
|
||||
--h4: 15px;
|
||||
}
|
||||
|
||||
.navbar-inner {
|
||||
height: auto;
|
||||
min-height: 64px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.brand {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.brand-text {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.nav-mobile-toggle {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.nav-collapse {
|
||||
width: 100%;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 8px 0 2px;
|
||||
border-top: 1px solid var(--c-border);
|
||||
}
|
||||
|
||||
.nav-collapse.open {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
width: 100%;
|
||||
min-height: 42px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.nav-spacer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-user {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
border-top: 1px solid var(--c-border);
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.page-header p,
|
||||
.page-lead {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.page-header-actions {
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-header-actions .btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-head,
|
||||
.card-body,
|
||||
.card-pad {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.card-head {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.card-head > .btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-head h3 {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
gap: 6px;
|
||||
margin-left: -4px;
|
||||
margin-right: -4px;
|
||||
padding: 0 4px;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.tabs-select-wrap {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tab {
|
||||
min-height: 44px;
|
||||
padding: 10px 13px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
min-height: 42px;
|
||||
}
|
||||
|
||||
.btn.btn-sm {
|
||||
min-height: 38px;
|
||||
}
|
||||
|
||||
.btn.btn-lg {
|
||||
min-height: 46px;
|
||||
}
|
||||
|
||||
.mobile-stack {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.mobile-stack > .btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
padding: 12px;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.modal {
|
||||
max-height: 94vh;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.modal-head,
|
||||
.modal-body,
|
||||
.modal-foot {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
.modal-foot {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.modal-foot .btn {
|
||||
flex: 1;
|
||||
min-width: 130px;
|
||||
}
|
||||
|
||||
.toast-stack {
|
||||
top: auto;
|
||||
bottom: 12px;
|
||||
right: 12px;
|
||||
left: 12px;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.spinner-center {
|
||||
padding: 36px 0;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 34px 16px;
|
||||
}
|
||||
|
||||
.empty-state h3 {
|
||||
font-size: 18px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.empty-state .btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
max-height: 320px;
|
||||
font-size: 12px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.progress {
|
||||
height: 9px;
|
||||
}
|
||||
|
||||
.list-item-sub {
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 761px) {
|
||||
.nav-mobile-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-collapse {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ export default function CreateTemplatePage() {
|
||||
<div className="page-header">
|
||||
<div>
|
||||
<h1>Nuevo examen</h1>
|
||||
<p>Define la estructura. Después podrás subir material y generar preguntas.</p>
|
||||
<p className="page-lead">Define la estructura. Después podrás subir material y generar preguntas.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -192,7 +192,7 @@ export default function CreateTemplatePage() {
|
||||
className="card"
|
||||
style={{ padding: 16, marginBottom: 14, background: "var(--c-surface-2)" }}
|
||||
>
|
||||
<div className="flex justify-between items-center mb">
|
||||
<div className="flex justify-between items-center mb wrap gap-sm">
|
||||
<strong>Bloque {idx + 1}</strong>
|
||||
{types.length > 1 && (
|
||||
<Button
|
||||
@@ -263,7 +263,7 @@ export default function CreateTemplatePage() {
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
<div style={{ paddingTop: 26 }}>
|
||||
<div style={{ paddingTop: 8 }}>
|
||||
<Checkbox
|
||||
label="Permitir varias respuestas correctas"
|
||||
checked={t.multiple_correct}
|
||||
@@ -343,7 +343,7 @@ export default function CreateTemplatePage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap justify-between">
|
||||
<div className="flex gap justify-between wrap mobile-stack mt">
|
||||
<Button type="button" variant="ghost" onClick={() => navigate("/")}>
|
||||
Cancelar
|
||||
</Button>
|
||||
|
||||
@@ -32,10 +32,10 @@ export default function DashboardPage() {
|
||||
<div className="page-header">
|
||||
<div>
|
||||
<h1>Mis exámenes</h1>
|
||||
<p>Gestiona tus plantillas de examen y genera preguntas con IA.</p>
|
||||
<p className="page-lead">Gestiona tus plantillas de examen y genera preguntas con IA.</p>
|
||||
</div>
|
||||
<div className="page-header-actions">
|
||||
<Button onClick={() => navigate("/plantillas/nueva")}>
|
||||
<Button onClick={() => navigate("/plantillas/nueva")} size="lg">
|
||||
<Icon name="plus" size={16} className="icon-inline" />
|
||||
Nuevo examen
|
||||
</Button>
|
||||
|
||||
@@ -154,6 +154,24 @@ export default function TemplateDetailPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="tabs-select-wrap">
|
||||
<label className="field-label" htmlFor="template-tab-select">
|
||||
Sección actual
|
||||
</label>
|
||||
<select
|
||||
id="template-tab-select"
|
||||
className="select"
|
||||
value={tab}
|
||||
onChange={(e) => setTab(e.target.value)}
|
||||
>
|
||||
{TABS.map((t) => (
|
||||
<option key={t.id} value={t.id}>
|
||||
{t.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="tabs">
|
||||
{TABS.map((t) => (
|
||||
<button
|
||||
|
||||
@@ -73,7 +73,7 @@ export default function ExportTab({ templateId, template, questions }) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p className="text-soft mb">
|
||||
<p className="text-soft page-lead mb">
|
||||
Tu examen tiene <strong>{questions.length} preguntas</strong>. Elige un
|
||||
formato para descargarlo o previsualizarlo.
|
||||
</p>
|
||||
@@ -87,7 +87,7 @@ export default function ExportTab({ templateId, template, questions }) {
|
||||
<p className="text-sm text-soft" style={{ minHeight: 60 }}>
|
||||
{fmt.desc}
|
||||
</p>
|
||||
<div className="flex gap-sm">
|
||||
<div className="flex gap-sm mobile-stack">
|
||||
<Button
|
||||
onClick={() => run(fmt, { download: true })}
|
||||
loading={loadingFormat === fmt.id}
|
||||
|
||||
@@ -104,7 +104,7 @@ export default function GenerateTab({
|
||||
const topicTooShort = topic.trim().length < 5;
|
||||
|
||||
return (
|
||||
<div className="grid" style={{ gridTemplateColumns: "1fr 340px" }}>
|
||||
<div className="grid layout-split">
|
||||
<div>
|
||||
<div className="tabs" style={{ marginBottom: 18 }}>
|
||||
{MODES.map((m) => (
|
||||
@@ -122,6 +122,9 @@ export default function GenerateTab({
|
||||
{mode !== "parse" && (
|
||||
<div className="card mb">
|
||||
<div className="card-body">
|
||||
<h3 className="section-title mb-sm">
|
||||
{mode === "auto" ? "Generación automática" : "Construcción de prompt"}
|
||||
</h3>
|
||||
<Field
|
||||
label="Tema / instrucciones para la IA"
|
||||
hint="Describe el contenido o enfoque del examen (mínimo 5 caracteres)."
|
||||
@@ -157,7 +160,7 @@ export default function GenerateTab({
|
||||
</Field>
|
||||
)}
|
||||
|
||||
<div className="flex gap mt">
|
||||
<div className="flex gap mt mobile-stack">
|
||||
{mode === "auto" ? (
|
||||
<Button
|
||||
size="lg"
|
||||
@@ -244,7 +247,7 @@ export default function GenerateTab({
|
||||
|
||||
{generated.length > 0 && (
|
||||
<div className="mt-lg">
|
||||
<div className="flex justify-between items-center mb">
|
||||
<div className="flex justify-between items-center mb wrap gap-sm">
|
||||
<h3 style={{ margin: 0 }}>
|
||||
Resultado ({generated.length} preguntas)
|
||||
</h3>
|
||||
|
||||
@@ -65,10 +65,12 @@ export default function ImagesTab({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid" style={{ gridTemplateColumns: "1fr 320px" }}>
|
||||
<div className="grid layout-split">
|
||||
<div>
|
||||
<div className="card mb">
|
||||
<div className="card-body">
|
||||
<h3 className="section-title">Subir imágenes del examen</h3>
|
||||
<p className="section-subtle mb">Añade recursos visuales para preguntas con soporte gráfico.</p>
|
||||
<FileDropzone
|
||||
accept={IMAGE_ACCEPT}
|
||||
icon="image"
|
||||
|
||||
@@ -67,10 +67,12 @@ export default function MaterialsTab({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid" style={{ gridTemplateColumns: "1fr 320px" }}>
|
||||
<div className="grid layout-split">
|
||||
<div>
|
||||
<div className="card mb">
|
||||
<div className="card-body">
|
||||
<h3 className="section-title">Subir material para IA</h3>
|
||||
<p className="section-subtle mb">Soporta PDF, DOCX, TXT, MD e imágenes con OCR.</p>
|
||||
{uploading ? (
|
||||
<div className="text-center" style={{ padding: 24 }}>
|
||||
<Spinner large />
|
||||
@@ -127,7 +129,7 @@ export default function MaterialsTab({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-sm items-center" style={{ flex: "none" }}>
|
||||
<div className="flex gap-sm items-center mobile-stack list-item-actions" style={{ flex: "none" }}>
|
||||
<Badge variant={badge.variant}>{badge.label}</Badge>
|
||||
<Button
|
||||
variant="danger-ghost"
|
||||
|
||||
@@ -13,18 +13,18 @@ export default function OverviewTab({ template, storage, goToTab }) {
|
||||
const profile = template.difficulty_profile || {};
|
||||
|
||||
return (
|
||||
<div className="grid" style={{ gridTemplateColumns: "1.4fr 1fr" }}>
|
||||
<div className="grid layout-split-wide">
|
||||
<div>
|
||||
<div className="card mb">
|
||||
<div className="card-head">
|
||||
<h3>Estructura del examen</h3>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<h4 className="text-soft text-sm">Tipos de pregunta</h4>
|
||||
<h4 className="section-title">Tipos de pregunta</h4>
|
||||
{qTypes.map((qt, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex justify-between items-center"
|
||||
className="flex justify-between items-center wrap gap-sm"
|
||||
style={{
|
||||
padding: "10px 0",
|
||||
borderBottom:
|
||||
@@ -50,7 +50,7 @@ export default function OverviewTab({ template, storage, goToTab }) {
|
||||
))}
|
||||
|
||||
<div className="divider-line" />
|
||||
<h4 className="text-soft text-sm">Reparto por dificultad</h4>
|
||||
<h4 className="section-title">Reparto por dificultad</h4>
|
||||
<div className="flex gap-sm wrap">
|
||||
{Object.entries(profile).map(([key, val]) =>
|
||||
val > 0 ? (
|
||||
@@ -62,6 +62,7 @@ export default function OverviewTab({ template, storage, goToTab }) {
|
||||
</div>
|
||||
|
||||
<div className="divider-line" />
|
||||
<h4 className="section-title">Opciones activas</h4>
|
||||
<div className="flex gap-sm wrap text-sm">
|
||||
<Badge variant={template.settings?.shuffle_questions ? "success" : undefined}>
|
||||
<Icon
|
||||
|
||||
@@ -52,8 +52,8 @@ export default function QuestionsTab({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb">
|
||||
<p className="text-soft" style={{ margin: 0 }}>
|
||||
<div className="flex justify-between items-center mb wrap gap-sm">
|
||||
<p className="text-soft page-lead" style={{ margin: 0 }}>
|
||||
{questions.length} preguntas guardadas. Vincula imágenes a las
|
||||
preguntas que las necesiten.
|
||||
</p>
|
||||
|
||||
Reference in New Issue
Block a user