fix(web): show only real inventory instances

This commit is contained in:
2026-09-08 20:05:04 -03:00
parent 3f0148e4b7
commit 4bc54f67fe
+54 -26
View File
@@ -15,14 +15,15 @@ import {
ASSET_OPERATIONAL_STATUSES,
ASSET_STATUSES,
} from '../features/assets/assetPresentation';
import { listAssets, listAssetTree, listAssetTypes } from '../lib/api';
import { listAssetTypes } from '../lib/api';
import type {
AssetInformationStatus,
AssetListItem,
AssetOperationalStatus,
AssetType,
PageMeta,
} from '../lib/api';
import { listRealInventory } from '../lib/inventoryBrowserApi';
import type { InventoryListItem, InventoryQuery } from '../lib/inventoryBrowserApi';
import { formatDate } from '../lib/format';
const quickViews = [
@@ -35,7 +36,7 @@ const quickViews = [
export function AssetsPage() {
const [urlParams, setUrlParams] = useSearchParams();
const operationalContext = useOperationalContext();
const [assets, setAssets] = useState<AssetListItem[]>([]);
const [assets, setAssets] = useState<InventoryListItem[]>([]);
const [types, setTypes] = useState<AssetType[]>([]);
const [meta, setMeta] = useState<PageMeta>({ page: 1, pageSize: 25, total: 0, totalPages: 0 });
const [loading, setLoading] = useState(true);
@@ -59,25 +60,39 @@ export function AssetsPage() {
const effectiveOperationalStatus = quick === 'out' ? 'OUT_OF_SERVICE' as AssetOperationalStatus : operationalStatus;
useEffect(() => {
listAssetTypes().then(setTypes).catch(() => undefined);
listAssetTypes().then((data) => setTypes(data.filter((type) => ['instalacion','subinstalacion'].includes(type.code.toLowerCase())))).catch(() => undefined);
}, []);
useEffect(() => {
if (view !== 'list') return;
setLoading(true); setError('');
listAssets({
page, pageSize: 25, search, typeId, status,
setLoading(true);
setError('');
listRealInventory({
page,
pageSize: 25,
search,
typeId,
status,
operationalStatus: effectiveOperationalStatus,
operationalAreaId, operatorCompanyId, needsValidation, hasGeometry,
operationalAreaId,
operatorCompanyId,
needsValidation,
hasGeometry,
})
.then((response) => { setAssets(response.data); setMeta(response.meta); })
.catch((requestError) => setError(errorMessage(requestError)))
.finally(() => setLoading(false));
}, [view, page, search, typeId, status, effectiveOperationalStatus, operationalAreaId, operatorCompanyId, needsValidation, hasGeometry]);
const filters: Parameters<typeof listAssetTree>[0] = useMemo(() => ({
search, typeId, status, operationalStatus: effectiveOperationalStatus,
operationalAreaId, operatorCompanyId, needsValidation, hasGeometry,
const filters: InventoryQuery = useMemo(() => ({
search,
typeId,
status,
operationalStatus: effectiveOperationalStatus,
operationalAreaId,
operatorCompanyId,
needsValidation,
hasGeometry,
}), [search, typeId, status, effectiveOperationalStatus, operationalAreaId, operatorCompanyId, needsValidation, hasGeometry]);
const update = (changes: Record<string, string | null>) => {
@@ -91,18 +106,25 @@ export function AssetsPage() {
const clearFilters = () => {
const next = new URLSearchParams();
if (view === 'list') next.set('view', 'list');
['section', 'companyId', 'parentId'].forEach((key) => {
const value = urlParams.get(key);
if (value) next.set(key, value);
});
setDraftSearch(''); setUrlParams(next);
const parentId=urlParams.get('parentId');
if (parentId) next.set('parentId',parentId);
setDraftSearch('');
setUrlParams(next);
};
const setPage = (value: number) => {
const next = new URLSearchParams(urlParams);
value > 1 ? next.set('page', String(value)) : next.delete('page');
setUrlParams(next);
};
const setPage = (value: number) => { const next = new URLSearchParams(urlParams); value > 1 ? next.set('page', String(value)) : next.delete('page'); setUrlParams(next); };
const advancedActive = Boolean(typeId || status || rawOperationalStatus);
return <section>
<div className="page-heading asset-center-heading">
<div><span className="eyebrow">INVENTARIOS</span><h1>Inventarios</h1><p>Consultá y administrá el inventario operativo de cada empresa desde un solo lugar.</p></div>
<div>
<span className="eyebrow">INVENTARIOS</span>
<h1>Inventarios</h1>
<p>Instancias reales registradas en campo, organizadas por Área Yacimiento Instalación Subinstalación.</p>
</div>
<PermissionGate permission="assets.create"><Link className="button primary" to="/inventarios/nuevo"><Icon name="plus" />Nuevo registro</Link></PermissionGate>
</div>
@@ -111,15 +133,15 @@ export function AssetsPage() {
<div className="asset-center-controls">
<form className="asset-center-search" onSubmit={applySearch}>
<Icon name="search" />
<input value={draftSearch} onChange={(event) => setDraftSearch(event.target.value)} placeholder="Buscar registro, código o identificación…" />
<input value={draftSearch} onChange={(event) => setDraftSearch(event.target.value)} placeholder="Buscar Inventario, código o identificación…" />
<button className="button primary" type="submit">Buscar</button>
</form>
<div className="quick-view-row" aria-label="Vistas rápidas">
{quickViews.map((item) => <button key={item.key} type="button" className={quick === item.key ? 'active' : ''} onClick={() => setQuick(item.key)}>{item.label}</button>)}
<button type="button" className={advancedOpen || advancedActive ? 'advanced active' : 'advanced'} onClick={() => setAdvancedOpen((current) => !current)}>Más filtros</button>
{view === 'list' && <button type="button" className={advancedOpen || advancedActive ? 'advanced active' : 'advanced'} onClick={() => setAdvancedOpen((current) => !current)}>Más filtros</button>}
</div>
{(advancedOpen || advancedActive) && <div className="advanced-filter-panel">
<label className="field compact-field"><span>Tipo</span><SearchableSelect value={typeId} onChange={(event) => update({ typeId: event.target.value || null })}><option value="">Todos</option>{types.map((type) => <option key={type.id} value={type.id}>{type.name}</option>)}</SearchableSelect></label>
{view === 'list' && (advancedOpen || advancedActive) && <div className="advanced-filter-panel">
<label className="field compact-field"><span>Nivel</span><SearchableSelect value={typeId} onChange={(event) => update({ typeId: event.target.value || null })}><option value="">Instalaciones y Subinstalaciones</option>{types.map((type) => <option key={type.id} value={type.id}>{type.name}</option>)}</SearchableSelect></label>
<label className="field compact-field"><span>Estado del dato</span><SearchableSelect value={status} onChange={(event) => update({ status: event.target.value || null })}><option value="">Todos</option>{ASSET_STATUSES.map((item) => <option key={item.value} value={item.value}>{item.label}</option>)}</SearchableSelect></label>
<label className="field compact-field"><span>Estado operativo</span><SearchableSelect value={rawOperationalStatus} onChange={(event) => update({ operationalStatus: event.target.value || null, quick: quick === 'out' ? null : quick === 'all' ? null : quick })}><option value="">Todos</option>{ASSET_OPERATIONAL_STATUSES.map((item) => <option key={item.value} value={item.value}>{item.label}</option>)}</SearchableSelect></label>
<button type="button" className="button text filter-clear" onClick={clearFilters}>Limpiar filtros</button>
@@ -127,13 +149,19 @@ export function AssetsPage() {
</div>
{error && <Alert>{error}</Alert>}
{view === 'hierarchy' ? <AssetHierarchyView filters={filters} /> : loading ? <LoadingBlock label="Cargando inventario…" /> : assets.length === 0 ? <EmptyState title="No encontramos registros" text="Probá con otra búsqueda o cambiá los filtros seleccionados." /> : <div className="table-panel compact-assets-table">
<div className="table-summary"><strong>{meta.total} registro{meta.total === 1 ? '' : 's'}</strong><span>Página {meta.page} de {Math.max(meta.totalPages, 1)}</span></div>
<div className="table-scroll"><table><thead><tr><th>Registro</th><th>Tipo</th><th>Área / Operadora</th><th>Estado</th><th>Actualizado</th><th aria-label="Acciones" /></tr></thead><tbody>
{view === 'hierarchy'
? <AssetHierarchyView filters={filters} />
: loading
? <LoadingBlock label="Cargando Inventario real…" />
: assets.length === 0
? <EmptyState title="Todavía no hay Inventario real" text="Las Áreas y Yacimientos precargados son contexto. Las Instalaciones/Subinstalaciones aparecerán aquí cuando sean registradas realmente." />
: <div className="table-panel compact-assets-table">
<div className="table-summary"><strong>{meta.total} instancia{meta.total === 1 ? '' : 's'} real{meta.total === 1 ? '' : 'es'}</strong><span>Página {meta.page} de {Math.max(meta.totalPages, 1)}</span></div>
<div className="table-scroll"><table><thead><tr><th>Inventario</th><th>Nivel</th><th>Área / Operadora vigente</th><th>Estado</th><th>Actualizado</th><th aria-label="Acciones" /></tr></thead><tbody>
{assets.map((asset) => <tr key={asset.id}>
<td><div className="asset-cell"><span className="asset-symbol"><Icon name="layers" size={16} /></span><div><Link to={`/inventarios/${asset.id}`} className="table-primary">{asset.name}</Link><small>{asset.code}{asset.parent ? ` · en ${asset.parent.name}` : ''}</small></div></div></td>
<td><span className="tag">{asset.type.name}</span></td>
<td>{asset.operationalArea || asset.operatorCompany ? <span><strong className="table-primary">{asset.operationalArea?.name ?? 'Sin área'}</strong><small className="cell-subtext">{asset.operatorCompany?.name ?? 'Sin operadora'}</small></span> : <span className="muted">Sin contexto</span>}</td>
<td>{asset.operationalArea ? <span><strong className="table-primary">{asset.operationalArea.name}</strong><small className="cell-subtext">{asset.operatorCompany?.name ?? 'Sin operadora vigente'}</small></span> : <span className="muted">Sin Área</span>}</td>
<td><div className="dual-status"><span className={`status-badge ${assetStatusClass(asset.informationStatus)}`}>{assetStatusLabel(asset.informationStatus)}</span><small>{assetOperationalStatusLabel(asset.operationalStatus)}</small></div></td>
<td>{formatDate(asset.updatedAt)}</td>
<td className="action-cell"><Link className="icon-button" to={`/inventarios/${asset.id}`} aria-label={`Abrir ${asset.name}`}><Icon name="chevron" /></Link></td>