import { SearchableSelect } from '../components/SearchableSelect'; import { useEffect, useMemo, useState } from 'react'; import type { FormEvent } from 'react'; import { Link, useSearchParams } from 'react-router'; import { PermissionGate } from '../auth/PermissionGate'; import { Alert, EmptyState, LoadingBlock, errorMessage } from '../components/Feedback'; import { Icon } from '../components/Icon'; import { useOperationalContext } from '../context/OperationalContext'; import { AssetCenterTabs } from '../features/assets/AssetCenterTabs'; import { AssetHierarchyView } from '../features/assets/AssetHierarchyView'; import { assetOperationalStatusLabel, assetStatusClass, assetStatusLabel, ASSET_OPERATIONAL_STATUSES, ASSET_STATUSES, } from '../features/assets/assetPresentation'; import { listAssetTypes } from '../lib/api'; import type { AssetInformationStatus, AssetOperationalStatus, AssetType, PageMeta, } from '../lib/api'; import { listInventory } from '../lib/inventoryBrowserApi'; import type { InventoryListItem, InventoryQuery } from '../lib/inventoryBrowserApi'; import { formatDate } from '../lib/format'; const quickViews = [ { key: 'all', label: 'Todos' }, { key: 'validation', label: 'Pendientes de validar' }, { key: 'location', label: 'Sin ubicación' }, { key: 'out', label: 'Fuera de servicio' }, ] as const; const structuredTypeCopy: Record = { departamento: { title: 'Departamentos', description: 'Raíz territorial del Inventario. Cada Departamento contiene sus Áreas.', }, area: { title: 'Áreas', description: 'Áreas dentro de un Departamento, con sus Empresas operadoras vinculadas.', }, yacimiento: { title: 'Yacimientos', description: 'Yacimientos pertenecientes a un Área y utilizados para organizar sus Instalaciones.', }, instalacion: { title: 'Instalaciones', description: 'Instalaciones dentro de un Yacimiento, con clasificación técnica y contexto operativo.', }, subinstalacion: { title: 'Subinstalaciones', description: 'Subinstalaciones dependientes de una Instalación, último nivel de la estructura.', }, }; function structuredPresentation(type: AssetType | undefined) { if (!type) return null; if (type.operationalRole === 'COMPANY') { return { title: 'Empresas', description: 'Maestro independiente de Empresas operadoras. Se vinculan a una o más Áreas.', }; } return structuredTypeCopy[type.code.toLowerCase()] ?? null; } export function AssetsPage() { const [urlParams, setUrlParams] = useSearchParams(); const operationalContext = useOperationalContext(); const [assets, setAssets] = useState([]); const [types, setTypes] = useState([]); const [meta, setMeta] = useState({ page: 1, pageSize: 25, total: 0, totalPages: 0 }); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [advancedOpen, setAdvancedOpen] = useState(false); const [draftSearch, setDraftSearch] = useState(urlParams.get('search') ?? ''); const view = urlParams.get('view') === 'list' ? 'list' : 'hierarchy'; const quick = (urlParams.get('quick') ?? 'all') as typeof quickViews[number]['key']; const search = urlParams.get('search') ?? ''; const typeId = urlParams.get('typeId') ?? ''; const rawStatus = urlParams.get('status') ?? ''; const rawOperationalStatus = urlParams.get('operationalStatus') ?? ''; const operationalAreaId = operationalContext.areaId || urlParams.get('operationalAreaId') || ''; const operatorCompanyId = operationalContext.companyId || urlParams.get('operatorCompanyId') || ''; const status = ASSET_STATUSES.some((item) => item.value === rawStatus) ? rawStatus as AssetInformationStatus : ''; const operationalStatus = ASSET_OPERATIONAL_STATUSES.some((item) => item.value === rawOperationalStatus) ? rawOperationalStatus as AssetOperationalStatus : ''; const page = Math.max(1, Number(urlParams.get('page') ?? 1) || 1); const needsValidation = quick === 'validation' ? true : undefined; const hasGeometry = quick === 'location' ? false : undefined; const effectiveOperationalStatus = quick === 'out' ? 'OUT_OF_SERVICE' as AssetOperationalStatus : operationalStatus; const selectedType = types.find((type) => type.id === typeId); const selectedPresentation = structuredPresentation(selectedType); useEffect(() => { listAssetTypes().then((data) => setTypes(data.filter((type) => type.operationalRole === 'COMPANY' || ['departamento','area','yacimiento','instalacion','subinstalacion'].includes(type.code.toLowerCase()), ))).catch(() => undefined); }, []); useEffect(() => { if (view !== 'list') return; setLoading(true); setError(''); listInventory({ page, pageSize: 25, search, typeId, status, operationalStatus: effectiveOperationalStatus, 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: InventoryQuery = useMemo(() => ({ search, typeId, status, operationalStatus: effectiveOperationalStatus, operationalAreaId, operatorCompanyId, needsValidation, hasGeometry, }), [search, typeId, status, effectiveOperationalStatus, operationalAreaId, operatorCompanyId, needsValidation, hasGeometry]); const update = (changes: Record) => { const next = new URLSearchParams(urlParams); Object.entries(changes).forEach(([key, value]) => value ? next.set(key, value) : next.delete(key)); next.delete('page'); setUrlParams(next); }; const applySearch = (event: FormEvent) => { event.preventDefault(); update({ search: draftSearch.trim() || null }); }; const setQuick = (key: string) => update({ quick: key === 'all' ? null : key, operationalStatus: key === 'out' ? null : rawOperationalStatus || null }); const clearFilters = () => { const next = new URLSearchParams(); if (view === 'list') next.set('view', 'list'); 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 advancedActive = Boolean(typeId || status || rawOperationalStatus); const sectionTitle = view === 'list' && selectedPresentation ? selectedPresentation.title : 'Inventarios'; const sectionDescription = view === 'list' && selectedPresentation ? selectedPresentation.description : 'Empresas y estructura territorial: Departamento → Área → Yacimiento → Instalación → Subinstalación.'; return
{selectedPresentation && view === 'list' ? 'INVENTARIOS · SECCIÓN' : 'INVENTARIOS'}

{sectionTitle}

{sectionDescription}

{selectedPresentation && view === 'list' && Ver estructura} Nuevo registro
setDraftSearch(event.target.value)} placeholder="Buscar por nombre, código o identificación…" />
{quickViews.map((item) => )} {view === 'list' && }
{view === 'list' && (advancedOpen || advancedActive) &&
}
{error && {error}} {view === 'hierarchy' ? : loading ? : assets.length === 0 ? :
{meta.total} registro{meta.total === 1 ? '' : 's'}Página {meta.page} de {Math.max(meta.totalPages, 1)}
{assets.map((asset) => )}
RegistroSecciónUbicación / OperadoraEstadoActualizado
{asset.name}{asset.code}{asset.parent ? ` · en ${asset.parent.name}` : ''}
{asset.type.name} {asset.operationalArea ? {asset.operationalArea.name}{asset.operatorCompany?.name ?? 'Sin operadora vigente'} : asset.operatorCompany ? {asset.operatorCompany.name} : {asset.parent?.name ?? 'Raíz / maestro independiente'}}
{assetStatusLabel(asset.informationStatus)}{assetOperationalStatusLabel(asset.operationalStatus)}
{formatDate(asset.updatedAt)}
{meta.total ? `${(page - 1) * meta.pageSize + 1}–${Math.min(page * meta.pageSize, meta.total)} de ${meta.total}` : '0 resultados'}
}
; }