fix(web): show only real inventory instances
This commit is contained in:
@@ -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,19 +149,25 @@ 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>
|
||||
{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><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>
|
||||
</tr>)}
|
||||
</tbody></table></div>
|
||||
<div className="pagination"><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 className="button secondary" disabled={page >= meta.totalPages} onClick={() => setPage(page + 1)}>Siguiente</button></div>
|
||||
</div>}
|
||||
{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 ? <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>
|
||||
</tr>)}
|
||||
</tbody></table></div>
|
||||
<div className="pagination"><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 className="button secondary" disabled={page >= meta.totalPages} onClick={() => setPage(page + 1)}>Siguiente</button></div>
|
||||
</div>}
|
||||
</section>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user