chore: import DH V2 D5.6.4 production baseline

This commit is contained in:
DH V2
2026-09-05 10:12:35 -03:00
commit 82213e72f5
757 changed files with 84218 additions and 0 deletions
+130
View File
@@ -0,0 +1,130 @@
import { SearchableSelect } from '../components/SearchableSelect';
import { useEffect, useState } from 'react';
import type { FormEvent } from 'react';
import { Link } from 'react-router';
import { Alert, EmptyState, LoadingBlock, errorMessage } from '../components/Feedback';
import { Icon } from '../components/Icon';
import { AssetCenterTabs } from '../features/assets/AssetCenterTabs';
import { assetStatusLabel, ASSET_STATUSES } from '../features/assets/assetPresentation';
import { AssetVersionDrawer } from '../features/assets/AssetVersionDrawer';
import {
ASSET_VERSION_CHANGES,
assetVersionChangeLabel,
assetVersionFieldLabel,
} from '../features/assets/assetVersionPresentation';
import {
getAssetVersion,
listAssetTypes,
listAssetVersions,
} from '../lib/api';
import type {
AssetInformationStatus,
AssetType,
AssetVersionChangeType,
AssetVersionDetail,
AssetVersionSummary,
PageMeta,
} from '../lib/api';
import { formatDate } from '../lib/format';
interface HistoryFilters {
search: string;
typeId: string;
status: AssetInformationStatus | '';
changeType: AssetVersionChangeType | '';
from: string;
to: string;
}
const emptyFilters: HistoryFilters = {
search: '',
typeId: '',
status: '',
changeType: '',
from: '',
to: '',
};
export function HistoryPage() {
const [versions, setVersions] = useState<AssetVersionSummary[]>([]);
const [types, setTypes] = useState<AssetType[]>([]);
const [meta, setMeta] = useState<PageMeta>({ page: 1, pageSize: 25, total: 0, totalPages: 0 });
const [draft, setDraft] = useState<HistoryFilters>(emptyFilters);
const [filters, setFilters] = useState<HistoryFilters>(emptyFilters);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const [detail, setDetail] = useState<AssetVersionDetail | null>(null);
const [detailLoading, setDetailLoading] = useState(false);
useEffect(() => {
listAssetTypes().then(setTypes).catch(() => undefined);
}, []);
useEffect(() => {
setLoading(true);
setError('');
listAssetVersions({
page,
pageSize: 25,
search: filters.search,
typeId: filters.typeId,
status: filters.status,
changeType: filters.changeType,
from: filters.from ? `${filters.from}T00:00:00.000Z` : undefined,
to: filters.to ? `${filters.to}T23:59:59.999Z` : undefined,
})
.then((response) => {
setVersions(response.data);
setMeta(response.meta);
})
.catch((requestError) => setError(errorMessage(requestError)))
.finally(() => setLoading(false));
}, [filters, page]);
const set = <K extends keyof HistoryFilters>(key: K, value: HistoryFilters[K]) => {
setDraft((current) => ({ ...current, [key]: value }));
};
const apply = (event: FormEvent) => {
event.preventDefault();
setPage(1);
setFilters({ ...draft, search: draft.search.trim() });
};
const clear = () => {
setDraft(emptyFilters);
setFilters(emptyFilters);
setPage(1);
};
const open = async (version: AssetVersionSummary) => {
setDetail(null);
setDetailLoading(true);
setError('');
try {
setDetail(await getAssetVersion(version.assetId, version.versionNumber));
} catch (requestError) {
setError(errorMessage(requestError));
} finally {
setDetailLoading(false);
}
};
return <section>
<div className="page-heading"><div><span className="eyebrow">INVENTARIOS</span><h1>Historial</h1><p>Historial completo de cambios realizados sobre los inventarios.</p></div><span className="count-pill large">{meta.total} versiones</span></div>
<AssetCenterTabs active="history" />
<form className="history-filters panel" onSubmit={apply}>
<label className="search-field"><Icon name="search" /><input value={draft.search} onChange={(event) => set('search', event.target.value)} placeholder="Código, registro o usuario" /></label>
<label className="field compact-field"><span>Tipo</span><SearchableSelect value={draft.typeId} onChange={(event) => set('typeId', event.target.value)}><option value="">Todos</option>{types.map((type) => <option key={type.id} value={type.id}>{type.name}</option>)}</SearchableSelect></label>
<label className="field compact-field"><span>Estado</span><SearchableSelect value={draft.status} onChange={(event) => set('status', event.target.value as AssetInformationStatus | '')}><option value="">Todos</option>{ASSET_STATUSES.map((status) => <option key={status.value} value={status.value}>{status.label}</option>)}</SearchableSelect></label>
<label className="field compact-field"><span>Cambio</span><SearchableSelect value={draft.changeType} onChange={(event) => set('changeType', event.target.value as AssetVersionChangeType | '')}><option value="">Todos</option>{ASSET_VERSION_CHANGES.map((change) => <option key={change.value} value={change.value}>{change.label}</option>)}</SearchableSelect></label>
<label className="field compact-field"><span>Desde</span><input type="date" value={draft.from} onChange={(event) => set('from', event.target.value)} /></label>
<label className="field compact-field"><span>Hasta</span><input type="date" value={draft.to} onChange={(event) => set('to', event.target.value)} /></label>
<div className="filter-actions"><button className="button text" type="button" onClick={clear}>Limpiar</button><button className="button primary">Aplicar filtros</button></div>
</form>
{error && <Alert>{error}</Alert>}
{loading ? <LoadingBlock label="Consultando versiones…" /> : versions.length === 0 ? <EmptyState title="Sin versiones" text="No hay versiones históricas para los filtros seleccionados." /> : <div className="table-panel history-table"><div className="table-summary"><strong>{meta.total} versiones registradas</strong><span>Página {page} de {Math.max(meta.totalPages, 1)}</span></div><div className="table-scroll"><table><thead><tr><th>Fecha y hora</th><th>Registro</th><th>Versión</th><th>Cambio</th><th>Campos</th><th>Usuario</th><th>Estado</th><th /></tr></thead><tbody>{versions.map((version) => <tr key={version.id}><td>{formatDate(version.occurredAt)}</td><td><Link className="history-asset-link" to={`/inventarios/${version.assetId}`}><strong>{version.assetName}</strong><small>{version.assetCode} · {version.typeName}</small></Link></td><td><span className={`version-badge ${version.isCurrent ? 'current' : ''}`}>v{version.versionNumber}{version.isCurrent ? ' · actual' : ''}</span></td><td><strong>{assetVersionChangeLabel(version.changeType)}</strong><small className="cell-subtext">{version.source}</small></td><td><div className="tag-list">{version.changedFields.slice(0, 3).map((field) => <span className="tag" key={field}>{assetVersionFieldLabel(field)}</span>)}</div></td><td>{version.actorUsername ?? 'Sistema'}</td><td>{assetStatusLabel(version.informationStatus)}</td><td><button type="button" className="icon-button" onClick={() => open(version)} aria-label={`Ver versión ${version.versionNumber}`}><Icon name="chevron" /></button></td></tr>)}</tbody></table></div><div className="pagination"><button type="button" className="button secondary" disabled={page <= 1} onClick={() => setPage(page - 1)}>Anterior</button><span>{meta.total ? `${(page - 1) * meta.pageSize + 1}${Math.min(page * meta.pageSize, meta.total)} de ${meta.total}` : '0 resultados'}</span><button type="button" className="button secondary" disabled={page >= meta.totalPages} onClick={() => setPage(page + 1)}>Siguiente</button></div></div>}
{(detailLoading || detail) && <AssetVersionDrawer detail={detail} loading={detailLoading} onClose={() => setDetail(null)} />}
</section>;
}