import { useEffect, useState } from 'react'; import type { FormEvent } from 'react'; import { Link } from 'react-router'; import { Alert, EmptyState, LoadingBlock, errorMessage } from '../components/Feedback'; import { Icon } from '../components/Icon'; import { AssetCenterTabs } from '../features/assets/AssetCenterTabs'; import { approveFieldDiscovery, listAssets, listFieldDiscoveries, matchFieldDiscovery, rejectFieldDiscovery, } from '../lib/api'; import type { AssetListItem, FieldDiscovery, FieldDiscoveryStatus, PageMeta } from '../lib/api'; import { formatDate } from '../lib/format'; const statuses: Array<{ value: FieldDiscoveryStatus | ''; label: string }> = [ { value: 'PENDING', label: 'Pendientes' }, { value: 'APPROVED', label: 'Aprobadas' }, { value: 'MATCHED', label: 'Conciliadas' }, { value: 'REJECTED', label: 'Rechazadas' }, { value: '', label: 'Todas' }, ]; function statusLabel(status: FieldDiscoveryStatus) { if (status === 'APPROVED') return 'Aprobada'; if (status === 'MATCHED') return 'Conciliada'; if (status === 'REJECTED') return 'Rechazada'; return 'Pendiente'; } export function FieldDiscoveriesPage() { const [items, setItems] = useState([]); const [meta, setMeta] = useState({ page: 1, pageSize: 25, total: 0, totalPages: 0 }); const [status, setStatus] = useState('PENDING'); const [search, setSearch] = useState(''); const [draftSearch, setDraftSearch] = useState(''); const [page, setPage] = useState(1); const [loading, setLoading] = useState(true); const [busyId, setBusyId] = useState(''); const [error, setError] = useState(''); const [matchFor, setMatchFor] = useState(null); const [matchSearch, setMatchSearch] = useState(''); const [candidates, setCandidates] = useState([]); const [matchLoading, setMatchLoading] = useState(false); const load = () => { setLoading(true); setError(''); listFieldDiscoveries({ page, pageSize: 25, status: status || undefined, search }) .then((response) => { setItems(response.data); setMeta(response.meta); }) .catch((requestError) => setError(errorMessage(requestError))) .finally(() => setLoading(false)); }; useEffect(load, [page, status, search]); const applySearch = (event: FormEvent) => { event.preventDefault(); setPage(1); setSearch(draftSearch.trim()); }; const approve = async (item: FieldDiscovery) => { if (!window.confirm(`¿Validar ${item.asset.name} e incorporarlo al Inventario?`)) return; setBusyId(item.id); setError(''); try { await approveFieldDiscovery(item.id); load(); } catch (requestError) { setError(errorMessage(requestError)); } finally { setBusyId(''); } }; const reject = async (item: FieldDiscovery) => { const reason = window.prompt('Motivo del rechazo (queda registrado en el historial):', ''); if (!reason || reason.trim().length < 5) return; setBusyId(item.id); setError(''); try { await rejectFieldDiscovery(item.id, reason.trim()); load(); } catch (requestError) { setError(errorMessage(requestError)); } finally { setBusyId(''); } }; const searchMatches = async (event: FormEvent) => { event.preventDefault(); if (!matchFor || matchSearch.trim().length < 2) return; setMatchLoading(true); setError(''); try { const response = await listAssets({ page: 1, pageSize: 12, search: matchSearch.trim(), operationalAreaId: matchFor.asset.operationalAreaId ?? undefined, }); setCandidates(response.data.filter((candidate) => candidate.id !== matchFor.asset.id && candidate.informationStatus !== 'INACTIVE')); } catch (requestError) { setError(errorMessage(requestError)); } finally { setMatchLoading(false); } }; const chooseMatch = async (candidate: AssetListItem) => { if (!matchFor) return; const reason = window.prompt(`Confirmá por qué ${matchFor.asset.name} corresponde a ${candidate.name}:`, 'Coincidencia verificada en oficina'); if (!reason || reason.trim().length < 5) return; setBusyId(matchFor.id); setError(''); try { await matchFieldDiscovery(matchFor.id, candidate.id, reason.trim()); setMatchFor(null); setMatchSearch(''); setCandidates([]); load(); } catch (requestError) { setError(errorMessage(requestError)); } finally { setBusyId(''); } }; return
INVENTARIOS

Altas encontradas en campo

Revisá elementos creados por inspectores durante una visita antes de incorporarlos definitivamente al Inventario.

setDraftSearch(event.target.value)} placeholder="Buscar nombre, código, inspección o inspector…" />
{statuses.map((option) => )}
{error && {error}} Una alta de campo queda en borrador. Podés corregir su ficha, aprobarla, conciliarla con un registro existente o rechazarla. Las Actas y Hallazgos que ya la referencien no se reescriben. {loading ? : items.length === 0 ? :
{meta.total} alta{meta.total === 1 ? '' : 's'}Página {meta.page} de {Math.max(meta.totalPages, 1)}
{items.map((item) => )}
ElementoInspecciónInspectorEstadoAcciones
{item.asset.name}{item.asset.code} · {item.asset.typeName}{item.asset.commonName ? ` · ${item.asset.commonName}` : ''} {item.visit.code}{formatDate(item.observedAt)} {item.creator.firstName} {item.creator.lastName}{item.creator.username} {statusLabel(item.status)}{item.matchedAsset && → {item.matchedAsset.name}} Corregir ficha {item.status === 'PENDING' && <> }
{meta.total ? `${(page - 1) * meta.pageSize + 1}–${Math.min(page * meta.pageSize, meta.total)} de ${meta.total}` : '0 resultados'}
} {matchFor &&
CONCILIAR

Buscar registro existente

Buscá dentro de la misma Área física. El alta de campo se archivará, pero su historial no se elimina.

setMatchSearch(event.target.value)} placeholder="Nombre, sobrenombre o código existente…" /> {candidates.length > 0 &&
{candidates.map((candidate) => )}
Registro existenteTipo
{candidate.name}{candidate.code}{candidate.commonName ? ` · ${candidate.commonName}` : ''}{candidate.type.name}
}
}
; }