chore: import DH V2 D5.6.4 production baseline
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Link, useNavigate, useParams } from 'react-router';
|
||||
import { useAuth } from '../auth/AuthContext';
|
||||
import { Alert, LoadingBlock, errorMessage } from '../components/Feedback';
|
||||
import { Icon } from '../components/Icon';
|
||||
import { FindingOfficeWorkspace } from '../features/inspections/FindingOfficeWorkspace';
|
||||
import { InspectionEvidencePanel } from '../features/inspections/InspectionEvidencePanel';
|
||||
import { getInspectionFinding } from '../lib/api';
|
||||
import type { InspectionFinding } from '../lib/api';
|
||||
import { formatDate, formatDateOnly } from '../lib/format';
|
||||
|
||||
function todayInput(): string {
|
||||
const now = new Date();
|
||||
const local = new Date(now.getTime() - now.getTimezoneOffset() * 60_000);
|
||||
return local.toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
export function FindingDetailPage() {
|
||||
const { id } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const { hasPermission } = useAuth();
|
||||
const [finding, setFinding] = useState<InspectionFinding | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
const [evidenceRefreshKey, setEvidenceRefreshKey] = useState(0);
|
||||
|
||||
const canFollowUp = hasPermission('inspection_findings.follow_up');
|
||||
const canClose = hasPermission('inspection_findings.close');
|
||||
const canReadEvidence = hasPermission('inspection_evidence.read');
|
||||
const canCreateEvidence = hasPermission('inspection_evidence.create');
|
||||
const canReadCommunications = hasPermission('inspection_communications.read');
|
||||
const canCreateCommunications = hasPermission('inspection_communications.create');
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
setLoading(true);
|
||||
setError('');
|
||||
getInspectionFinding(id)
|
||||
.then(setFinding)
|
||||
.catch((requestError) => setError(errorMessage(requestError)))
|
||||
.finally(() => setLoading(false));
|
||||
}, [id]);
|
||||
|
||||
if (loading) return <LoadingBlock label="Cargando hallazgo…" />;
|
||||
if (!finding) return <section><Alert>{error || 'Hallazgo no encontrado'}</Alert></section>;
|
||||
|
||||
const responseOverdue = finding.status === 'OPEN'
|
||||
&& !finding.companyResponseReceivedOn
|
||||
&& Boolean(finding.correctionDueOn && finding.correctionDueOn < todayInput());
|
||||
const verificationOverdue = finding.status === 'OPEN'
|
||||
&& Boolean(finding.companyResponseReceivedOn && finding.nextControlOn && finding.nextControlOn < todayInput());
|
||||
const latestVerification = finding.latestVerification;
|
||||
const verificationReadyToClose = latestVerification?.outcome === 'RESOLVED'
|
||||
&& latestVerification.visitStatus === 'CLOSED';
|
||||
const verificationOutcomeLabel = latestVerification?.outcome === 'RESOLVED'
|
||||
? 'Solucionado'
|
||||
: latestVerification?.outcome === 'NOT_RESOLVED'
|
||||
? 'No solucionado'
|
||||
: latestVerification?.outcome === 'REQUIRES_NEW_DATE'
|
||||
? 'Requiere nueva fecha'
|
||||
: 'Sin resultado';
|
||||
|
||||
return <section>
|
||||
<div className="finding-detail-breadcrumb"><button type="button" className="text-link" onClick={() => navigate('/hallazgos')}>Hallazgos</button><span>›</span><strong>{finding.code}</strong></div>
|
||||
<div className="page-heading finding-detail-heading">
|
||||
<div><span className="eyebrow">{finding.code}</span><h1>{finding.title}</h1><p>{finding.asset.operatorCompany?.name ?? 'Empresa sin asignar'} · {finding.asset.operationalArea?.name ?? 'Área sin asignar'}</p></div>
|
||||
<span className={`status-badge large ${finding.status === 'OPEN' ? 'observed' : 'active'}`}>{finding.status === 'OPEN' ? 'Abierto' : 'Cerrado'}</span>
|
||||
</div>
|
||||
|
||||
{error && <Alert>{error}</Alert>}
|
||||
|
||||
<div className="finding-detail-summary-grid">
|
||||
<article><small>Elemento del inventario</small><Link to={`/inventarios/${finding.assetId}`}>{finding.asset.name}</Link><span>{finding.asset.code} · {finding.asset.typeName}</span></article>
|
||||
<article><small>Acta / inspección</small><Link to={`/inspecciones/actas/${finding.actId}`}>{finding.document.actCode}</Link><span><Link className="text-link" to={`/inspecciones/${finding.document.visitId}`}>{finding.document.visitCode}</Link></span></article>
|
||||
<article className={responseOverdue ? 'overdue' : ''}><small>Vencimiento administrativo</small><strong>{formatDateOnly(finding.correctionDueOn)}</strong><span>{finding.companyResponseReceivedOn ? `Respondido ${formatDateOnly(finding.companyResponseReceivedOn)}` : 'Esperando respuesta de empresa'}</span></article>
|
||||
<article className={verificationOverdue ? 'overdue' : ''}><small>Fecha de verificación</small><strong>{formatDateOnly(finding.nextControlOn)}</strong><span>{finding.nextControlOn ? 'Planificación de inspección o control' : 'Todavía sin definir'}</span></article>
|
||||
<article><small>Gravedad</small><strong>{finding.severity ? `${finding.severity}/10` : 'Sin calificar'}</strong><span>{finding.suggestedSeverity ? `Sugerida por catálogo: ${finding.suggestedSeverity}/10` : finding.catalogItemId ? 'El catálogo no define una sugerencia' : 'Hallazgo OTROS'}</span></article>
|
||||
</div>
|
||||
|
||||
{finding.verificationVisit && <article className="panel verification-linked-visit">
|
||||
<div className="panel-heading"><div><span className="eyebrow">VERIFICACIÓN PLANIFICADA</span><h2>{finding.verificationVisit.code}</h2><p className="section-copy">Este hallazgo ya está incorporado a una visita de verificación.</p></div><Link className="button secondary" to={`/inspecciones/${finding.verificationVisit.id}`}>Abrir visita <Icon name="chevron" /></Link></div>
|
||||
<div className="finding-two-deadlines"><div><span>FECHA OBJETIVO</span><strong>{formatDateOnly(finding.nextControlOn)}</strong><p>Fecha definida en el seguimiento del hallazgo.</p></div><div><span>VISITA</span><strong>{formatDate(finding.verificationVisit.plannedStartAt)}</strong><p>{finding.verificationVisit.code}</p></div></div>
|
||||
</article>}
|
||||
|
||||
{latestVerification?.outcome && <article className={`panel verification-result-panel ${latestVerification.outcome === 'RESOLVED' ? 'resolved' : ''}`}>
|
||||
<div className="panel-heading"><div><span className="eyebrow">ÚLTIMA VERIFICACIÓN DE CAMPO</span><h2>{verificationOutcomeLabel}</h2><p className="section-copy">Resultado registrado por el inspector durante {latestVerification.visitCode}.</p></div><Link className="button secondary" to={`/inspecciones/${latestVerification.visitId}`}>Abrir verificación <Icon name="chevron" /></Link></div>
|
||||
<div className="finding-two-deadlines">
|
||||
<div><span>RESULTADO</span><strong>{verificationOutcomeLabel}</strong><p>{latestVerification.resultNotes || 'Sin observaciones adicionales.'}</p></div>
|
||||
<div><span>VERIFICADO</span><strong>{formatDate(latestVerification.verifiedAt)}</strong><p>{latestVerification.evidenceCount} foto{latestVerification.evidenceCount === 1 ? '' : 's'} de verificación.</p></div>
|
||||
{latestVerification.rescheduledControlOn && <div><span>NUEVA FECHA</span><strong>{formatDateOnly(latestVerification.rescheduledControlOn)}</strong><p>El hallazgo vuelve a la planificación operativa.</p></div>}
|
||||
</div>
|
||||
{verificationReadyToClose && finding.status === 'OPEN' && <Alert type="success">La verificación de campo indicó que el hallazgo está solucionado y quedó listo para revisión de oficina.</Alert>}
|
||||
{latestVerification.outcome === 'NOT_RESOLVED' && finding.status === 'OPEN' && <Alert>La verificación indicó que el hallazgo continúa abierto. Debe definirse una nueva fecha de control.</Alert>}
|
||||
</article>}
|
||||
|
||||
<FindingOfficeWorkspace
|
||||
finding={finding}
|
||||
canFollowUp={canFollowUp}
|
||||
canClose={canClose}
|
||||
canCreateEvidence={canCreateEvidence}
|
||||
canCreateCommunications={canCreateCommunications}
|
||||
onFindingChanged={setFinding}
|
||||
onEvidenceChanged={() => setEvidenceRefreshKey((value) => value + 1)}
|
||||
/>
|
||||
|
||||
<div id="documentos-hallazgo">
|
||||
<InspectionEvidencePanel
|
||||
finding={finding}
|
||||
canReadEvidence={canReadEvidence}
|
||||
canCreateEvidence={canCreateEvidence}
|
||||
canCreateFieldEvidence={false}
|
||||
canReadCommunications={canReadCommunications}
|
||||
canCreateCommunications={canCreateCommunications}
|
||||
refreshKey={evidenceRefreshKey}
|
||||
managedCompanyResponse
|
||||
/>
|
||||
</div>
|
||||
|
||||
{finding.status === 'CLOSED' && <article className="closed-seal finding-closed-seal"><Icon name="check" /><div><span className="eyebrow">HALLAZGO CERRADO</span><strong>{formatDate(finding.closedAt)}</strong><p>{finding.closureNotes}</p></div></article>}
|
||||
</section>;
|
||||
}
|
||||
Reference in New Issue
Block a user