feat(ui): mejorar la responsividad y estructura de PublicationsTable

Se ajustan los estilos en PublicationsTable para mejorar la responsividad en dispositivos móviles. Se modifica la estructura del componente para incluir un mensaje de estado cuando no se encuentran publicaciones y se optimizan los estilos de los elementos de entrada y botones.
This commit is contained in:
Alexis
2026-06-02 10:58:43 +02:00
parent 6603ddfe23
commit 35eef2ff90
@@ -258,7 +258,7 @@ export function PublicationsTable({
)}
</p>
</div>
<div className="flex flex-wrap items-center gap-2">
<div className="flex w-full flex-wrap items-center gap-2 sm:w-auto">
<button
type="button"
onClick={() => setFiltersOpen((o) => !o)}
@@ -282,7 +282,7 @@ export function PublicationsTable({
className={`transition-transform ${filtersOpen ? "rotate-180" : ""}`}
/>
</button>
<div className="relative">
<div className="relative w-full sm:w-auto">
<input
type="text"
placeholder="Filtrar publicaciones..."
@@ -291,7 +291,7 @@ export function PublicationsTable({
setFilter(e.target.value);
setPage(1);
}}
className="w-[220px] rounded-lg border border-surface-border-strong bg-surface-secondary py-2 pl-9 pr-3.5 text-[13px] text-ink-primary outline-none focus:border-brand-accent"
className="w-full rounded-lg border border-surface-border-strong bg-surface-secondary py-2 pl-9 pr-3.5 text-[13px] text-ink-primary outline-none focus:border-brand-accent sm:w-[220px]"
/>
<span className="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-ink-tertiary/70">
<SearchIcon />
@@ -366,123 +366,217 @@ export function PublicationsTable({
) : loading ? (
<LoadingState />
) : (
<table className="w-full border-collapse">
<thead>
<tr className="bg-surface-secondary">
<th
scope="col"
className="w-10 border-b border-surface-border/60 px-4 py-2.5 text-left"
onClick={(e) => e.stopPropagation()}
>
<TriStateCheckbox
checked={pageSelectionStats.allChecked}
indeterminate={pageSelectionStats.anyChecked}
onChange={toggleCurrentPage}
ariaLabel="Seleccionar todas las publicaciones de esta página"
/>
</th>
{COLUMNS.map((col) => (
<th
key={col.key}
onClick={() => toggleSort(col.key)}
className={`select-none border-b border-surface-border/60 px-4 py-2.5 text-left text-xs font-medium tracking-wide text-ink-secondary${col.thClass ? ` ${col.thClass}` : ""}`}
>
<span className="flex cursor-pointer items-center">
{col.label.toUpperCase()}
<SortIcon
active={sortKey === col.key}
direction={sortDir}
/>
</span>
</th>
))}
</tr>
</thead>
<tbody>
<>
<div className="md:hidden">
{filtered.length === 0 ? (
<tr>
<td
colSpan={COLUMNS.length + 1}
className="p-10 text-center text-sm text-ink-tertiary"
>
No se encontraron publicaciones con los filtros aplicados.
</td>
</tr>
<p className="p-8 text-center text-sm text-ink-tertiary">
No se encontraron publicaciones con los filtros aplicados.
</p>
) : (
pageRows.map((pub, i) => {
const isSelected = selectedIds.has(pub.id);
return (
<tr
key={pub.id}
className={`transition-colors ${
isSelected
? "bg-tag-article-bg/70 hover:bg-tag-article-bg"
: "hover:bg-surface-secondary/70"
} ${
i < pageRows.length - 1
? "border-b border-surface-border/60"
: ""
}`}
<>
<div className="border-b border-surface-border/60 bg-surface-secondary px-4 py-2.5">
<TriStateCheckbox
checked={pageSelectionStats.allChecked}
indeterminate={pageSelectionStats.anyChecked}
onChange={toggleCurrentPage}
ariaLabel="Seleccionar todas las publicaciones de esta página"
/>
</div>
<div>
{pageRows.map((pub, i) => {
const isSelected = selectedIds.has(pub.id);
return (
<article
key={pub.id}
className={`px-4 py-3.5 transition-colors ${
isSelected
? "bg-tag-article-bg/70 hover:bg-tag-article-bg"
: "hover:bg-surface-secondary/70"
} ${
i < pageRows.length - 1
? "border-b border-surface-border/60"
: ""
}`}
>
<div className="mb-2 flex items-start gap-2.5">
<TriStateCheckbox
checked={isSelected}
onChange={() => toggleRow(pub.id)}
ariaLabel={`Seleccionar publicación ${pub.title}`}
/>
<div className="min-w-0 flex-1">
<div className="flex flex-wrap items-start gap-1.5">
{isAuthenticated && pub.downloaded_by_me === false && (
<span
title="No descargada aún por ti"
className="mt-0.5 inline-flex shrink-0 items-center gap-0.5 rounded-full bg-brand-accent/10 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-brand-accent"
>
<SparkleIcon size={9} />
Nuevo
</span>
)}
<p className="text-[14px] font-medium leading-relaxed text-ink-primary">
{pub.title}
</p>
</div>
</div>
</div>
<div className="space-y-1.5 pl-6.5 text-[12px] text-ink-secondary">
<p>
<span className="font-medium text-ink-primary">Revista:</span>{" "}
{pub.journal || "—"}
</p>
<p>
<span className="font-medium text-ink-primary">Año:</span>{" "}
{pub.publication_year ?? "—"}
</p>
<p>
<span className="font-medium text-ink-primary">DOI:</span>{" "}
{pub.doi ? (
<a
href={`https://doi.org/${pub.doi}`}
target="_blank"
rel="noopener noreferrer"
className="break-all font-mono text-[11px] text-brand-accent hover:underline"
>
{pub.doi}
</a>
) : (
<span className="font-mono text-[11px] text-ink-tertiary">
</span>
)}
</p>
<div>
<Badge type={pub.type} />
</div>
</div>
</article>
);
})}
</div>
</>
)}
</div>
<table className="hidden w-full border-collapse md:table">
<thead>
<tr className="bg-surface-secondary">
<th
scope="col"
className="w-10 border-b border-surface-border/60 px-4 py-2.5 text-left"
onClick={(e) => e.stopPropagation()}
>
<TriStateCheckbox
checked={pageSelectionStats.allChecked}
indeterminate={pageSelectionStats.anyChecked}
onChange={toggleCurrentPage}
ariaLabel="Seleccionar todas las publicaciones de esta página"
/>
</th>
{COLUMNS.map((col) => (
<th
key={col.key}
onClick={() => toggleSort(col.key)}
className={`select-none border-b border-surface-border/60 px-4 py-2.5 text-left text-xs font-medium tracking-wide text-ink-secondary${col.thClass ? ` ${col.thClass}` : ""}`}
>
<td
className="w-10 cursor-pointer px-4 py-3.5"
onClick={(e) => {
e.stopPropagation();
toggleRow(pub.id);
}}
>
<TriStateCheckbox
checked={isSelected}
onChange={() => toggleRow(pub.id)}
ariaLabel={`Seleccionar publicación ${pub.title}`}
<span className="flex cursor-pointer items-center">
{col.label.toUpperCase()}
<SortIcon
active={sortKey === col.key}
direction={sortDir}
/>
</td>
<td className="max-w-[280px] px-4 py-3.5 text-[13px] font-medium leading-relaxed text-ink-primary">
<span className="flex flex-wrap items-start gap-1.5">
{isAuthenticated && pub.downloaded_by_me === false && (
<span
title="No descargada aún por ti"
className="mt-0.5 inline-flex shrink-0 items-center gap-0.5 rounded-full bg-brand-accent/10 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-brand-accent"
</span>
</th>
))}
</tr>
</thead>
<tbody>
{filtered.length === 0 ? (
<tr>
<td
colSpan={COLUMNS.length + 1}
className="p-10 text-center text-sm text-ink-tertiary"
>
No se encontraron publicaciones con los filtros aplicados.
</td>
</tr>
) : (
pageRows.map((pub, i) => {
const isSelected = selectedIds.has(pub.id);
return (
<tr
key={pub.id}
className={`transition-colors ${
isSelected
? "bg-tag-article-bg/70 hover:bg-tag-article-bg"
: "hover:bg-surface-secondary/70"
} ${
i < pageRows.length - 1
? "border-b border-surface-border/60"
: ""
}`}
>
<td
className="w-10 cursor-pointer px-4 py-3.5"
onClick={(e) => {
e.stopPropagation();
toggleRow(pub.id);
}}
>
<TriStateCheckbox
checked={isSelected}
onChange={() => toggleRow(pub.id)}
ariaLabel={`Seleccionar publicación ${pub.title}`}
/>
</td>
<td className="max-w-[280px] px-4 py-3.5 text-[13px] font-medium leading-relaxed text-ink-primary">
<span className="flex flex-wrap items-start gap-1.5">
{isAuthenticated && pub.downloaded_by_me === false && (
<span
title="No descargada aún por ti"
className="mt-0.5 inline-flex shrink-0 items-center gap-0.5 rounded-full bg-brand-accent/10 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-brand-accent"
>
<SparkleIcon size={9} />
Nuevo
</span>
)}
{pub.title}
</span>
</td>
<td className="px-4 py-3.5 text-[13px] text-ink-secondary">
{pub.journal || "—"}
</td>
<td className="whitespace-nowrap px-4 py-3.5 text-[13px] font-medium text-ink-primary">
{pub.publication_year ?? "—"}
</td>
<td className="px-4 py-3.5">
{pub.doi ? (
<a
href={`https://doi.org/${pub.doi}`}
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
className="whitespace-nowrap font-mono text-xs text-brand-accent hover:underline"
>
<SparkleIcon size={9} />
Nuevo
{pub.doi}
</a>
) : (
<span className="whitespace-nowrap font-mono text-xs text-ink-tertiary">
</span>
)}
{pub.title}
</span>
</td>
<td className="px-4 py-3.5 text-[13px] text-ink-secondary">
{pub.journal || "—"}
</td>
<td className="whitespace-nowrap px-4 py-3.5 text-[13px] font-medium text-ink-primary">
{pub.publication_year ?? "—"}
</td>
<td className="px-4 py-3.5">
{pub.doi ? (
<a
href={`https://doi.org/${pub.doi}`}
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
className="whitespace-nowrap font-mono text-xs text-brand-accent hover:underline"
>
{pub.doi}
</a>
) : (
<span className="whitespace-nowrap font-mono text-xs text-ink-tertiary">
</span>
)}
</td>
<td className="px-4 py-3.5">
<Badge type={pub.type} />
</td>
</tr>
);
})
)}
</tbody>
</table>
</td>
<td className="px-4 py-3.5">
<Badge type={pub.type} />
</td>
</tr>
);
})
)}
</tbody>
</table>
</>
)}
</div>