feat(export): perfiles DSpace/EPrints/Dublin Core y selector SWORD en UI

Backend: generadores por repositorio, ZIP multi-formato y query profile en /export/sword. Frontend: selector Destino que envia profile al descargar SWORD XML.
This commit is contained in:
Mireya Cueto Garrido
2026-05-20 13:25:35 +02:00
parent 9b596af494
commit aa2e7280dc
9 changed files with 585 additions and 64 deletions
@@ -7,13 +7,15 @@ import {
SparkleIcon,
} from "../ui/Icons";
import { Spinner } from "../ui/Spinner";
import { SwordProfileSelect } from "./SwordProfileSelect";
import { DEFAULT_EXPORT_PROFILE } from "../../utils/exportProfiles";
const FORMATS = [
{
format: "xml",
icon: <DocumentIcon size={20} className="shrink-0 text-ink-secondary" />,
label: "SWORD XML",
desc: "Metadatos en formato Atom",
desc: "Según destino seleccionado",
},
{
format: "zip",
@@ -31,6 +33,8 @@ const FORMATS = [
* - `newPublicationsCount` → cuántas publicaciones tiene downloaded_by_me=false.
* - `selectedCount` → publicaciones seleccionadas manualmente.
* - `exportingFormat` → formato en curso (pone el botón en loading).
* - `swordProfile` → perfil SWORD (dublin_core, dspace, eprints…).
* - `onSwordProfileChange` → callback al cambiar destino.
*/
export function ExportDropdown({
onExport,
@@ -38,6 +42,8 @@ export function ExportDropdown({
selectedCount = 0,
isAuthenticated = false,
newPublicationsCount = 0,
swordProfile = DEFAULT_EXPORT_PROFILE,
onSwordProfileChange,
}) {
const [open, setOpen] = useState(false);
const rootRef = useRef(null);
@@ -57,7 +63,7 @@ export function ExportDropdown({
function handlePick(format) {
setOpen(false);
onExport(format);
onExport(format, format === "xml" ? swordProfile : undefined);
}
// Label logic:
@@ -80,7 +86,14 @@ export function ExportDropdown({
}
return (
<div className="relative" ref={rootRef}>
<div className="flex flex-wrap items-center justify-end gap-2">
<SwordProfileSelect
id="dashboard-sword-profile"
value={swordProfile}
onChange={onSwordProfileChange}
/>
<div className="relative" ref={rootRef}>
<button
type="button"
onClick={() => setOpen((o) => !o)}
@@ -124,6 +137,7 @@ export function ExportDropdown({
))}
</div>
)}
</div>
</div>
);
}
@@ -0,0 +1,35 @@
import {
DEFAULT_EXPORT_PROFILE,
EXPORT_PROFILE_OPTIONS,
} from "../../utils/exportProfiles";
/**
* Selector de destino para exportación SWORD XML (DSpace, EPrints, Dublin Core…).
*/
export function SwordProfileSelect({
value = DEFAULT_EXPORT_PROFILE,
onChange,
id = "sword-export-profile",
className = "",
}) {
return (
<label
htmlFor={id}
className={`flex items-center gap-2 text-sm ${className}`.trim()}
>
<span className="whitespace-nowrap text-ink-tertiary">Destino:</span>
<select
id={id}
value={value}
onChange={(event) => onChange(event.target.value)}
className="min-w-[9.5rem] rounded-lg border border-surface-border-strong bg-surface-primary px-2.5 py-2 text-sm font-medium text-ink-primary transition-colors hover:bg-surface-secondary focus:border-brand-primary focus:outline-none focus:ring-2 focus:ring-brand-primary/20"
>
{EXPORT_PROFILE_OPTIONS.map(({ value: optionValue, label }) => (
<option key={optionValue} value={optionValue}>
{label}
</option>
))}
</select>
</label>
);
}