diff --git a/web-v2/src/features/inspections/FindingCatalogTypeApplicabilityPanel.tsx b/web-v2/src/features/inspections/FindingCatalogTypeApplicabilityPanel.tsx index 497f702..29f3871 100644 --- a/web-v2/src/features/inspections/FindingCatalogTypeApplicabilityPanel.tsx +++ b/web-v2/src/features/inspections/FindingCatalogTypeApplicabilityPanel.tsx @@ -2,54 +2,66 @@ import { SearchableSelect } from '../../components/SearchableSelect'; import { useEffect, useMemo, useState } from 'react'; import { Alert, LoadingBlock, errorMessage } from '../../components/Feedback'; import { Icon } from '../../components/Icon'; +import { getFindingCatalogAdmin } from '../../lib/api'; +import type { FindingAdminCatalog } from '../../lib/api'; import { - getFindingCatalogAssetTypeSelection, - listAssetTypes, - replaceFindingCatalogAssetTypeSelection, -} from '../../lib/api'; -import type { AssetType, FindingCatalogAssetTypeSelection } from '../../lib/api'; + listInventoryFamiliesAdmin, + replaceInventoryFamilyFindings, +} from '../../lib/inventoryStructureApi'; +import type { InventoryFamily } from '../../lib/inventoryStructureApi'; + +const EMPTY_CATALOG: FindingAdminCatalog = { categories: [], items: [] }; export function FindingCatalogTypeApplicabilityPanel() { - const [types, setTypes] = useState([]); - const [typeId, setTypeId] = useState(''); - const [selection, setSelection] = useState(null); + const [families, setFamilies] = useState([]); + const [catalog, setCatalog] = useState(EMPTY_CATALOG); + const [familyId, setFamilyId] = useState(''); const [enabled, setEnabled] = useState>(new Set()); - const [reason, setReason] = useState(''); + const [reason, setReason] = useState('Actualización de aplicabilidad por clasificación de Inventario'); const [search, setSearch] = useState(''); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [error, setError] = useState(''); const [success, setSuccess] = useState(''); + const selectedFamily = families.find((family) => family.id === familyId) ?? null; + useEffect(() => { - listAssetTypes() - .then((loaded) => { - const technical = loaded.filter((type) => type.isActive && type.operationalRole === 'GENERIC'); - setTypes(technical); - setTypeId(technical[0]?.id ?? ''); + Promise.all([listInventoryFamiliesAdmin(), getFindingCatalogAdmin()]) + .then(([loadedFamilies, loadedCatalog]) => { + const activeFamilies = loadedFamilies.filter((family) => family.isActive !== false); + setFamilies(activeFamilies); + setCatalog(loadedCatalog); + setFamilyId(activeFamilies[0]?.id ?? ''); }) .catch((requestError) => setError(errorMessage(requestError))) .finally(() => setLoading(false)); }, []); useEffect(() => { - if (!typeId) { setSelection(null); return; } - setLoading(true); setError(''); setSuccess(''); - getFindingCatalogAssetTypeSelection(typeId) - .then((loaded) => { - setSelection(loaded); - setEnabled(new Set(loaded.items.filter((item) => item.enabled).map((item) => item.id))); - setReason(loaded.reason ?? ''); - }) - .catch((requestError) => setError(errorMessage(requestError))) - .finally(() => setLoading(false)); - }, [typeId]); + const selected = families.find((family) => family.id === familyId); + setEnabled(new Set(selected?.findingItemIds ?? [])); + setSuccess(''); + setError(''); + }, [familyId, families]); + + const activeCategoryIds = useMemo(() => new Set( + catalog.categories.filter((category) => category.isActive).map((category) => category.id), + ), [catalog.categories]); + + const categoryName = useMemo(() => new Map( + catalog.categories.map((category) => [category.id, category.name]), + ), [catalog.categories]); const visible = useMemo(() => { - const needle = search.trim().toLocaleLowerCase(); - return selection?.items.filter((item) => !needle || [item.title, item.code, item.categoryName] - .some((value) => value.toLocaleLowerCase().includes(needle))) ?? []; - }, [selection, search]); + const needle = search.trim().toLocaleLowerCase('es-AR'); + return catalog.items.filter((item) => + item.isActive + && activeCategoryIds.has(item.categoryId) + && (!needle || [item.title, item.code, categoryName.get(item.categoryId) ?? ''] + .some((value) => value.toLocaleLowerCase('es-AR').includes(needle))), + ); + }, [catalog.items, search, activeCategoryIds, categoryName]); const toggle = (id: string) => setEnabled((current) => { const next = new Set(current); @@ -58,17 +70,19 @@ export function FindingCatalogTypeApplicabilityPanel() { }); const save = async () => { - if (!selection) return; + if (!selectedFamily) return; setSaving(true); setError(''); setSuccess(''); try { - const saved = await replaceFindingCatalogAssetTypeSelection(selection.assetType.id, { - enabledItemIds: [...enabled], + const saved = await replaceInventoryFamilyFindings(selectedFamily.id, { + itemIds: [...enabled], reason, }); - setSelection(saved); - setEnabled(new Set(saved.items.filter((item) => item.enabled).map((item) => item.id))); - setReason(saved.reason ?? reason); - setSuccess(`Aplicabilidad guardada para ${saved.assetType.name}.`); + const itemIds = saved.items.map((item) => item.id); + setEnabled(new Set(itemIds)); + setFamilies((current) => current.map((family) => family.id === selectedFamily.id + ? { ...family, findingItemIds: itemIds, findingCount: itemIds.length } + : family)); + setSuccess(`Hallazgos guardados para ${selectedFamily.name}.`); } catch (requestError) { setError(errorMessage(requestError)); } finally { @@ -76,20 +90,43 @@ export function FindingCatalogTypeApplicabilityPanel() { } }; - if (loading && types.length === 0) return
; - if (types.length === 0) return null; + if (loading) return
; + if (families.length === 0) return No hay clasificaciones de Instalación/Subinstalación disponibles. Crealas primero en Configuración de Inventarios.; return
-
APLICABILIDAD POR TIPO TÉCNICO

Qué hallazgos verá el inspector

Configurá el catálogo base para cada tipo de elemento del Inventario. Después se pueden hacer excepciones por objeto concreto.

{enabled.size} habilitados
+
+
+ APLICABILIDAD POR INSTALACIÓN / SUBINSTALACIÓN +

Qué Hallazgos verá el inspector

+

Los Hallazgos se vinculan a la clasificación concreta del elemento, no a una “función”. La opción OTROS permanece siempre disponible en la APK.

+
+ {enabled.size} vinculados +
{error && {error}}{success && {success}}
- - + +
- {selection && !selection.configured &&

Este tipo todavía no fue configurado. Para no romper el funcionamiento actual, hoy recibe todo el catálogo activo. Al guardar esta pantalla, sólo quedarán habilitados los seleccionados.

} -
-
{visible.map((item) => )}
-