Add manufacturer catalog, category/manufacturer library management, and built-in cable flag

Normalizes manufacturer as a shared catalog entity (like device categories)
instead of free text on each device, giving the admin duplicate-detection
nudge a reliable signal. Adds full CRUD (including Admin direct-publish,
bypassing the submission queue) for categories, manufacturers, port types,
and cable types, plus a Categories & Manufacturers library modal and a
browse-by-manufacturer/search view in the device palette. Adds
Port.builtInCable so a captive/permanently-attached cable (a keyboard's USB
lead, a budget AVR's power cord) can be flagged and excluded from the BOM.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
This commit is contained in:
2026-09-28 10:04:05 -05:00
co-authored by Claude Sonnet 5
parent 00777077c2
commit 29c3ed46a3
28 changed files with 2201 additions and 320 deletions
+16 -3
View File
@@ -19,6 +19,7 @@ export const CATALOG_TABLE_BY_ENTITY_TYPE: Record<CatalogEntityType, string> = {
port_type: 'port_types',
cable_type: 'cable_types',
device_category: 'device_categories',
manufacturer: 'manufacturers',
}
export function portTypeToRow(portType: Omit<PortType, 'id' | 'custom'>): Record<string, unknown> {
@@ -45,11 +46,15 @@ export function deviceCategoryToRow(name: string): Record<string, unknown> {
return { name }
}
export function manufacturerToRow(name: string): Record<string, unknown> {
return { name }
}
export function deviceTemplateToRow(template: Omit<DeviceTemplate, 'id' | 'custom'>): Record<string, unknown> {
return {
name: template.name,
category_id: template.category,
manufacturer: template.manufacturer ?? null,
manufacturer_id: template.manufacturerId ?? null,
model: template.model ?? null,
cost: template.cost ?? null,
ports: template.ports.map((port, index) => ({
@@ -57,6 +62,7 @@ export function deviceTemplateToRow(template: Omit<DeviceTemplate, 'id' | 'custo
direction: port.direction,
port_type_id: port.portTypeId,
sort_order: index,
built_in_cable: port.builtInCable ?? false,
})),
}
}
@@ -92,11 +98,17 @@ export function rowToCableTypeFields(row: Record<string, unknown>): Omit<CableTy
}
export function rowToDeviceTemplateFields(row: Record<string, unknown>): Omit<DeviceTemplate, 'id' | 'custom'> {
const ports = (row.ports as Array<{ name: string; direction: DeviceTemplate['ports'][number]['direction']; port_type_id: string }>) ?? []
const ports =
(row.ports as Array<{
name: string
direction: DeviceTemplate['ports'][number]['direction']
port_type_id: string
built_in_cable?: boolean
}>) ?? []
return {
name: row.name as string,
category: row.category_id as string,
manufacturer: (row.manufacturer as string | null) ?? undefined,
manufacturerId: (row.manufacturer_id as string | null) ?? undefined,
model: (row.model as string | null) ?? undefined,
cost: (row.cost as number | null) ?? undefined,
ports: ports.map((port, index) => ({
@@ -104,6 +116,7 @@ export function rowToDeviceTemplateFields(row: Record<string, unknown>): Omit<De
name: port.name,
direction: port.direction,
portTypeId: port.port_type_id,
builtInCable: port.built_in_cable ?? false,
})),
}
}