diff --git a/src/model/catalog.test.ts b/src/model/catalog.test.ts index 79f02f8..efd7e8f 100644 --- a/src/model/catalog.test.ts +++ b/src/model/catalog.test.ts @@ -276,11 +276,25 @@ describe('helpers', () => { expect(switchesForRole('spine').map((i) => i.id)).toContain('switch-as7726') expect(switchesForRole('spine').map((i) => i.id)).not.toContain('switch-as4630') expect(serversForUsage('management').map((i) => i.id)).toEqual([ - 'server-mgmt', 'server-mgmt-121h', + 'server-mgmt', ]) }) + it('sorts eol and withdrawn hardware to the bottom', () => { + const lists = [ + serversForUsage('worker'), + serversForUsage('management'), + switchesForRole('leaf'), + ] + for (const list of lists) { + const current = list.filter((i) => (i.availability ?? 'current') === 'current') + const eol = list.filter((i) => i.availability === 'eol') + const withdrawn = list.filter((i) => i.availability === 'withdrawn') + expect(list).toEqual([...current, ...eol, ...withdrawn]) + } + }) + it('offers GPUs only for GPU-capable servers', () => { expect(gpusForServer('server-microcloud-x13').length).toBeGreaterThan(0) expect(gpusForServer('server-bigtwin-x11')).toEqual([]) diff --git a/src/model/catalog.ts b/src/model/catalog.ts index 8e8c2fc..c3e7a7a 100644 --- a/src/model/catalog.ts +++ b/src/model/catalog.ts @@ -622,18 +622,36 @@ export function portCount(item: CatalogItem, speed: PortSpeed): number { return item.ports?.find((p) => p.speed === speed)?.count ?? 0 } +// Dropdown order: current hardware first, then eol, then withdrawn. +// Catalog order within each group (the sort is stable). +const availabilityRank: Record = { current: 0, eol: 1, withdrawn: 2 } + +function rankOf(item: CatalogItem): number { + return availabilityRank[item.availability ?? 'current'] +} + +function byAvailability(a: CatalogItem, b: CatalogItem): number { + return rankOf(a) - rankOf(b) +} + export function switchesForRole(role: SwitchRole): CatalogItem[] { - return Object.values(catalog).filter((i) => i.switchRoles?.includes(role)) + return Object.values(catalog) + .filter((i) => i.switchRoles?.includes(role)) + .sort(byAvailability) } export function serversForUsage(usage: ServerUsage): CatalogItem[] { - return Object.values(catalog).filter((i) => i.serverUsages?.includes(usage)) + return Object.values(catalog) + .filter((i) => i.serverUsages?.includes(usage)) + .sort(byAvailability) } /** GPU models offerable for a server model — empty unless it accepts any. */ export function gpusForServer(serverModelId: string): CatalogItem[] { if (!catalog[serverModelId]?.gpuCapable) return [] - return Object.values(catalog).filter((i) => i.category === 'gpu') + return Object.values(catalog) + .filter((i) => i.category === 'gpu') + .sort(byAvailability) } /** How a BOM line names its item: the model people say, else the ordering