Jerarquía física definitiva, compatibilidades N↔N de clasificaciones, Hallazgos y campos técnicos por familia, navegación WEB/Android y APK F6 0.14.0-debug.
98 lines
4.8 KiB
TypeScript
98 lines
4.8 KiB
TypeScript
import { useEffect, useMemo, useState } from 'react';
|
|
import { Link } from 'react-router';
|
|
import { useAuth } from '../../auth/AuthContext';
|
|
import { Alert, LoadingBlock, errorMessage } from '../../components/Feedback';
|
|
import { Icon } from '../../components/Icon';
|
|
import {
|
|
getInventoryFamilyFindings,
|
|
getInventoryTechnicalValues,
|
|
} from '../../lib/inventoryStructureApi';
|
|
import type {
|
|
InventoryFamilyFinding,
|
|
InventoryTechnicalValues,
|
|
} from '../../lib/inventoryStructureApi';
|
|
import { AssetTechnicalDataPanel } from './AssetTechnicalDataPanel';
|
|
|
|
export function AssetFindingCatalogPanel({ assetId, canManage }: { assetId: string; canManage: boolean }) {
|
|
const { hasPermission } = useAuth();
|
|
const canEditTechnical = hasPermission('assets.update');
|
|
const [technical, setTechnical] = useState<InventoryTechnicalValues | null>(null);
|
|
const [items, setItems] = useState<InventoryFamilyFinding[]>([]);
|
|
const [search, setSearch] = useState('');
|
|
const [loading, setLoading] = useState(true);
|
|
const [unsupported, setUnsupported] = useState(false);
|
|
const [error, setError] = useState('');
|
|
|
|
useEffect(() => {
|
|
let active = true;
|
|
setLoading(true);
|
|
setUnsupported(false);
|
|
setError('');
|
|
getInventoryTechnicalValues(assetId)
|
|
.then(async (loaded) => {
|
|
const findings = await getInventoryFamilyFindings(loaded.family.id);
|
|
if (!active) return;
|
|
setTechnical(loaded);
|
|
setItems(findings.items);
|
|
})
|
|
.catch((requestError) => {
|
|
if (!active) return;
|
|
const message = errorMessage(requestError);
|
|
if (message.includes('no tiene clasificación técnica')) setUnsupported(true);
|
|
else setError(message);
|
|
})
|
|
.finally(() => active && setLoading(false));
|
|
return () => { active = false; };
|
|
}, [assetId]);
|
|
|
|
const visible = useMemo(() => {
|
|
const needle = search.trim().toLocaleLowerCase();
|
|
if (!needle) return items;
|
|
return items.filter((item) => [item.title, item.code, item.categoryName, item.legalBasis ?? '']
|
|
.some((value) => value.toLocaleLowerCase().includes(needle)));
|
|
}, [items, search]);
|
|
|
|
if (loading) return <div className="panel"><LoadingBlock label="Cargando clasificación y Hallazgos aplicables…" /></div>;
|
|
if (error) return <Alert>{error}</Alert>;
|
|
if (unsupported || !technical) return <section className="panel asset-finding-catalog-panel">
|
|
<div className="panel-heading"><div><span className="eyebrow">HALLAZGOS APLICABLES</span><h2>Sin clasificación técnica</h2><p className="section-copy">Departamento, Área y Yacimiento son niveles estructurales. Los Hallazgos se registran sobre Instalaciones y Subinstalaciones clasificadas.</p></div></div>
|
|
</section>;
|
|
|
|
return <div className="asset-tab-stack">
|
|
<AssetTechnicalDataPanel assetId={assetId} canEdit={canEditTechnical} />
|
|
<section className="panel asset-finding-catalog-panel">
|
|
<div className="panel-heading">
|
|
<div>
|
|
<span className="eyebrow">HALLAZGOS APLICABLES</span>
|
|
<h2>{technical.family.name}</h2>
|
|
<p className="section-copy">Se muestran únicamente los Hallazgos asociados a esta clasificación técnica. Éste es el mismo catálogo que consume la APK para este objeto.</p>
|
|
</div>
|
|
<span className="count-pill">{items.length} asociados</span>
|
|
</div>
|
|
|
|
{canManage && <div className="temporal-notice">
|
|
<Icon name="layers" />
|
|
<p><strong>¿Falta un Hallazgo?</strong> Administrá la clasificación en Configuración de Inventarios y el cambio se aplicará a todos los objetos de este mismo tipo técnico.</p>
|
|
<Link className="button secondary" to="/admin/asset-types">Configurar Inventarios</Link>
|
|
</div>}
|
|
|
|
{items.length > 0 && <label className="search-field">
|
|
<Icon name="search" />
|
|
<input value={search} onChange={(event) => setSearch(event.target.value)} placeholder={`Buscar entre los ${items.length} Hallazgos asociados…`} />
|
|
</label>}
|
|
|
|
{items.length === 0
|
|
? <div className="inline-empty">Esta clasificación todavía no tiene Hallazgos asociados. Desde Configuración de Inventarios podés vincular los que correspondan.</div>
|
|
: visible.length === 0
|
|
? <div className="inline-empty">No hay Hallazgos asociados que coincidan con la búsqueda.</div>
|
|
: <div className="finding-selection-list">{visible.map((item) => <div className="finding-selection-row" key={item.id}>
|
|
<span className="asset-symbol"><Icon name="alert" size={15} /></span>
|
|
<span>
|
|
<strong>{item.title}</strong>
|
|
<small>{item.categoryName} · {item.code}{item.suggestedSeverity ? ` · gravedad sugerida ${item.suggestedSeverity}/10` : ''}</small>
|
|
{item.legalBasis && <small>{item.legalBasis}</small>}
|
|
</span>
|
|
</div>)}</div>}
|
|
</section>
|
|
</div>;
|
|
} |