map: fix subtype filter dropping every measuring tool

This commit is contained in:
2026-07-31 07:59:20 -04:00
parent d6e3edb33f
commit a595eaffd5
4 changed files with 63 additions and 30 deletions

View File

@@ -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

View File

@@ -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 {}

View File

@@ -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')
})
})

View File

@@ -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