From 71982fc0f1563a694038fc1b22720656a772a2de Mon Sep 17 00:00:00 2001 From: cproudlock Date: Fri, 31 Jul 2026 07:56:09 -0400 Subject: [PATCH] map: fix subtype filter dropping every measuring tool MapView carried its own copy of the per-type subtype-id lookup and it never gained a Measuring Tool branch, so selecting any measuring-tool subtype filtered out all assets. Marker coloring and the PDF export were unaffected because both already used the shared getSubtypeId helper. Point the filter at that shared helper and delete the duplicate copy in ShopFloorMap too, so one definition serves filter, coloring and export. Adds a table-driven spec covering every subtype-carrying asset type. --- frontend/src/components/ShopFloorMap.vue | 14 +-------- frontend/src/utils/mapColors.spec.js | 38 ++++++++++++++++++++++++ frontend/src/views/MapView.vue | 24 +++++---------- 3 files changed, 46 insertions(+), 30 deletions(-) create mode 100644 frontend/src/utils/mapColors.spec.js diff --git a/frontend/src/components/ShopFloorMap.vue b/frontend/src/components/ShopFloorMap.vue index 675ce4b..5ab6411 100644 --- a/frontend/src/components/ShopFloorMap.vue +++ b/frontend/src/components/ShopFloorMap.vue @@ -97,6 +97,7 @@ import L from 'leaflet' import 'leaflet/dist/leaflet.css' import { loadMapConfig, blueprintUrlFor, state as mapConfig } from '../composables/mapConfig' import { assetTypeLabel, assetDetailRoute } from '../utils/assetTypes' +import { getSubtypeId } from '../utils/mapColors' import api from '../api' const props = defineProps({ @@ -239,19 +240,6 @@ const visibleAssetTypes = computed(() => { return result }) -// Get subtype ID from asset based on asset type -function getSubtypeId(asset) { - if (!asset.typedata) return null - // Normalize network_device -> network device so the subtype id resolves. - const typeLower = (asset.assettype || '').toLowerCase().replace(/_/g, ' ') - if (typeLower === 'machine') return asset.typedata.machinetypeid - if (typeLower === 'computer') return asset.typedata.computertypeid - if (typeLower === 'network device') return asset.typedata.networkdevicetypeid - if (typeLower === 'printer') return asset.typedata.printertypeid - if (typeLower === 'measuring tool') return asset.typedata.measuringtooltypeid - return null -} - // Get visible subtypes when a type is selected const visibleSubtypes = computed(() => { if (!props.selectedAssetType) return {} diff --git a/frontend/src/utils/mapColors.spec.js b/frontend/src/utils/mapColors.spec.js new file mode 100644 index 0000000..36b9233 --- /dev/null +++ b/frontend/src/utils/mapColors.spec.js @@ -0,0 +1,38 @@ +import { describe, it, expect } from 'vitest' +import { getSubtypeId, resolveMarkerColor } from './mapColors' + +// One case per asset type that carries a subtype. A missing branch here means +// the map subtype filter silently drops every asset of that type. +const cases = [ + ['Machine', { machinetypeid: 3 }], + ['Computer', { computertypeid: 3 }], + ['Network Device', { networkdevicetypeid: 3 }], + ['network_device', { networkdevicetypeid: 3 }], + ['Printer', { printertypeid: 3 }], + ['Measuring Tool', { measuringtooltypeid: 3 }], +] + +describe('getSubtypeId', () => { + it.each(cases)('resolves the subtype id for %s', (assettype, typedata) => { + expect(getSubtypeId({ assettype, typedata })).toBe(3) + }) + + it('returns null without typedata', () => { + expect(getSubtypeId({ assettype: 'Machine' })).toBeNull() + }) + + it('returns null for an asset type with no subtype table', () => { + expect(getSubtypeId({ assettype: 'Furniture', typedata: {} })).toBeNull() + }) +}) + +describe('resolveMarkerColor', () => { + it('colors a measuring tool by its subtype when a type is selected', () => { + const asset = { assettype: 'Measuring Tool', typedata: { measuringtooltypeid: 7 } } + const color = resolveMarkerColor(asset, { + selectedType: 'Measuring Tool', + subtypeColors: { 7: '#00BCD4' }, + }) + expect(color).toBe('#00BCD4') + }) +}) diff --git a/frontend/src/views/MapView.vue b/frontend/src/views/MapView.vue index 88e2e5f..913be7f 100644 --- a/frontend/src/views/MapView.vue +++ b/frontend/src/views/MapView.vue @@ -85,6 +85,7 @@ import { useAuthStore } from '../stores/auth' import { loadMapConfig, state as mapConfig } from '../composables/mapConfig' import { exportMapPdf } from '../utils/mapPdf' import { assetTypeLabel, assetDetailRoute } from '../utils/assetTypes' +import { getSubtypeId } from '../utils/mapColors' import { useToast } from '../composables/toast' const toast = useToast() @@ -133,7 +134,8 @@ const subtypeLabel = computed(() => { 'machine': 'All Machine Types', 'computer': 'All Computer Types', 'network device': 'All Device Types', - 'printer': 'All Printer Types' + 'printer': 'All Printer Types', + 'measuring tool': 'All Tool Types' } return labels[selectedType.value.toLowerCase().replace(/_/g, ' ')] || 'All Subtypes' }) @@ -175,24 +177,12 @@ const filteredAssets = computed(() => { result = result.filter(a => a.assettype && a.assettype.toLowerCase() === selectedLower) } - // Filter by subtype (normalize network_device -> network device) + // Filter by subtype. Uses the shared getSubtypeId helper so this stays in + // step with the marker coloring - a local copy of the per-type id lookup went + // stale and silently dropped every measuring tool. if (selectedSubtype.value) { const subtypeId = parseInt(selectedSubtype.value) - const typeLower = (selectedType.value || '').toLowerCase().replace(/_/g, ' ') - result = result.filter(a => { - if (!a.typedata) return false - // Check different ID fields based on asset type - if (typeLower === 'machine') { - return a.typedata.machinetypeid === subtypeId - } else if (typeLower === 'computer') { - return a.typedata.computertypeid === subtypeId - } else if (typeLower === 'network device') { - return a.typedata.networkdevicetypeid === subtypeId - } else if (typeLower === 'printer') { - return a.typedata.printertypeid === subtypeId - } - return false - }) + result = result.filter(a => getSubtypeId(a) === subtypeId) } // Filter by business unit