|
|
|
@@ -2,17 +2,19 @@ import { useEffect, useMemo, useState } from 'react';
|
|
|
|
|
import type { FormEvent } from 'react';
|
|
|
|
|
import { Link, useSearchParams } from 'react-router';
|
|
|
|
|
import { useAuth } from '../auth/AuthContext';
|
|
|
|
|
import { hasAdministratorRole } from '../auth/adminAccess';
|
|
|
|
|
import { Alert, LoadingBlock, errorMessage } from '../components/Feedback';
|
|
|
|
|
import { Icon } from '../components/Icon';
|
|
|
|
|
import { AssetHistoryPanel } from '../features/assets/AssetHistoryPanel';
|
|
|
|
|
import { assetStatusClass, assetStatusLabel } from '../features/assets/assetPresentation';
|
|
|
|
|
import {
|
|
|
|
|
getAssetLineage,
|
|
|
|
|
listAreaCompanyRelations,
|
|
|
|
|
listAssetTreeChildren,
|
|
|
|
|
listInspectionVisits,
|
|
|
|
|
updateAsset,
|
|
|
|
|
} from '../lib/api';
|
|
|
|
|
import type { AreaCompanyRelation, AssetDetail, AssetListItem } from '../lib/api';
|
|
|
|
|
import type { AreaCompanyRelation, AssetDetail, AssetLineageItem, AssetListItem } from '../lib/api';
|
|
|
|
|
import {
|
|
|
|
|
listInspectionActsGlobalF4,
|
|
|
|
|
type InspectionActStatusF4,
|
|
|
|
@@ -31,8 +33,12 @@ function normalized(value: string) {
|
|
|
|
|
.trim();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function tokensForType(type: { code: string; name: string }) {
|
|
|
|
|
return new Set(`${normalized(type.code)} ${normalized(type.name)}`.split(' ').filter(Boolean));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function typeTokens(asset: AssetDetail) {
|
|
|
|
|
return new Set(`${normalized(asset.type.code)} ${normalized(asset.type.name)}`.split(' ').filter(Boolean));
|
|
|
|
|
return tokensForType(asset.type);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function isTerritorialInventoryAsset(asset: AssetDetail) {
|
|
|
|
@@ -44,6 +50,14 @@ function isOperationalArea(asset: AssetDetail) {
|
|
|
|
|
return typeTokens(asset).has('area');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isYacimiento(asset: AssetDetail) {
|
|
|
|
|
return typeTokens(asset).has('yacimiento');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function lineageArea(lineage: AssetLineageItem[]) {
|
|
|
|
|
return [...lineage].reverse().find((item) => tokensForType(item.type).has('area')) ?? null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function relationRoleLabel(role: AreaCompanyRelation['relationRole']) {
|
|
|
|
|
if (role === 'OPERATOR') return 'Operadora';
|
|
|
|
|
if (role === 'TECHNICAL_OPERATOR') return 'Operadora técnica';
|
|
|
|
@@ -57,7 +71,7 @@ export function TerritorialInventoryPage({ initialAsset }: { initialAsset: Asset
|
|
|
|
|
const [params, setParams] = useSearchParams();
|
|
|
|
|
const requestedTab = params.get('tab') as TerritoryTab | null;
|
|
|
|
|
const tab: TerritoryTab = requestedTab === 'history' ? 'history' : 'summary';
|
|
|
|
|
const { hasPermission } = useAuth();
|
|
|
|
|
const { user, hasPermission } = useAuth();
|
|
|
|
|
const canEdit = hasPermission('assets.update');
|
|
|
|
|
const canCreate = hasPermission('assets.create');
|
|
|
|
|
const canReadHistory = hasPermission('assets.read_history');
|
|
|
|
@@ -65,10 +79,12 @@ export function TerritorialInventoryPage({ initialAsset }: { initialAsset: Asset
|
|
|
|
|
const canReadActs = hasPermission('inspection_acts.read');
|
|
|
|
|
const canReadReports = hasPermission('inspection_reports.read');
|
|
|
|
|
const canReadInspections = hasPermission('inspections.read');
|
|
|
|
|
const canAdvanced = hasAdministratorRole(user);
|
|
|
|
|
|
|
|
|
|
const [asset, setAsset] = useState(initialAsset);
|
|
|
|
|
const [children, setChildren] = useState<AssetListItem[]>([]);
|
|
|
|
|
const [childrenHasMore, setChildrenHasMore] = useState(false);
|
|
|
|
|
const [lineage, setLineage] = useState<AssetLineageItem[]>([]);
|
|
|
|
|
const [relations, setRelations] = useState<AreaCompanyRelation[]>([]);
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
const [error, setError] = useState('');
|
|
|
|
@@ -80,18 +96,31 @@ export function TerritorialInventoryPage({ initialAsset }: { initialAsset: Asset
|
|
|
|
|
const [metrics, setMetrics] = useState({ activeInspections: 0, acts: 0, reports: 0 });
|
|
|
|
|
|
|
|
|
|
const area = isOperationalArea(asset);
|
|
|
|
|
const yacimiento = isYacimiento(asset);
|
|
|
|
|
const activeRelations = useMemo(() => relations.filter((item) => item.active), [relations]);
|
|
|
|
|
const relationAreaContext = useMemo(() => {
|
|
|
|
|
if (area) return { id: asset.id, code: asset.code, name: asset.name };
|
|
|
|
|
if (asset.operationalArea) return asset.operationalArea;
|
|
|
|
|
const ancestor = lineageArea(lineage);
|
|
|
|
|
return ancestor ? { id: ancestor.id, code: ancestor.code, name: ancestor.name } : null;
|
|
|
|
|
}, [area, asset.id, asset.code, asset.name, asset.operationalArea, lineage]);
|
|
|
|
|
|
|
|
|
|
const loadSummary = async () => {
|
|
|
|
|
setLoading(true);
|
|
|
|
|
setError('');
|
|
|
|
|
try {
|
|
|
|
|
const loadedLineage = await getAssetLineage(asset.id);
|
|
|
|
|
const ancestorArea = lineageArea(loadedLineage);
|
|
|
|
|
const relationAreaId = area
|
|
|
|
|
? asset.id
|
|
|
|
|
: asset.operationalArea?.id ?? ancestorArea?.id ?? null;
|
|
|
|
|
const [childPage, loadedRelations] = await Promise.all([
|
|
|
|
|
listAssetTreeChildren({ parentId: asset.id, limit: 100 }),
|
|
|
|
|
area && canReadRelations
|
|
|
|
|
? listAreaCompanyRelations({ areaId: asset.id, includeHistory: true })
|
|
|
|
|
relationAreaId && canReadRelations
|
|
|
|
|
? listAreaCompanyRelations({ areaId: relationAreaId, includeHistory: true })
|
|
|
|
|
: Promise.resolve([]),
|
|
|
|
|
]);
|
|
|
|
|
setLineage(loadedLineage);
|
|
|
|
|
setChildren(childPage.data);
|
|
|
|
|
setChildrenHasMore(childPage.meta.hasMore);
|
|
|
|
|
setRelations(loadedRelations);
|
|
|
|
@@ -223,10 +252,10 @@ export function TerritorialInventoryPage({ initialAsset }: { initialAsset: Asset
|
|
|
|
|
{childrenHasMore && <div className="territory-card-actions"><Link className="button secondary" to={`/inventarios?section=territory&parentId=${asset.id}`}>Ver contenido completo</Link></div>}
|
|
|
|
|
</article>
|
|
|
|
|
|
|
|
|
|
{area && canReadRelations && <article className="panel territory-card">
|
|
|
|
|
<div className="territory-card-heading"><div><span className="eyebrow">OPERACIÓN</span><h2>Empresas vinculadas</h2><p>Quién opera o participa actualmente en esta área.</p></div><span className="count-pill">{activeRelations.length}</span></div>
|
|
|
|
|
{loading ? <LoadingBlock label="Cargando empresas…" /> : activeRelations.length === 0 ? <div className="inline-empty">No hay empresas con vínculo vigente.</div> : <div className="territory-company-list">
|
|
|
|
|
{activeRelations.map((relation) => <Link to={`/inventarios/${relation.company.id}`} key={relation.id} className="territory-company-row"><span><strong>{relation.company.name}</strong><small>{relationRoleLabel(relation.relationRole)}</small></span><Icon name="chevron" size={16} /></Link>)}
|
|
|
|
|
{relationAreaContext && canReadRelations && <article className="panel territory-card">
|
|
|
|
|
<div className="territory-card-heading"><div><span className="eyebrow">OPERACIÓN</span><h2>Empresas vinculadas</h2><p>{area ? 'Quién opera o participa actualmente en esta área.' : yacimiento ? `Empresas que operan o participan en el Área ${relationAreaContext.name}, contexto operativo de este yacimiento.` : `Empresas vinculadas al Área ${relationAreaContext.name}.`}</p></div><span className="count-pill">{activeRelations.length}</span></div>
|
|
|
|
|
{loading ? <LoadingBlock label="Cargando empresas…" /> : activeRelations.length === 0 ? <div className="inline-empty">No hay empresas con vínculo vigente en el Área {relationAreaContext.name}.</div> : <div className="territory-company-list">
|
|
|
|
|
{activeRelations.map((relation) => <Link to={`/inventarios/${relation.company.id}`} key={relation.id} className="territory-company-row"><span><strong>{relation.company.name}</strong><small>{relationRoleLabel(relation.relationRole)}{!area ? ` · ${relation.area.name}` : ''}</small></span><Icon name="chevron" size={16} /></Link>)}
|
|
|
|
|
</div>}
|
|
|
|
|
</article>}
|
|
|
|
|
|
|
|
|
@@ -239,7 +268,7 @@ export function TerritorialInventoryPage({ initialAsset }: { initialAsset: Asset
|
|
|
|
|
</div>
|
|
|
|
|
</article>}
|
|
|
|
|
|
|
|
|
|
<div className="territory-advanced-link"><span>Los datos estructurales, procedencia, ubicación y opciones técnicas quedan fuera de la vista cotidiana.</span><Link to={`/inventarios/${asset.id}?advanced=1`}>Administración avanzada</Link></div>
|
|
|
|
|
{canAdvanced && <div className="territory-advanced-link"><span>Los datos estructurales, procedencia, ubicación y opciones técnicas quedan fuera de la vista cotidiana.</span><Link to={`/inventarios/${asset.id}?advanced=1`}>Administración avanzada</Link></div>}
|
|
|
|
|
</div>}
|
|
|
|
|
|
|
|
|
|
{tab === 'history' && canReadHistory && <AssetHistoryPanel assetId={asset.id} />}
|
|
|
|
|