map: fix subtype filter dropping every measuring tool
This commit is contained in:
17
CHANGELOG.md
17
CHANGELOG.md
@@ -10,6 +10,14 @@ ADR-007 and ADR-002.
|
|||||||
|
|
||||||
## [Unreleased]
|
## [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
|
### Changed
|
||||||
|
|
||||||
- Asset detail pages (machines, PCs, printers, network devices, measuring
|
- Asset detail pages (machines, PCs, printers, network devices, measuring
|
||||||
@@ -24,6 +32,15 @@ ADR-007 and ADR-002.
|
|||||||
|
|
||||||
### Fixed
|
### 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
|
- 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
|
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
|
instead of resetting to page 1. Applies to all 18 list views via a shared
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ import L from 'leaflet'
|
|||||||
import 'leaflet/dist/leaflet.css'
|
import 'leaflet/dist/leaflet.css'
|
||||||
import { loadMapConfig, blueprintUrlFor, state as mapConfig } from '../composables/mapConfig'
|
import { loadMapConfig, blueprintUrlFor, state as mapConfig } from '../composables/mapConfig'
|
||||||
import { assetTypeLabel, assetDetailRoute } from '../utils/assetTypes'
|
import { assetTypeLabel, assetDetailRoute } from '../utils/assetTypes'
|
||||||
|
import { getSubtypeId } from '../utils/mapColors'
|
||||||
import api from '../api'
|
import api from '../api'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -239,19 +240,6 @@ const visibleAssetTypes = computed(() => {
|
|||||||
return result
|
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
|
// Get visible subtypes when a type is selected
|
||||||
const visibleSubtypes = computed(() => {
|
const visibleSubtypes = computed(() => {
|
||||||
if (!props.selectedAssetType) return {}
|
if (!props.selectedAssetType) return {}
|
||||||
|
|||||||
38
frontend/src/utils/mapColors.spec.js
Normal file
38
frontend/src/utils/mapColors.spec.js
Normal 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')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -85,6 +85,7 @@ import { useAuthStore } from '../stores/auth'
|
|||||||
import { loadMapConfig, state as mapConfig } from '../composables/mapConfig'
|
import { loadMapConfig, state as mapConfig } from '../composables/mapConfig'
|
||||||
import { exportMapPdf } from '../utils/mapPdf'
|
import { exportMapPdf } from '../utils/mapPdf'
|
||||||
import { assetTypeLabel, assetDetailRoute } from '../utils/assetTypes'
|
import { assetTypeLabel, assetDetailRoute } from '../utils/assetTypes'
|
||||||
|
import { getSubtypeId } from '../utils/mapColors'
|
||||||
import { useToast } from '../composables/toast'
|
import { useToast } from '../composables/toast'
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
@@ -133,7 +134,8 @@ const subtypeLabel = computed(() => {
|
|||||||
'machine': 'All Machine Types',
|
'machine': 'All Machine Types',
|
||||||
'computer': 'All Computer Types',
|
'computer': 'All Computer Types',
|
||||||
'network device': 'All Device 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'
|
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)
|
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) {
|
if (selectedSubtype.value) {
|
||||||
const subtypeId = parseInt(selectedSubtype.value)
|
const subtypeId = parseInt(selectedSubtype.value)
|
||||||
const typeLower = (selectedType.value || '').toLowerCase().replace(/_/g, ' ')
|
result = result.filter(a => getSubtypeId(a) === subtypeId)
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter by business unit
|
// Filter by business unit
|
||||||
|
|||||||
Reference in New Issue
Block a user