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:
@@ -1,5 +1,5 @@
|
||||
import type { CatalogEntityType } from './SubmissionRepository'
|
||||
import type { Catalog, CableType, DeviceCategoryDef, DeviceTemplate, PortType } from '../domain/types'
|
||||
import type { Catalog, CableType, DeviceCategoryDef, DeviceTemplate, Manufacturer, PortType } from '../domain/types'
|
||||
|
||||
/**
|
||||
* Storage abstraction for the global port/cable/category/device-template
|
||||
@@ -22,26 +22,75 @@ export interface CatalogRepository {
|
||||
load(): Promise<Catalog>
|
||||
|
||||
addDeviceCategory(name: string): Promise<DeviceCategoryDef>
|
||||
updateDeviceCategory(id: string, name: string): Promise<void>
|
||||
/** Throws (rather than swallowing, like most methods here) if the delete
|
||||
* is rejected — most commonly a foreign-key violation because one of
|
||||
* your device templates still references this category, which the
|
||||
* caller needs to know about to tell you why nothing happened. */
|
||||
removeDeviceCategory(id: string): Promise<void>
|
||||
|
||||
addManufacturer(name: string): Promise<Manufacturer>
|
||||
updateManufacturer(id: string, name: string): Promise<void>
|
||||
/** Same throw-on-failure contract as removeDeviceCategory. */
|
||||
removeManufacturer(id: string): Promise<void>
|
||||
|
||||
addPortType(portType: Omit<PortType, 'id' | 'custom'>): Promise<PortType>
|
||||
updatePortType(id: string, patch: Partial<Omit<PortType, 'id' | 'custom'>>): Promise<void>
|
||||
/** Same throw-on-failure contract as removeDeviceCategory — most commonly
|
||||
* a foreign-key violation because some device template's port still
|
||||
* references it. */
|
||||
removePortType(id: string): Promise<void>
|
||||
|
||||
addCableType(cableType: Omit<CableType, 'id' | 'custom'>): Promise<CableType>
|
||||
updateCableType(id: string, patch: Partial<Omit<CableType, 'id' | 'custom'>>): Promise<void>
|
||||
removeCableType(id: string): Promise<void>
|
||||
|
||||
addDeviceTemplate(template: Omit<DeviceTemplate, 'id' | 'custom'>): Promise<DeviceTemplate>
|
||||
updateDeviceTemplate(id: string, patch: Partial<Omit<DeviceTemplate, 'id' | 'custom'>>): Promise<void>
|
||||
removeDeviceTemplate(id: string): Promise<void>
|
||||
|
||||
/** Admin/Super-Admin direct-to-public creation — skips the submission/
|
||||
* review workflow entirely, for authoring the shared library yourself
|
||||
* (e.g. building out the initial device catalog) rather than reviewing
|
||||
* someone else's proposal. Same RLS branch that already allows an Admin
|
||||
* to directly edit a public entry (`is_admin() and is_public`) also
|
||||
* covers inserting one — unlike adminUpdate* below, this is a genuinely
|
||||
* different write, not just an alias (is_public: true, owner_id: null
|
||||
* from the start). */
|
||||
adminAddDeviceCategory(name: string): Promise<DeviceCategoryDef>
|
||||
adminAddManufacturer(name: string): Promise<Manufacturer>
|
||||
adminAddPortType(portType: Omit<PortType, 'id' | 'custom'>): Promise<PortType>
|
||||
adminAddCableType(cableType: Omit<CableType, 'id' | 'custom'>): Promise<CableType>
|
||||
adminAddDeviceTemplate(template: Omit<DeviceTemplate, 'id' | 'custom'>): Promise<DeviceTemplate>
|
||||
|
||||
/** Admin/Super-Admin direct edit of an already-public port/cable/device
|
||||
* entry — same underlying write as approving a submission, minus the
|
||||
* submission. */
|
||||
adminUpdatePortType(id: string, patch: Partial<Omit<PortType, 'id' | 'custom'>>): Promise<void>
|
||||
adminUpdateCableType(id: string, patch: Partial<Omit<CableType, 'id' | 'custom'>>): Promise<void>
|
||||
adminUpdateDeviceTemplate(id: string, patch: Partial<Omit<DeviceTemplate, 'id' | 'custom'>>): Promise<void>
|
||||
adminUpdateDeviceCategory(id: string, name: string): Promise<void>
|
||||
adminUpdateManufacturer(id: string, name: string): Promise<void>
|
||||
/** Unpublishes a public entry (is_public -> false) rather than deleting
|
||||
* it — organized-ideas.md §3's "public catalog entries are never hard-
|
||||
* deleted, only hidden/unpublished". Existing diagrams that reference it
|
||||
* by id are unaffected; it just stops being offered for new use. */
|
||||
adminUnpublish(entityType: CatalogEntityType, id: string): Promise<void>
|
||||
|
||||
/** Admin/Super-Admin permanent delete of a built-in (public) entry —
|
||||
* narrower than adminUnpublish: the caller is expected to have already
|
||||
* verified zero usage (no diagram references it, nothing else in the
|
||||
* catalog does either) before calling this, same gate already applied to
|
||||
* a regular user deleting their own private entry. Doesn't relax
|
||||
* organized-ideas.md §3's "never break an existing diagram" principle —
|
||||
* it only ever removes something genuinely unused (e.g. a mistake made
|
||||
* while authoring the library), which is why this exists alongside
|
||||
* adminUnpublish rather than instead of it. Same RLS branch as the other
|
||||
* admin* methods (`is_admin()`), so these are aliases of the plain
|
||||
* remove* methods above, not separate logic. Throws on failure. */
|
||||
adminRemoveDeviceCategory(id: string): Promise<void>
|
||||
adminRemoveManufacturer(id: string): Promise<void>
|
||||
adminRemovePortType(id: string): Promise<void>
|
||||
adminRemoveCableType(id: string): Promise<void>
|
||||
adminRemoveDeviceTemplate(id: string): Promise<void>
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/** Which catalog table a submission is about. Mirrors `catalog_submissions.entity_type`'s
|
||||
* check constraint — `manufacturer` is omitted since the app doesn't wire up the
|
||||
* normalized manufacturer catalog yet (see organized-ideas.md §3, deferred alongside
|
||||
* device_templates.manufacturer staying free text for now). */
|
||||
export type CatalogEntityType = 'device_template' | 'port_type' | 'cable_type' | 'device_category'
|
||||
* check constraint. */
|
||||
export type CatalogEntityType = 'device_template' | 'port_type' | 'cable_type' | 'device_category' | 'manufacturer'
|
||||
|
||||
/**
|
||||
* A user's request to change the public catalog — either promoting one of
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import type { Catalog, CableType, DeviceCategoryDef, DeviceTemplate, Port, PortType } from '../domain/types'
|
||||
import type { Catalog, CableType, DeviceCategoryDef, DeviceTemplate, Manufacturer, Port, PortType } from '../domain/types'
|
||||
import { CATALOG_TABLE_BY_ENTITY_TYPE, cableTypeToRow, deviceTemplateToRow, portTypeToRow } from './catalogRowMapping'
|
||||
import type { CatalogRepository } from './CatalogRepository'
|
||||
import type { CatalogEntityType } from './SubmissionRepository'
|
||||
@@ -11,6 +11,12 @@ interface DeviceCategoryRow {
|
||||
is_public: boolean
|
||||
}
|
||||
|
||||
interface ManufacturerRow {
|
||||
id: string
|
||||
name: string
|
||||
is_public: boolean
|
||||
}
|
||||
|
||||
interface PortTypeRow {
|
||||
id: string
|
||||
name: string
|
||||
@@ -35,7 +41,7 @@ interface DeviceTemplateRow {
|
||||
id: string
|
||||
name: string
|
||||
category_id: string
|
||||
manufacturer: string | null
|
||||
manufacturer_id: string | null
|
||||
model: string | null
|
||||
cost: number | null
|
||||
is_public: boolean
|
||||
@@ -48,12 +54,17 @@ interface DeviceTemplatePortRow {
|
||||
direction: Port['direction']
|
||||
port_type_id: string
|
||||
sort_order: number
|
||||
built_in_cable: boolean
|
||||
}
|
||||
|
||||
function toDeviceCategory(row: DeviceCategoryRow): DeviceCategoryDef {
|
||||
return { id: row.id, name: row.name, custom: !row.is_public }
|
||||
}
|
||||
|
||||
function toManufacturer(row: ManufacturerRow): Manufacturer {
|
||||
return { id: row.id, name: row.name, custom: !row.is_public }
|
||||
}
|
||||
|
||||
function toPortType(row: PortTypeRow): PortType {
|
||||
return {
|
||||
id: row.id,
|
||||
@@ -83,13 +94,13 @@ function toDeviceTemplate(row: DeviceTemplateRow, portRows: DeviceTemplatePortRo
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
category: row.category_id,
|
||||
manufacturer: row.manufacturer ?? undefined,
|
||||
manufacturerId: row.manufacturer_id ?? undefined,
|
||||
model: row.model ?? undefined,
|
||||
cost: row.cost ?? undefined,
|
||||
ports: portRows
|
||||
.filter((p) => p.device_template_id === row.id)
|
||||
.sort((a, b) => a.sort_order - b.sort_order)
|
||||
.map((p) => ({ id: p.id, name: p.name, direction: p.direction, portTypeId: p.port_type_id })),
|
||||
.map((p) => ({ id: p.id, name: p.name, direction: p.direction, portTypeId: p.port_type_id, builtInCable: p.built_in_cable })),
|
||||
custom: !row.is_public,
|
||||
}
|
||||
}
|
||||
@@ -105,8 +116,9 @@ function toDeviceTemplate(row: DeviceTemplateRow, portRows: DeviceTemplatePortRo
|
||||
*/
|
||||
export class SupabaseCatalogRepository implements CatalogRepository {
|
||||
async load(): Promise<Catalog> {
|
||||
const [categories, portTypes, cableTypes, templates, templatePorts] = await Promise.all([
|
||||
const [categories, manufacturers, portTypes, cableTypes, templates, templatePorts] = await Promise.all([
|
||||
supabase.from('device_categories').select('id, name, is_public').order('name'),
|
||||
supabase.from('manufacturers').select('id, name, is_public').order('name'),
|
||||
supabase
|
||||
.from('port_types')
|
||||
.select('id, name, category, family, compatible_family_ids, max_connections, is_public')
|
||||
@@ -115,12 +127,15 @@ export class SupabaseCatalogRepository implements CatalogRepository {
|
||||
.from('cable_types')
|
||||
.select('id, name, family, family2, unit, cost_per_unit, is_public')
|
||||
.order('name'),
|
||||
supabase.from('device_templates').select('id, name, category_id, manufacturer, model, cost, is_public').order('name'),
|
||||
supabase.from('device_template_ports').select('id, device_template_id, name, direction, port_type_id, sort_order'),
|
||||
supabase.from('device_templates').select('id, name, category_id, manufacturer_id, model, cost, is_public').order('name'),
|
||||
supabase
|
||||
.from('device_template_ports')
|
||||
.select('id, device_template_id, name, direction, port_type_id, sort_order, built_in_cable'),
|
||||
])
|
||||
|
||||
for (const [label, result] of [
|
||||
['device categories', categories],
|
||||
['manufacturers', manufacturers],
|
||||
['port types', portTypes],
|
||||
['cable types', cableTypes],
|
||||
['device templates', templates],
|
||||
@@ -132,6 +147,7 @@ export class SupabaseCatalogRepository implements CatalogRepository {
|
||||
const portRows = (templatePorts.data ?? []) as DeviceTemplatePortRow[]
|
||||
return {
|
||||
deviceCategories: ((categories.data ?? []) as DeviceCategoryRow[]).map(toDeviceCategory),
|
||||
manufacturers: ((manufacturers.data ?? []) as ManufacturerRow[]).map(toManufacturer),
|
||||
portTypes: ((portTypes.data ?? []) as PortTypeRow[]).map(toPortType),
|
||||
cableTypes: ((cableTypes.data ?? []) as CableTypeRow[]).map(toCableType),
|
||||
deviceTemplates: ((templates.data ?? []) as DeviceTemplateRow[]).map((row) => toDeviceTemplate(row, portRows)),
|
||||
@@ -145,24 +161,81 @@ export class SupabaseCatalogRepository implements CatalogRepository {
|
||||
return user?.id ?? null
|
||||
}
|
||||
|
||||
async addDeviceCategory(name: string): Promise<DeviceCategoryDef> {
|
||||
const ownerId = await this.currentUserId()
|
||||
private async insertDeviceCategory(name: string, isPublic: boolean, ownerId: string | null): Promise<DeviceCategoryDef> {
|
||||
const id = uuid()
|
||||
const { error } = await supabase
|
||||
.from('device_categories')
|
||||
.insert({ id, name, is_public: false, owner_id: ownerId })
|
||||
const { error } = await supabase.from('device_categories').insert({ id, name, is_public: isPublic, owner_id: ownerId })
|
||||
if (error) console.error('Failed to add device category to Supabase', error)
|
||||
return { id, name, custom: true }
|
||||
return { id, name, custom: !isPublic }
|
||||
}
|
||||
|
||||
async addPortType(portType: Omit<PortType, 'id' | 'custom'>): Promise<PortType> {
|
||||
const ownerId = await this.currentUserId()
|
||||
async addDeviceCategory(name: string): Promise<DeviceCategoryDef> {
|
||||
return this.insertDeviceCategory(name, false, await this.currentUserId())
|
||||
}
|
||||
|
||||
adminAddDeviceCategory(name: string): Promise<DeviceCategoryDef> {
|
||||
return this.insertDeviceCategory(name, true, null)
|
||||
}
|
||||
|
||||
async updateDeviceCategory(id: string, name: string): Promise<void> {
|
||||
const { error } = await supabase.from('device_categories').update({ name }).eq('id', id)
|
||||
if (error) console.error('Failed to update device category in Supabase', error)
|
||||
}
|
||||
|
||||
async removeDeviceCategory(id: string): Promise<void> {
|
||||
const { error } = await supabase.from('device_categories').delete().eq('id', id)
|
||||
if (error) {
|
||||
console.error('Failed to remove device category from Supabase', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
private async insertManufacturer(name: string, isPublic: boolean, ownerId: string | null): Promise<Manufacturer> {
|
||||
const id = uuid()
|
||||
const { error } = await supabase.from('manufacturers').insert({ id, name, is_public: isPublic, owner_id: ownerId })
|
||||
if (error) console.error('Failed to add manufacturer to Supabase', error)
|
||||
return { id, name, custom: !isPublic }
|
||||
}
|
||||
|
||||
async addManufacturer(name: string): Promise<Manufacturer> {
|
||||
return this.insertManufacturer(name, false, await this.currentUserId())
|
||||
}
|
||||
|
||||
adminAddManufacturer(name: string): Promise<Manufacturer> {
|
||||
return this.insertManufacturer(name, true, null)
|
||||
}
|
||||
|
||||
async updateManufacturer(id: string, name: string): Promise<void> {
|
||||
const { error } = await supabase.from('manufacturers').update({ name }).eq('id', id)
|
||||
if (error) console.error('Failed to update manufacturer in Supabase', error)
|
||||
}
|
||||
|
||||
async removeManufacturer(id: string): Promise<void> {
|
||||
const { error } = await supabase.from('manufacturers').delete().eq('id', id)
|
||||
if (error) {
|
||||
console.error('Failed to remove manufacturer from Supabase', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
private async insertPortType(
|
||||
portType: Omit<PortType, 'id' | 'custom'>,
|
||||
isPublic: boolean,
|
||||
ownerId: string | null,
|
||||
): Promise<PortType> {
|
||||
const id = uuid()
|
||||
const { error } = await supabase
|
||||
.from('port_types')
|
||||
.insert({ id, ...portTypeToRow(portType), is_public: false, owner_id: ownerId })
|
||||
.insert({ id, ...portTypeToRow(portType), is_public: isPublic, owner_id: ownerId })
|
||||
if (error) console.error('Failed to add port type to Supabase', error)
|
||||
return { ...portType, id, custom: true }
|
||||
return { ...portType, id, custom: !isPublic }
|
||||
}
|
||||
|
||||
async addPortType(portType: Omit<PortType, 'id' | 'custom'>): Promise<PortType> {
|
||||
return this.insertPortType(portType, false, await this.currentUserId())
|
||||
}
|
||||
|
||||
adminAddPortType(portType: Omit<PortType, 'id' | 'custom'>): Promise<PortType> {
|
||||
return this.insertPortType(portType, true, null)
|
||||
}
|
||||
|
||||
async updatePortType(id: string, patch: Partial<Omit<PortType, 'id' | 'custom'>>): Promise<void> {
|
||||
@@ -176,14 +249,33 @@ export class SupabaseCatalogRepository implements CatalogRepository {
|
||||
if (error) console.error('Failed to update port type in Supabase', error)
|
||||
}
|
||||
|
||||
async addCableType(cableType: Omit<CableType, 'id' | 'custom'>): Promise<CableType> {
|
||||
const ownerId = await this.currentUserId()
|
||||
async removePortType(id: string): Promise<void> {
|
||||
const { error } = await supabase.from('port_types').delete().eq('id', id)
|
||||
if (error) {
|
||||
console.error('Failed to remove port type from Supabase', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
private async insertCableType(
|
||||
cableType: Omit<CableType, 'id' | 'custom'>,
|
||||
isPublic: boolean,
|
||||
ownerId: string | null,
|
||||
): Promise<CableType> {
|
||||
const id = uuid()
|
||||
const { error } = await supabase
|
||||
.from('cable_types')
|
||||
.insert({ id, ...cableTypeToRow(cableType), is_public: false, owner_id: ownerId })
|
||||
.insert({ id, ...cableTypeToRow(cableType), is_public: isPublic, owner_id: ownerId })
|
||||
if (error) console.error('Failed to add cable type to Supabase', error)
|
||||
return { ...cableType, id, custom: true }
|
||||
return { ...cableType, id, custom: !isPublic }
|
||||
}
|
||||
|
||||
async addCableType(cableType: Omit<CableType, 'id' | 'custom'>): Promise<CableType> {
|
||||
return this.insertCableType(cableType, false, await this.currentUserId())
|
||||
}
|
||||
|
||||
adminAddCableType(cableType: Omit<CableType, 'id' | 'custom'>): Promise<CableType> {
|
||||
return this.insertCableType(cableType, true, null)
|
||||
}
|
||||
|
||||
async updateCableType(id: string, patch: Partial<Omit<CableType, 'id' | 'custom'>>): Promise<void> {
|
||||
@@ -197,14 +289,25 @@ export class SupabaseCatalogRepository implements CatalogRepository {
|
||||
if (error) console.error('Failed to update cable type in Supabase', error)
|
||||
}
|
||||
|
||||
async addDeviceTemplate(template: Omit<DeviceTemplate, 'id' | 'custom'>): Promise<DeviceTemplate> {
|
||||
const ownerId = await this.currentUserId()
|
||||
async removeCableType(id: string): Promise<void> {
|
||||
const { error } = await supabase.from('cable_types').delete().eq('id', id)
|
||||
if (error) {
|
||||
console.error('Failed to remove cable type from Supabase', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
private async insertDeviceTemplate(
|
||||
template: Omit<DeviceTemplate, 'id' | 'custom'>,
|
||||
isPublic: boolean,
|
||||
ownerId: string | null,
|
||||
): Promise<DeviceTemplate> {
|
||||
const id = uuid()
|
||||
const { ports: _ports, ...templateRow } = deviceTemplateToRow(template)
|
||||
const { error } = await supabase.from('device_templates').insert({ id, ...templateRow, is_public: false, owner_id: ownerId })
|
||||
const { error } = await supabase.from('device_templates').insert({ id, ...templateRow, is_public: isPublic, owner_id: ownerId })
|
||||
if (error) {
|
||||
console.error('Failed to add device template to Supabase', error)
|
||||
return { ...template, id, custom: true }
|
||||
return { ...template, id, custom: !isPublic }
|
||||
}
|
||||
const ports = template.ports.map((port, index) => ({ ...port, id: uuid(), sortOrder: index }))
|
||||
if (ports.length > 0) {
|
||||
@@ -216,18 +319,27 @@ export class SupabaseCatalogRepository implements CatalogRepository {
|
||||
direction: port.direction,
|
||||
port_type_id: port.portTypeId,
|
||||
sort_order: port.sortOrder,
|
||||
built_in_cable: port.builtInCable ?? false,
|
||||
})),
|
||||
)
|
||||
if (portsError) console.error('Failed to add device template ports to Supabase', portsError)
|
||||
}
|
||||
return { ...template, id, ports, custom: true }
|
||||
return { ...template, id, ports, custom: !isPublic }
|
||||
}
|
||||
|
||||
async addDeviceTemplate(template: Omit<DeviceTemplate, 'id' | 'custom'>): Promise<DeviceTemplate> {
|
||||
return this.insertDeviceTemplate(template, false, await this.currentUserId())
|
||||
}
|
||||
|
||||
adminAddDeviceTemplate(template: Omit<DeviceTemplate, 'id' | 'custom'>): Promise<DeviceTemplate> {
|
||||
return this.insertDeviceTemplate(template, true, null)
|
||||
}
|
||||
|
||||
async updateDeviceTemplate(id: string, patch: Partial<Omit<DeviceTemplate, 'id' | 'custom'>>): Promise<void> {
|
||||
const payload: Record<string, unknown> = {}
|
||||
if (patch.name !== undefined) payload.name = patch.name
|
||||
if (patch.category !== undefined) payload.category_id = patch.category
|
||||
if (patch.manufacturer !== undefined) payload.manufacturer = patch.manufacturer
|
||||
if (patch.manufacturerId !== undefined) payload.manufacturer_id = patch.manufacturerId
|
||||
if (patch.model !== undefined) payload.model = patch.model
|
||||
if (patch.cost !== undefined) payload.cost = patch.cost
|
||||
if (Object.keys(payload).length > 0) {
|
||||
@@ -250,6 +362,7 @@ export class SupabaseCatalogRepository implements CatalogRepository {
|
||||
direction: port.direction,
|
||||
port_type_id: port.portTypeId,
|
||||
sort_order: index,
|
||||
built_in_cable: port.builtInCable ?? false,
|
||||
})),
|
||||
)
|
||||
if (insertError) console.error('Failed to replace device template ports in Supabase', insertError)
|
||||
@@ -280,9 +393,41 @@ export class SupabaseCatalogRepository implements CatalogRepository {
|
||||
return this.updateDeviceTemplate(id, patch)
|
||||
}
|
||||
|
||||
adminUpdateDeviceCategory(id: string, name: string): Promise<void> {
|
||||
return this.updateDeviceCategory(id, name)
|
||||
}
|
||||
|
||||
adminUpdateManufacturer(id: string, name: string): Promise<void> {
|
||||
return this.updateManufacturer(id, name)
|
||||
}
|
||||
|
||||
async adminUnpublish(entityType: CatalogEntityType, id: string): Promise<void> {
|
||||
const table = CATALOG_TABLE_BY_ENTITY_TYPE[entityType]
|
||||
const { error } = await supabase.from(table).update({ is_public: false }).eq('id', id)
|
||||
if (error) console.error('Failed to unpublish catalog entry in Supabase', error)
|
||||
}
|
||||
|
||||
adminRemoveDeviceCategory(id: string): Promise<void> {
|
||||
return this.removeDeviceCategory(id)
|
||||
}
|
||||
|
||||
adminRemoveManufacturer(id: string): Promise<void> {
|
||||
return this.removeManufacturer(id)
|
||||
}
|
||||
|
||||
adminRemovePortType(id: string): Promise<void> {
|
||||
return this.removePortType(id)
|
||||
}
|
||||
|
||||
adminRemoveCableType(id: string): Promise<void> {
|
||||
return this.removeCableType(id)
|
||||
}
|
||||
|
||||
async adminRemoveDeviceTemplate(id: string): Promise<void> {
|
||||
const { error } = await supabase.from('device_templates').delete().eq('id', id)
|
||||
if (error) {
|
||||
console.error('Failed to remove device template from Supabase', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user