F1.4: integrar pendientes en la planificación de inspección
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import { Alert, EmptyState, LoadingBlock, errorMessage } from '../../components/Feedback';
|
||||
import { Icon } from '../../components/Icon';
|
||||
import type { InspectionVisit } from '../../lib/api';
|
||||
import { formatDate, formatDateOnly } from '../../lib/format';
|
||||
import {
|
||||
getInspectionFieldBriefing,
|
||||
type FieldBriefing,
|
||||
type FieldBriefingActState,
|
||||
} from './fieldBriefingApi';
|
||||
|
||||
function stateLabel(state: FieldBriefingActState): string {
|
||||
if (state === 'ACT_RESPONSE_OVERDUE') return 'Respuesta del Acta vencida';
|
||||
if (state === 'ACT_RESPONSE_DUE_SOON') return 'Respuesta próxima a vencer';
|
||||
if (state === 'COMPANY_COMMITMENT_OVERDUE') return 'Compromiso de empresa vencido';
|
||||
if (state === 'VERIFICATION_PENDING') return 'Verificación pendiente';
|
||||
if (state === 'RESPONSE_RECEIVED') return 'Respuesta recibida';
|
||||
return 'Esperando respuesta';
|
||||
}
|
||||
|
||||
function stateClass(state: FieldBriefingActState): string {
|
||||
if (state === 'ACT_RESPONSE_OVERDUE' || state === 'COMPANY_COMMITMENT_OVERDUE') return 'danger';
|
||||
if (state === 'ACT_RESPONSE_DUE_SOON' || state === 'VERIFICATION_PENDING') return 'warning';
|
||||
return 'pending';
|
||||
}
|
||||
|
||||
export function FieldBriefingPanel({ visit }: { visit: InspectionVisit }) {
|
||||
const [briefing, setBriefing] = useState<FieldBriefing | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
setLoading(true);
|
||||
setError('');
|
||||
getInspectionFieldBriefing(visit.id)
|
||||
.then((value) => { if (active) setBriefing(value); })
|
||||
.catch((requestError) => { if (active) setError(errorMessage(requestError)); })
|
||||
.finally(() => { if (active) setLoading(false); });
|
||||
return () => { active = false; };
|
||||
}, [visit.id, visit.updatedAt]);
|
||||
|
||||
return <section className="panel inspection-checklist-panel">
|
||||
<div className="panel-heading">
|
||||
<div>
|
||||
<span className="eyebrow">ANTES DE CONFIRMAR LA SALIDA</span>
|
||||
<h2>Pendientes para revisar en campo</h2>
|
||||
<p className="section-copy">Actas anteriores del mismo Área y Operadora que requieren atención durante esta inspección. El plazo administrativo pertenece al Acta y los hallazgos se mantienen dentro de su expediente de origen.</p>
|
||||
</div>
|
||||
<div className="form-actions">
|
||||
<Link className="button secondary" to="/preparacion-campo"><Icon name="clipboard" />Vista imprimible</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <Alert>{error}</Alert>}
|
||||
{loading ? <LoadingBlock label="Preparando pendientes de campo…" /> : briefing && <>
|
||||
<div className="inspection-checklist-metrics">
|
||||
<div><strong>{briefing.summary.acts}</strong><span>actas con pendientes</span></div>
|
||||
<div><strong>{briefing.summary.findings}</strong><span>hallazgos a revisar</span></div>
|
||||
<div><strong>{briefing.summary.inventoryItems}</strong><span>registros involucrados</span></div>
|
||||
<div><strong>{briefing.summary.responseOverdue}</strong><span>respuestas vencidas</span></div>
|
||||
<div><strong>{briefing.summary.commitmentOverdue}</strong><span>compromisos vencidos</span></div>
|
||||
<div><strong>{briefing.summary.verificationPending}</strong><span>verificaciones pendientes</span></div>
|
||||
</div>
|
||||
|
||||
<div className="temporal-notice">
|
||||
<Icon name="clipboard" />
|
||||
<p><strong>Contexto de salida:</strong> {briefing.inspection.area?.name ?? 'Área sin definir'} · {briefing.inspection.operatorCompany?.name ?? 'Operadora sin definir'} · {formatDateOnly(briefing.plannedOn)}.</p>
|
||||
</div>
|
||||
|
||||
{briefing.acts.length === 0
|
||||
? <EmptyState title="Sin pendientes anteriores" text="No hay Actas anteriores con acciones de campo pendientes para este Área y Operadora." />
|
||||
: <div>{briefing.acts.map((act) => <div className="table-panel" key={act.actId}>
|
||||
<div className="table-summary">
|
||||
<div><strong>{act.actCode}</strong><span>{formatDate(act.occurredAt)} · {act.findings.length} hallazgo{act.findings.length === 1 ? '' : 's'}</span></div>
|
||||
<span className={`status-badge ${stateClass(act.adminState)}`}>{stateLabel(act.adminState)}</span>
|
||||
</div>
|
||||
<div className="table-scroll"><table>
|
||||
<thead><tr><th>Hallazgo</th><th>Inventario</th><th>Gravedad</th><th>Control</th><th /></tr></thead>
|
||||
<tbody>{act.findings.map((finding) => <tr key={finding.id}>
|
||||
<td><Link className="history-asset-link" to={`/hallazgos/${finding.id}`}><strong>{finding.title}</strong><small>{finding.code} · {finding.status}</small></Link></td>
|
||||
<td><Link className="history-asset-link" to={`/inventarios/${finding.asset.id}`}><strong>{finding.asset.name}</strong><small>{finding.asset.code} · {finding.asset.typeName}</small></Link></td>
|
||||
<td>{finding.severity ?? '—'}</td>
|
||||
<td>{finding.nextControlOn ? formatDateOnly(finding.nextControlOn) : 'A definir'}</td>
|
||||
<td className="action-cell"><Link className="button secondary compact" to={`/seguimiento-actas/${act.actId}`}>Ver Acta</Link></td>
|
||||
</tr>)}</tbody>
|
||||
</table></div>
|
||||
<div className="table-summary">
|
||||
<span>Plazo empresa: <strong>{act.responseDueOn ? formatDateOnly(act.responseDueOn) : 'sin definir'}</strong></span>
|
||||
<span>Respuesta: <strong>{act.responseReceivedOn ? formatDateOnly(act.responseReceivedOn) : 'pendiente'}</strong></span>
|
||||
<span>Compromiso: <strong>{act.committedCorrectionOn ? formatDateOnly(act.committedCorrectionOn) : '—'}</strong></span>
|
||||
</div>
|
||||
</div>)}</div>}
|
||||
</>}
|
||||
</section>;
|
||||
}
|
||||
Reference in New Issue
Block a user