fix(actas): keep sealed act content findings-only
This commit is contained in:
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "dhv2-web",
|
||||
"version": "0.23.0-6",
|
||||
"version": "0.23.0-7",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "dhv2-web",
|
||||
"version": "0.23.0-6",
|
||||
"version": "0.23.0-7",
|
||||
"dependencies": {
|
||||
"maplibre-gl": "6.4.1",
|
||||
"react": "^19.0.0",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dhv2-web",
|
||||
"version": "0.23.0-6",
|
||||
"version": "0.23.0-7",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
|
||||
@@ -18,7 +18,7 @@ const actSections: ProjectionSection[] = [
|
||||
},
|
||||
{
|
||||
title: 'Alcance y constatación',
|
||||
description: 'Objeto de la actuación, descripción de lo actuado, observaciones e Inventarios inspeccionados con su ruta Instalación / Subinstalación.',
|
||||
description: 'Objeto de la actuación, descripción de lo actuado y observaciones generales de la inspección.',
|
||||
},
|
||||
{
|
||||
title: 'Hallazgos',
|
||||
@@ -34,7 +34,7 @@ const actSections: ProjectionSection[] = [
|
||||
},
|
||||
{
|
||||
title: 'Anexos',
|
||||
description: 'Registro fotográfico y evidencias sólo si el formulario oficial exige incorporarlos al Acta; el modelo queda preparado para hacerlo sin alterar el dato fuente.',
|
||||
description: 'Sólo evidencia vinculada a Hallazgos del Acta. Las fotos de inventario y altas de campo sin Hallazgos quedan fuera del documento.',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Alert, EmptyState, LoadingBlock, errorMessage } from '../../components/Feedback';
|
||||
import {
|
||||
getAssetMediaBlob,
|
||||
getInspectionFindingEvidenceBlob,
|
||||
listInspectionActFieldMedia,
|
||||
listInspectionFindingEvidence,
|
||||
listInspectionFindings,
|
||||
} from '../../lib/api';
|
||||
import type { InspectionActFieldMedia, InspectionFinding, InspectionFindingEvidence } from '../../lib/api';
|
||||
import type { InspectionFinding, InspectionFindingEvidence } from '../../lib/api';
|
||||
import { formatDate } from '../../lib/format';
|
||||
|
||||
type FindingWithPhotos = { finding: InspectionFinding; photos: InspectionFindingEvidence[] };
|
||||
@@ -30,50 +28,44 @@ function Photo({ id, title, caption, load }: { id: string; title: string; captio
|
||||
</figure>;
|
||||
}
|
||||
|
||||
function Finding({ item, assetPhotos }: { item: FindingWithPhotos; assetPhotos: InspectionActFieldMedia[] }) {
|
||||
function Finding({ item }: { item: FindingWithPhotos }) {
|
||||
const { finding, photos } = item;
|
||||
return <article className="act-finding-record">
|
||||
<div className="inspection-finding-heading"><div><span className="eyebrow">{finding.code}</span><h3>{finding.title}</h3><p>{finding.asset.name} · {finding.asset.code}</p></div></div>
|
||||
<p className="inspection-finding-description">{finding.description}</p>
|
||||
{finding.legalBasis && <p><strong>Normativa:</strong> {finding.legalBasis}</p>}
|
||||
{finding.severity != null && <p>Gravedad {finding.severity}/10</p>}
|
||||
{(photos.length > 0 || assetPhotos.length > 0) && <div className="act-finding-photos">
|
||||
{photos.length > 0 && <div className="act-finding-photos">
|
||||
{photos.map((photo) => <Photo key={photo.id} id={photo.id} title={photo.title || finding.title} caption={`Hallazgo · ${formatDate(photo.capturedAt || photo.createdAt)}${photo.latitude != null && photo.longitude != null ? ` · GPS ${photo.latitude.toFixed(6)}, ${photo.longitude.toFixed(6)}` : ''}`} load={getInspectionFindingEvidenceBlob} />)}
|
||||
{assetPhotos.map((photo) => <Photo key={photo.id} id={photo.id} title={photo.title || finding.asset.name} caption={`Inventario · ${formatDate(photo.fieldCapturedAt || photo.capturedAt || photo.createdAt)}`} load={getAssetMediaBlob} />)}
|
||||
</div>}
|
||||
{photos.length === 0 && assetPhotos.length === 0 && <small className="muted">Sin fotografías vinculadas.</small>}
|
||||
{photos.length === 0 && <small className="muted">Sin fotografías vinculadas al hallazgo.</small>}
|
||||
</article>;
|
||||
}
|
||||
|
||||
export function InspectionActMediaPanel({ actId }: { actId: string }) {
|
||||
const [items, setItems] = useState<FindingWithPhotos[]>([]);
|
||||
const [assetPhotos, setAssetPhotos] = useState<InspectionActFieldMedia[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
setLoading(true); setError('');
|
||||
Promise.all([listInspectionFindings(actId), listInspectionActFieldMedia(actId).catch(() => [])])
|
||||
.then(async ([findings, media]) => {
|
||||
listInspectionFindings(actId)
|
||||
.then(async (findings) => {
|
||||
const records = await Promise.all(findings.map(async (finding) => ({
|
||||
finding, photos: (await listInspectionFindingEvidence(finding.id)).filter((evidence) => evidence.kind === 'PHOTO' && evidence.purpose === 'OBSERVATION'),
|
||||
finding,
|
||||
photos: (await listInspectionFindingEvidence(finding.id)).filter((evidence) => evidence.kind === 'PHOTO' && evidence.purpose === 'OBSERVATION'),
|
||||
})));
|
||||
if (!active) return;
|
||||
setItems(records);
|
||||
setAssetPhotos(media.filter((photo) => photo.kind === 'PHOTO'));
|
||||
if (active) setItems(records);
|
||||
})
|
||||
.catch((requestError) => active && setError(errorMessage(requestError)))
|
||||
.finally(() => active && setLoading(false));
|
||||
return () => { active = false; };
|
||||
}, [actId]);
|
||||
const findingAssetIds = new Set(items.map((item) => item.finding.asset.id));
|
||||
const otherPhotos = assetPhotos.filter((photo) => !findingAssetIds.has(photo.assetId));
|
||||
return <section className="panel act-media-panel">
|
||||
<div className="panel-heading"><div><h2>Hallazgos del Acta</h2><p className="section-copy">Cada hallazgo reúne su descripción y las fotos tomadas en campo.</p></div><span className="count-pill">{items.length}</span></div>
|
||||
<div className="panel-heading"><div><h2>Hallazgos del Acta</h2><p className="section-copy">El Acta muestra únicamente Hallazgos y la evidencia fotográfica vinculada a cada uno.</p></div><span className="count-pill">{items.length}</span></div>
|
||||
{error && <Alert>{error}</Alert>}
|
||||
{loading ? <LoadingBlock label="Cargando hallazgos y fotografías…" /> : items.length ?
|
||||
<div className="act-finding-list">{items.map((item) => <Finding key={item.finding.id} item={item} assetPhotos={assetPhotos.filter((photo) => photo.assetId === item.finding.asset.id)} />)}</div> :
|
||||
<div className="act-finding-list">{items.map((item) => <Finding key={item.finding.id} item={item} />)}</div> :
|
||||
<EmptyState title="Sin hallazgos" text="Esta Acta no contiene hallazgos sincronizados." />}
|
||||
{!loading && otherPhotos.length > 0 && <div className="act-other-asset-photos"><h3>Fotos de otras instalaciones</h3><div className="act-finding-photos">{otherPhotos.map((photo) => <Photo key={photo.id} id={photo.id} title={photo.title || 'Instalación inspeccionada'} caption={`${photo.title || 'Instalación'} · ${formatDate(photo.fieldCapturedAt || photo.capturedAt || photo.createdAt)}`} load={getAssetMediaBlob} />)}</div></div>}
|
||||
</section>;
|
||||
}
|
||||
|
||||
@@ -13,13 +13,6 @@ interface PublicFinding {
|
||||
recurrenceOfFindingId: string | null;
|
||||
}
|
||||
|
||||
interface PublicInventory {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
typeName: string | null;
|
||||
}
|
||||
|
||||
interface PublicSignatureView {
|
||||
invitation: {
|
||||
id: string;
|
||||
@@ -41,7 +34,6 @@ interface PublicSignatureView {
|
||||
documentNumber: string | null;
|
||||
position: string | null;
|
||||
};
|
||||
inventories: PublicInventory[];
|
||||
findings: PublicFinding[];
|
||||
consent: string;
|
||||
allowedActions: string[];
|
||||
@@ -351,12 +343,6 @@ export function CompanySignaturePage() {
|
||||
<h2>Contenido del Acta</h2>
|
||||
{view.act.summary && <><strong>Resumen</strong><p>{view.act.summary}</p></>}
|
||||
{view.act.observations && <><strong>Observaciones</strong><p>{view.act.observations}</p></>}
|
||||
<h3>Inventario inspeccionado</h3>
|
||||
{view.inventories.length === 0 ? <p>Sin Inventario detallado.</p> : view.inventories.map((item) => (
|
||||
<div key={item.id} style={{ padding: '9px 0', borderBottom: '1px solid #e3e9ed' }}>
|
||||
<strong>{item.code} · {item.name}</strong>{item.typeName && <div style={{ color: '#5c707b' }}>{item.typeName}</div>}
|
||||
</div>
|
||||
))}
|
||||
<h3 style={{ marginTop: 24 }}>Hallazgos</h3>
|
||||
{view.findings.length === 0 ? <p>El Acta no contiene Hallazgos.</p> : view.findings.map((finding) => (
|
||||
<article key={finding.id} style={{ padding: 14, marginBottom: 10, border: '1px solid #d8e1e7', borderRadius: 10 }}>
|
||||
|
||||
@@ -93,7 +93,6 @@ export function InspectionActEditorPage() {
|
||||
</div>
|
||||
{meaningfulSummary && <div><h2>Lo actuado</h2><p>{meaningfulSummary}</p></div>}
|
||||
{act.observations && <div><h3>Observaciones</h3><p>{act.observations}</p></div>}
|
||||
{act.assets.length > 0 && <div><h3>Instalaciones inspeccionadas</h3><p>{act.assets.map((asset) => `${asset.name} (${asset.code})`).join(' · ')}</p></div>}
|
||||
</section>}
|
||||
|
||||
{act && <InspectionActMediaPanel actId={act.id} />}
|
||||
|
||||
Reference in New Issue
Block a user