chore: import DH V2 D5.6.4 production baseline
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useAuth } from '../../auth/AuthContext';
|
||||
import { Alert, LoadingBlock, errorMessage } from '../../components/Feedback';
|
||||
import { Icon } from '../../components/Icon';
|
||||
import { getInspectionClosure, getInspectionSignatureBlob } from '../../lib/api';
|
||||
import type { InspectionAct, InspectionActSignature, InspectionClosure } from '../../lib/api';
|
||||
import { formatDate } from '../../lib/format';
|
||||
|
||||
function signatureStatusLabel(value: InspectionActSignature['status']): string {
|
||||
if (value === 'SIGNED') return 'Firmada';
|
||||
if (value === 'REFUSED') return 'Se negó a firmar';
|
||||
return 'Ausente';
|
||||
}
|
||||
|
||||
function signerTypeLabel(value: InspectionActSignature['signerType']): string {
|
||||
return value === 'INSPECTOR' ? 'Inspector/a' : 'Responsable de la empresa';
|
||||
}
|
||||
|
||||
export function InspectionClosurePanel({ act }: { act: InspectionAct }) {
|
||||
const { hasPermission } = useAuth();
|
||||
const canRead = hasPermission('inspection_closure.read');
|
||||
const [closure, setClosure] = useState<InspectionClosure | null>(null);
|
||||
const [loading, setLoading] = useState(canRead);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (!canRead) return;
|
||||
setLoading(true);
|
||||
getInspectionClosure(act.id)
|
||||
.then(setClosure)
|
||||
.catch((requestError) => setError(errorMessage(requestError)))
|
||||
.finally(() => setLoading(false));
|
||||
}, [act.id, canRead]);
|
||||
|
||||
const inspectorSignatures = useMemo(
|
||||
() => closure?.signatures.filter((item) => item.signerType === 'INSPECTOR') ?? [],
|
||||
[closure],
|
||||
);
|
||||
const companyOutcome = useMemo(
|
||||
() => closure?.signatures.find((item) => item.signerType === 'COMPANY_RESPONSIBLE') ?? null,
|
||||
[closure],
|
||||
);
|
||||
|
||||
const viewSignature = async (signature: InspectionActSignature) => {
|
||||
const tab = window.open('about:blank', '_blank');
|
||||
if (tab) tab.opener = null;
|
||||
try {
|
||||
const blob = await getInspectionSignatureBlob(signature.id);
|
||||
const url = URL.createObjectURL(blob);
|
||||
if (tab) tab.location.href = url;
|
||||
else window.open(url, '_blank', 'noopener,noreferrer');
|
||||
window.setTimeout(() => URL.revokeObjectURL(url), 60_000);
|
||||
} catch (requestError) {
|
||||
tab?.close();
|
||||
setError(errorMessage(requestError));
|
||||
}
|
||||
};
|
||||
|
||||
if (!canRead) return null;
|
||||
if (loading || !closure) return <section className="panel inspection-closure-panel"><LoadingBlock label="Cargando cierre del acta…" /></section>;
|
||||
|
||||
const constanciasCompletas = inspectorSignatures.length > 0 && Boolean(companyOutcome);
|
||||
|
||||
return <section className="panel inspection-closure-panel">
|
||||
<div className="panel-heading"><div><span className="eyebrow">CIERRE DE LA INSPECCIÓN · SÓLO LECTURA</span><h2>Responsable, firmas y sellado</h2><p className="section-copy">La preparación, las firmas y el cierre son operaciones exclusivas de la APK para inspectores.</p></div><span className={`status-badge large ${act.status === 'CLOSED' ? 'active' : act.status === 'READY' ? 'observed' : 'pending'}`}>{act.status === 'CLOSED' ? 'Cierre sellado' : act.status === 'READY' ? 'Esperando firmas' : 'Pendiente en APK'}</span></div>
|
||||
|
||||
{error && <Alert>{error}</Alert>}
|
||||
<div className="temporal-notice"><Icon name="clipboard" /><p><strong>Operación exclusiva en APK.</strong> El dashboard muestra las constancias sincronizadas, pero no permite identificar al responsable, firmar, reabrir ni cerrar el acta.</p></div>
|
||||
|
||||
<div className="closure-step-grid">
|
||||
<div className={closure.responsible ? 'complete' : ''}><span>1</span><strong>Responsable</strong><small>{closure.responsible ? 'Registrado' : 'Pendiente'}</small></div>
|
||||
<div className={closure.closure?.isCurrent ? 'complete' : ''}><span>2</span><strong>Congelado</strong><small>{closure.closure?.isCurrent ? 'SHA-256 listo' : 'Pendiente'}</small></div>
|
||||
<div className={constanciasCompletas ? 'complete' : ''}><span>3</span><strong>Constancias</strong><small>{inspectorSignatures.length} inspector · {companyOutcome ? 'empresa OK' : 'empresa pendiente'}</small></div>
|
||||
<div className={act.status === 'CLOSED' ? 'complete' : ''}><span>4</span><strong>Cierre</strong><small>{act.status === 'CLOSED' ? 'Sellado' : 'Pendiente'}</small></div>
|
||||
</div>
|
||||
|
||||
{closure.responsible && <div className="responsible-summary"><div><small>Situación</small><strong>{closure.responsible.attendanceStatus === 'PRESENT' ? 'Presente' : 'Ausente'}</strong></div><div><small>Responsable</small><strong>{closure.responsible.fullName ?? 'No estuvo presente'}</strong></div><div><small>Documento / cargo</small><strong>{closure.responsible.documentNumber ? `${closure.responsible.documentType} ${closure.responsible.documentNumber}` : 'No informado'}{closure.responsible.position ? ` · ${closure.responsible.position}` : ''}</strong></div><div><small>Contacto</small><strong>{closure.responsible.email ?? closure.responsible.phone ?? 'No informado'}</strong></div></div>}
|
||||
|
||||
{closure.closure?.isCurrent && <div className="closure-hash-card"><div><span className="eyebrow">CONTENIDO CONGELADO</span><strong>{closure.closure.schemaVersion}</strong><small>Preparado {formatDate(closure.closure.preparedAt)}</small></div><code>{closure.closure.preparedSha256}</code></div>}
|
||||
|
||||
{closure.signatures.length > 0 && <div className="signature-records">{closure.signatures.map((signature) => <article key={signature.id}><div><span className={`status-badge ${signature.status === 'SIGNED' ? 'active' : 'observed'}`}>{signatureStatusLabel(signature.status)}</span><strong>{signature.signerName}</strong><small>{signerTypeLabel(signature.signerType)} · {formatDate(signature.createdAt)}</small></div><code title={signature.signaturePayloadSha256}>{signature.signaturePayloadSha256}</code>{signature.status === 'SIGNED' ? <button type="button" className="button secondary" onClick={() => viewSignature(signature)}>Ver firma</button> : <p>{signature.reason}</p>}</article>)}</div>}
|
||||
|
||||
{act.status === 'CLOSED' && closure.closure?.finalSha256 && <div className="closed-seal"><Icon name="check" /><div><span className="eyebrow">ACTA Y VISITA CERRADAS</span><strong>{formatDate(closure.closure.serverClosedAt)}</strong><p>Los hallazgos continúan abiertos para la respuesta de la empresa y el próximo control.</p><code>{closure.closure.finalSha256}</code></div></div>}
|
||||
</section>;
|
||||
}
|
||||
Reference in New Issue
Block a user