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:
@@ -0,0 +1,100 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import { allManufacturers } from '../../domain/diagram'
|
||||
import { useCatalogStore } from '../../state/catalogStore'
|
||||
|
||||
const NEW_MANUFACTURER_VALUE = '__new__'
|
||||
const NONE_VALUE = ''
|
||||
|
||||
/** A manufacturer <select> mirroring CategorySelect's "+ New…" inline-create
|
||||
* pattern, but optional — unlike a device category, not every device has a
|
||||
* known manufacturer (DIY/generic gear), so this always offers a blank
|
||||
* "(none)" option alongside the catalog and the create option. */
|
||||
export default function ManufacturerSelect({
|
||||
value,
|
||||
onChange,
|
||||
className = 'mt-1 w-full rounded border border-slate-300 px-2 py-1 text-sm',
|
||||
publishPublic = false,
|
||||
}: {
|
||||
value: string | undefined
|
||||
onChange: (manufacturerId: string | undefined) => void
|
||||
className?: string
|
||||
/** See CategorySelect's own doc — same cascade for the same reason. */
|
||||
publishPublic?: boolean
|
||||
}) {
|
||||
const catalog = useCatalogStore((s) => s.catalog)
|
||||
const addCustomManufacturer = useCatalogStore((s) => s.addCustomManufacturer)
|
||||
const adminAddManufacturer = useCatalogStore((s) => s.adminAddManufacturer)
|
||||
const [creating, setCreating] = useState(false)
|
||||
const [draftName, setDraftName] = useState('')
|
||||
|
||||
const manufacturers = useMemo(
|
||||
() => [...allManufacturers(catalog)].sort((a, b) => a.name.localeCompare(b.name)),
|
||||
[catalog],
|
||||
)
|
||||
|
||||
const commitNewManufacturer = async () => {
|
||||
const trimmed = draftName.trim()
|
||||
if (trimmed) {
|
||||
onChange(await (publishPublic ? adminAddManufacturer(trimmed) : addCustomManufacturer(trimmed)))
|
||||
}
|
||||
setCreating(false)
|
||||
setDraftName('')
|
||||
}
|
||||
|
||||
if (creating) {
|
||||
return (
|
||||
<div className="mt-1 flex gap-1.5">
|
||||
<input
|
||||
autoFocus
|
||||
value={draftName}
|
||||
onChange={(e) => setDraftName(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault()
|
||||
commitNewManufacturer()
|
||||
} else if (e.key === 'Escape') {
|
||||
setCreating(false)
|
||||
setDraftName('')
|
||||
}
|
||||
}}
|
||||
placeholder="New manufacturer name"
|
||||
className="w-full min-w-0 flex-1 rounded border border-slate-300 px-2 py-1 text-sm"
|
||||
/>
|
||||
<button
|
||||
onClick={commitNewManufacturer}
|
||||
className="shrink-0 rounded bg-indigo-600 px-2 text-xs font-medium text-white hover:bg-indigo-500"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setCreating(false)
|
||||
setDraftName('')
|
||||
}}
|
||||
className="shrink-0 rounded px-2 text-xs text-slate-500 hover:bg-slate-100"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<select
|
||||
value={value ?? NONE_VALUE}
|
||||
onChange={(e) => {
|
||||
if (e.target.value === NEW_MANUFACTURER_VALUE) setCreating(true)
|
||||
else onChange(e.target.value === NONE_VALUE ? undefined : e.target.value)
|
||||
}}
|
||||
className={className}
|
||||
>
|
||||
<option value={NONE_VALUE}>(none)</option>
|
||||
{manufacturers.map((m) => (
|
||||
<option key={m.id} value={m.id}>
|
||||
{m.name}
|
||||
</option>
|
||||
))}
|
||||
<option value={NEW_MANUFACTURER_VALUE}>+ New manufacturer…</option>
|
||||
</select>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user