Per organized-ideas.md §8: the storage layer (DiagramRepository etc.) was already renamed in an earlier phase; this finishes it everywhere else. - domain/types.ts: Project -> Diagram. domain/project.ts -> domain/diagram.ts (createEmptyProject -> createEmptyDiagram, default name "Untitled Diagram"). - domain/compatibility.ts, domain/bom.ts: Project param/type -> Diagram. - data/exportImport.ts: ProjectImportError -> DiagramImportError, projectToJson/downloadProjectFile/readProjectFile/normalizeProject -> their Diagram equivalents. - state/projectStore.ts -> state/diagramStore.ts: useProjectStore -> useDiagramStore, the `project` field -> `diagram`, newProject/renameProject/ importProject/applyRestoredProject -> *Diagram, restoredProjectUpdatedAt -> restoredDiagramUpdatedAt. - Every component updated to match, compiler-guided (tsc -b enumerated each remaining call site after the core rename, the same approach used for the earlier catalog-parameter refactor). - README updated for the terminology, and to match the repository class names (which had already been renamed but the README hadn't caught up). No backend/schema changes: the JSON shape stored in diagrams.data never changed, only TypeScript-side identifiers, so existing diagrams are unaffected. One SQL comment fixed for accuracy (no migration needed). Verified: tsc -b and oxlint clean; grepped src/ for any remaining Project/project reference (none) after the sweep.
94 lines
2.7 KiB
TypeScript
94 lines
2.7 KiB
TypeScript
import { useMemo, useState } from 'react'
|
|
import { allDeviceCategories } from '../../domain/diagram'
|
|
import { useCatalogStore } from '../../state/catalogStore'
|
|
|
|
const NEW_CATEGORY_VALUE = '__new__'
|
|
|
|
/** A device-category <select> with a built-in "+ New category…" option that
|
|
* switches to a small inline text field, creates the category, and selects
|
|
* it — so adding a category never requires leaving the form you're in. */
|
|
export default function CategorySelect({
|
|
value,
|
|
onChange,
|
|
className = 'mt-1 w-full rounded border border-slate-300 px-2 py-1 text-sm',
|
|
}: {
|
|
value: string
|
|
onChange: (categoryId: string) => void
|
|
className?: string
|
|
}) {
|
|
const catalog = useCatalogStore((s) => s.catalog)
|
|
const addCustomDeviceCategory = useCatalogStore((s) => s.addCustomDeviceCategory)
|
|
const [creating, setCreating] = useState(false)
|
|
const [draftName, setDraftName] = useState('')
|
|
|
|
const categories = useMemo(
|
|
() => [...allDeviceCategories(catalog)].sort((a, b) => a.name.localeCompare(b.name)),
|
|
[catalog],
|
|
)
|
|
|
|
const commitNewCategory = async () => {
|
|
const trimmed = draftName.trim()
|
|
if (trimmed) {
|
|
onChange(await addCustomDeviceCategory(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()
|
|
commitNewCategory()
|
|
} else if (e.key === 'Escape') {
|
|
setCreating(false)
|
|
setDraftName('')
|
|
}
|
|
}}
|
|
placeholder="New category name"
|
|
className="w-full min-w-0 flex-1 rounded border border-slate-300 px-2 py-1 text-sm"
|
|
/>
|
|
<button
|
|
onClick={commitNewCategory}
|
|
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}
|
|
onChange={(e) => {
|
|
if (e.target.value === NEW_CATEGORY_VALUE) setCreating(true)
|
|
else onChange(e.target.value)
|
|
}}
|
|
className={className}
|
|
>
|
|
{categories.map((c) => (
|
|
<option key={c.id} value={c.id}>
|
|
{c.name}
|
|
</option>
|
|
))}
|
|
<option value={NEW_CATEGORY_VALUE}>+ New category…</option>
|
|
</select>
|
|
)
|
|
}
|