From a595eaffd585575f7f8e8df37453c3ee6f056e6e Mon Sep 17 00:00:00 2001 From: Cam Proudlock Date: Fri, 31 Jul 2026 07:59:20 -0400 Subject: [PATCH] map: fix subtype filter dropping every measuring tool --- CHANGELOG.md | 17 +++++++++++ frontend/src/components/ShopFloorMap.vue | 14 +-------- frontend/src/utils/mapColors.spec.js | 38 ++++++++++++++++++++++++ frontend/src/views/MapView.vue | 24 +++++---------- 4 files changed, 63 insertions(+), 30 deletions(-) create mode 100644 frontend/src/utils/mapColors.spec.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 6997d139..fc0adbc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,14 @@ ADR-007 and ADR-002. ## [Unreleased] +### Added + +- Configurable site timezone: a `site_timezone` site setting (default + `America/New_York`, public-readable), editable in Settings > Site > + Localization. Notification start/end times are entered and displayed in this + zone, and daily-reset notification expiry is computed in it. A shared + `frontend/src/utils/datetime.js` (Intl-based, DST-safe) does the conversion. + ### Changed - Asset detail pages (machines, PCs, printers, network devices, measuring @@ -24,6 +32,15 @@ ADR-007 and ADR-002. ### Fixed +- Notification start/end times were off by the timezone offset (a 2:34 PM entry + displayed as 6:34 PM). Times are now stored UTC and shown/entered in the site + timezone; the calendar keys all-day events off the site-local day. +- Kiosk displays showed a white screen on login: a legacy 32-bit kiosk + installer's autostart kept relaunching Edge at a now-dead URL. The install's + HKLM Run value was WOW64-redirected into `SOFTWARE\Wow6432Node\...\Run` and + survived earlier cleanup. The `gea-shopfloor-display` dispatcher now purges + the legacy autostarts every enforce cycle across both registry views, all + user hives, Run/RunOnce/policy-Run, and every Startup folder. - List pages keep the current page (and search term) in the URL query, so paging to page 9, opening an item, and hitting browser Back returns to page 9 instead of resetting to page 1. Applies to all 18 list views via a shared diff --git a/frontend/src/components/ShopFloorMap.vue b/frontend/src/components/ShopFloorMap.vue index 675ce4ba..5ab64110 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 00000000..36b9233d --- /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 88e2e5f2..913be7f8 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