import { useEffect, useMemo, useState } from 'react'; import type { FormEvent } from 'react'; import { Link, useSearchParams } from 'react-router'; import { useAuth } from '../auth/AuthContext'; import { Alert, EmptyState, LoadingBlock, errorMessage } from '../components/Feedback'; import { Icon } from '../components/Icon'; import { SearchableSelect } from '../components/SearchableSelect'; import { AssetHistoryPanel } from '../features/assets/AssetHistoryPanel'; import { AssetMediaPanel } from '../features/assets/AssetMediaPanel'; import { assetStatusClass, assetStatusLabel } from '../features/assets/assetPresentation'; import { createAreaCompanyRelation, endAreaCompanyRelation, getAssetRegistry, listAreaCompanyRelations, listInspectionVisits, listOperationalAreas, updateAsset, upsertOrganizationProfile, } from '../lib/api'; import type { AreaCompanyRelation, AssetDetail, AssetRegistry, OperationalAssetSummary, OrganizationKind, } from '../lib/api'; import { listInspectionActsGlobalF4, type InspectionActListItemF4, type InspectionActStatusF4, } from '../lib/inspectionActF4Api'; import { listInspectionReportsF4, type InspectionReportDetailF4, } from '../lib/reportWorkflowApi'; import { formatDate } from '../lib/format'; import './companyInventory.css'; type CompanyTab = 'summary' | 'issued' | 'documents' | 'history'; const organizationLabels: Record = { COMPANY: 'Empresa', UTE: 'UTE', PUBLIC_ENTITY: 'Entidad pública', OTHER: 'Otra organización', }; function relationRoleLabel(role: AreaCompanyRelation['relationRole']) { if (role === 'OPERATOR') return 'Operadora'; if (role === 'TECHNICAL_OPERATOR') return 'Operadora técnica'; if (role === 'CONCESSIONAIRE') return 'Concesionaria / titular'; if (role === 'PERMIT_HOLDER') return 'Permisionaria'; if (role === 'PARTICIPANT') return 'Participante'; return 'Otro vínculo'; } function reportStatusLabel(report: InspectionReportDetailF4) { if (report.status === 'OFFICIALIZED') return report.gedoIfIdentifier ? `GEDO · ${report.gedoIfIdentifier}` : 'Oficializado en GEDO'; if (report.status === 'WORKING') return 'INF en preparación'; if (report.status === 'CANCELLED') return 'Cancelado'; return 'Informe histórico'; } function reportStatusClass(report: InspectionReportDetailF4) { if (report.status === 'OFFICIALIZED') return 'active'; if (report.status === 'CANCELLED') return 'danger'; return 'pending'; } function areaLabel(items: Array<{ name: string }>) { if (!items.length) return 'Área sin informar'; const first = items[0]; if (!first) return 'Área sin informar'; return items.length === 1 ? first.name : `${first.name} +${items.length - 1}`; } async function loadAllActs(companyId: string): Promise { const statuses: InspectionActStatusF4[] = ['SEALED', 'CLOSED', 'RECTIFIED']; const groups = await Promise.all(statuses.map(async (status) => { const collected: InspectionActListItemF4[] = []; let page = 1; let totalPages = 1; do { const response = await listInspectionActsGlobalF4({ companyId, status, page, pageSize: 100 }); collected.push(...response.data); totalPages = response.meta.totalPages; page += 1; } while (page <= totalPages); return collected; })); return groups.flat().sort((a, b) => new Date(b.occurredAt).getTime() - new Date(a.occurredAt).getTime()); } async function loadAllReports(companyId: string): Promise { const collected: InspectionReportDetailF4[] = []; let page = 1; let totalPages = 1; do { const response = await listInspectionReportsF4({ companyId, page, pageSize: 100 }); collected.push(...response.data); totalPages = response.meta.totalPages; page += 1; } while (page <= totalPages); return collected .filter((item) => item.status !== 'CANCELLED') .sort((a, b) => new Date(b.generatedAt).getTime() - new Date(a.generatedAt).getTime()); } export function CompanyInventoryPage({ initialAsset }: { initialAsset: AssetDetail }) { const [params, setParams] = useSearchParams(); const requestedTab = params.get('tab') as CompanyTab | null; const tab: CompanyTab = requestedTab && ['summary', 'issued', 'documents', 'history'].includes(requestedTab) ? requestedTab : 'summary'; const { hasPermission } = useAuth(); const canEditAsset = hasPermission('assets.update'); const canReadRegistry = hasPermission('asset_registry.read'); const canManageRegistry = hasPermission('asset_registry.manage'); const canReadRelations = hasPermission('asset_relations.read'); const canManageRelations = hasPermission('asset_relations.manage'); const canReadActs = hasPermission('inspection_acts.read'); const canReadReports = hasPermission('inspection_reports.read'); const canReadInspections = hasPermission('inspections.read'); const canReadMedia = hasPermission('assets.read_media'); const canManageMedia = hasPermission('assets.manage_media'); const canReadHistory = hasPermission('assets.read_history'); const [asset, setAsset] = useState(initialAsset); const [registry, setRegistry] = useState(null); const [relations, setRelations] = useState([]); const [areas, setAreas] = useState([]); const [loadingContext, setLoadingContext] = useState(true); const [contextError, setContextError] = useState(''); const [success, setSuccess] = useState(''); const [editing, setEditing] = useState(false); const [saving, setSaving] = useState(false); const [companyForm, setCompanyForm] = useState({ name: initialAsset.name, commonName: initialAsset.commonName ?? '', organizationKind: 'COMPANY' as OrganizationKind, legalName: initialAsset.name, taxId: '', notificationEmail: '', notes: '', }); const [relationForm, setRelationForm] = useState({ areaId: '', reason: '' }); const [metrics, setMetrics] = useState({ acts: 0, reports: 0, activeInspections: 0 }); const [acts, setActs] = useState([]); const [reports, setReports] = useState([]); const [documentsLoading, setDocumentsLoading] = useState(false); const [documentsError, setDocumentsError] = useState(''); const [documentSearch, setDocumentSearch] = useState(''); const activeRelations = useMemo(() => relations.filter((item) => item.active), [relations]); const loadContext = async () => { setLoadingContext(true); setContextError(''); try { const [loadedRegistry, loadedRelations, loadedAreas] = await Promise.all([ canReadRegistry ? getAssetRegistry(asset.id) : Promise.resolve(null), canReadRelations ? listAreaCompanyRelations({ companyId: asset.id, includeHistory: true }) : Promise.resolve([]), canManageRelations ? listOperationalAreas() : Promise.resolve([]), ]); setRegistry(loadedRegistry); setRelations(loadedRelations); setAreas(loadedAreas); if (loadedRegistry?.organizationProfile) { const profile = loadedRegistry.organizationProfile; setCompanyForm({ name: asset.name, commonName: asset.commonName ?? '', organizationKind: profile.organizationKind, legalName: profile.legalName ?? asset.name, taxId: profile.taxId ?? '', notificationEmail: profile.notificationEmail ?? '', notes: profile.notes ?? '', }); } } catch (requestError) { setContextError(errorMessage(requestError)); } finally { setLoadingContext(false); } }; useEffect(() => { void loadContext(); }, [asset.id]); useEffect(() => { let cancelled = false; const loadMetrics = async () => { try { const [sealed, closed, rectified, reportPage, planned, inProgress] = await Promise.all([ canReadActs ? listInspectionActsGlobalF4({ companyId: asset.id, status: 'SEALED', pageSize: 1 }) : Promise.resolve(null), canReadActs ? listInspectionActsGlobalF4({ companyId: asset.id, status: 'CLOSED', pageSize: 1 }) : Promise.resolve(null), canReadActs ? listInspectionActsGlobalF4({ companyId: asset.id, status: 'RECTIFIED', pageSize: 1 }) : Promise.resolve(null), canReadReports ? listInspectionReportsF4({ companyId: asset.id, pageSize: 1 }) : Promise.resolve(null), canReadInspections ? listInspectionVisits({ companyId: asset.id, status: 'PLANNED', pageSize: 1 }) : Promise.resolve(null), canReadInspections ? listInspectionVisits({ companyId: asset.id, status: 'IN_PROGRESS', pageSize: 1 }) : Promise.resolve(null), ]); if (cancelled) return; setMetrics({ acts: (sealed?.meta.total ?? 0) + (closed?.meta.total ?? 0) + (rectified?.meta.total ?? 0), reports: reportPage?.meta.total ?? 0, activeInspections: (planned?.meta.total ?? 0) + (inProgress?.meta.total ?? 0), }); } catch { if (!cancelled) setMetrics({ acts: 0, reports: 0, activeInspections: 0 }); } }; void loadMetrics(); return () => { cancelled = true; }; }, [asset.id, canReadActs, canReadReports, canReadInspections]); useEffect(() => { if (tab !== 'issued' || (!canReadActs && !canReadReports)) return; let cancelled = false; setDocumentsLoading(true); setDocumentsError(''); Promise.all([ canReadActs ? loadAllActs(asset.id) : Promise.resolve([]), canReadReports ? loadAllReports(asset.id) : Promise.resolve([]), ]) .then(([loadedActs, loadedReports]) => { if (cancelled) return; setActs(loadedActs); setReports(loadedReports); }) .catch((requestError) => { if (!cancelled) setDocumentsError(errorMessage(requestError)); }) .finally(() => { if (!cancelled) setDocumentsLoading(false); }); return () => { cancelled = true; }; }, [tab, asset.id, canReadActs, canReadReports]); const saveCompany = async (event: FormEvent) => { event.preventDefault(); setSaving(true); setContextError(''); setSuccess(''); try { let savedAsset = asset; if (canEditAsset) { savedAsset = await updateAsset(asset.id, { name: companyForm.name.trim(), commonName: companyForm.commonName.trim() || null, }); setAsset(savedAsset); } if (canManageRegistry) { await upsertOrganizationProfile(asset.id, { organizationKind: companyForm.organizationKind, legalName: companyForm.legalName.trim() || null, taxId: companyForm.taxId.trim() || null, notificationEmail: companyForm.notificationEmail.trim() || null, notes: companyForm.notes.trim() || null, }); } setEditing(false); setSuccess('Datos de la empresa actualizados.'); await loadContext(); } catch (requestError) { setContextError(errorMessage(requestError)); } finally { setSaving(false); } }; const addRelation = async (event: FormEvent) => { event.preventDefault(); if (!relationForm.areaId || relationForm.reason.trim().length < 3) return; setSaving(true); setContextError(''); setSuccess(''); try { await createAreaCompanyRelation({ areaId: relationForm.areaId, companyId: asset.id, reason: relationForm.reason.trim(), }); setRelationForm({ areaId: '', reason: '' }); setSuccess('Área vinculada a la empresa.'); await loadContext(); } catch (requestError) { setContextError(errorMessage(requestError)); } finally { setSaving(false); } }; const endRelation = async (relation: AreaCompanyRelation) => { const reason = window.prompt(`Motivo para finalizar el vínculo con ${relation.area.name}`); if (!reason?.trim() || reason.trim().length < 3) return; setSaving(true); setContextError(''); setSuccess(''); try { await endAreaCompanyRelation(relation.id, reason.trim()); setSuccess('Vínculo finalizado. El historial quedó conservado.'); await loadContext(); } catch (requestError) { setContextError(errorMessage(requestError)); } finally { setSaving(false); } }; const setTab = (next: CompanyTab) => { const nextParams = new URLSearchParams(params); next === 'summary' ? nextParams.delete('tab') : nextParams.set('tab', next); nextParams.delete('advanced'); setParams(nextParams); }; const reportByAct = useMemo(() => new Map(reports.map((report) => [report.actId, report])), [reports]); const filteredActs = useMemo(() => { const needle = documentSearch.trim().toLocaleLowerCase('es'); if (!needle) return acts; return acts.filter((act) => [ act.code, act.title, act.visit.code, ...act.areas.map((item) => item.name), reportByAct.get(act.id)?.code ?? '', reportByAct.get(act.id)?.gedoIfIdentifier ?? '', ].join(' ').toLocaleLowerCase('es').includes(needle)); }, [acts, documentSearch, reportByAct]); const usedAreaIds = new Set(activeRelations.map((relation) => relation.area.id)); const availableAreas = areas.filter((item) => !usedAreaIds.has(item.id)); const profile = registry?.organizationProfile ?? null; return
EMPRESA

{asset.name}

{asset.code}{profile?.taxId ? ` · CUIT ${profile.taxId}` : ''}

{assetStatusLabel(asset.informationStatus)} {(canEditAsset || canManageRegistry) && tab === 'summary' && }
{contextError && {contextError}} {success && {success}} {tab === 'summary' &&
{loadingContext ? : <>
IDENTIDAD

Datos de la empresa

La información que se usa para identificarla y enviar documentación oficial.

{editing ?