Wire the app to the shared Supabase catalog (public catalog read integration)
Moves port/cable/category/device-template data from per-diagram embedded storage to the global public/private catalog backed by Supabase, per organized-ideas.md's "live reference, not snapshot" decision. - domain/types.ts, project.ts, compatibility.ts, bom.ts: lookup functions now take an explicit Catalog parameter instead of deriving data from Project — Project is reduced to just diagram-scoped fields. - New CatalogRepository/SupabaseCatalogRepository (mirrors the DiagramRepository pattern) and catalogStore.ts, replacing the catalog-related actions that used to live in projectStore. - device_templates gets a plain-text manufacturer column for now (the normalized manufacturer catalog from organized-ideas.md §3 is its own future pass, not blocking this one). - domain/library.ts is no longer imported by the app — it's now only the source scripts/generate-seed.mjs reads to produce supabase/seed.sql. - Swept every UI call site via tsc -b until clean; oxlint clean; 23/23 pgTAP RLS tests still passing after a `supabase db reset`.
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
import { create } from 'zustand'
|
||||
import type { CableType, Catalog, DeviceTemplate, PortType } from '../domain/types'
|
||||
import type { CatalogRepository } from '../data/CatalogRepository'
|
||||
import { SupabaseCatalogRepository } from '../data/SupabaseCatalogRepository'
|
||||
|
||||
const repository: CatalogRepository = new SupabaseCatalogRepository()
|
||||
|
||||
// Which public device templates you've hidden from your own palette — a
|
||||
// per-user UI preference, not catalog data, so it's not a Supabase row: hiding
|
||||
// a public entry only affects your view of it (see domain/project.ts's
|
||||
// allDeviceTemplates/hiddenDeviceTemplates), never the shared entry itself.
|
||||
const HIDDEN_PUBLIC_TEMPLATES_KEY = 'av-planner:hiddenPublicDeviceTemplateIds'
|
||||
function loadHiddenPublicIds(): Set<string> {
|
||||
try {
|
||||
const raw = window.localStorage.getItem(HIDDEN_PUBLIC_TEMPLATES_KEY)
|
||||
return raw ? new Set(JSON.parse(raw)) : new Set()
|
||||
} catch {
|
||||
return new Set()
|
||||
}
|
||||
}
|
||||
function saveHiddenPublicIds(ids: Set<string>): void {
|
||||
try {
|
||||
window.localStorage.setItem(HIDDEN_PUBLIC_TEMPLATES_KEY, JSON.stringify([...ids]))
|
||||
} catch {
|
||||
// Not essential — worst case a hidden template reappears next launch.
|
||||
}
|
||||
}
|
||||
|
||||
const emptyCatalog: Catalog = { portTypes: [], cableTypes: [], deviceCategories: [], deviceTemplates: [] }
|
||||
|
||||
interface CatalogStoreState {
|
||||
catalog: Catalog
|
||||
isLoaded: boolean
|
||||
hiddenPublicDeviceTemplateIds: Set<string>
|
||||
|
||||
loadCatalog: () => Promise<void>
|
||||
|
||||
addCustomDeviceCategory: (name: string) => Promise<string>
|
||||
|
||||
addCustomPortType: (portType: Omit<PortType, 'id' | 'custom'>) => Promise<void>
|
||||
updateCustomPortType: (id: string, patch: Partial<Omit<PortType, 'id' | 'custom'>>) => Promise<void>
|
||||
|
||||
addCustomCableType: (cableType: Omit<CableType, 'id' | 'custom'>) => Promise<void>
|
||||
updateCustomCableType: (id: string, patch: Partial<Omit<CableType, 'id' | 'custom'>>) => Promise<void>
|
||||
|
||||
addCustomDeviceTemplate: (template: Omit<DeviceTemplate, 'id' | 'custom'>) => Promise<void>
|
||||
updateCustomDeviceTemplate: (id: string, patch: Partial<Omit<DeviceTemplate, 'id' | 'custom'>>) => Promise<void>
|
||||
removeCustomDeviceTemplate: (id: string) => Promise<void>
|
||||
|
||||
hidePublicDeviceTemplate: (id: string) => void
|
||||
restorePublicDeviceTemplate: (id: string) => void
|
||||
}
|
||||
|
||||
export const useCatalogStore = create<CatalogStoreState>((set, get) => ({
|
||||
catalog: emptyCatalog,
|
||||
isLoaded: false,
|
||||
hiddenPublicDeviceTemplateIds: loadHiddenPublicIds(),
|
||||
|
||||
loadCatalog: async () => {
|
||||
const catalog = await repository.load()
|
||||
set({ catalog, isLoaded: true })
|
||||
},
|
||||
|
||||
addCustomDeviceCategory: async (name) => {
|
||||
const category = await repository.addDeviceCategory(name)
|
||||
set((state) => ({ catalog: { ...state.catalog, deviceCategories: [...state.catalog.deviceCategories, category] } }))
|
||||
return category.id
|
||||
},
|
||||
|
||||
addCustomPortType: async (portType) => {
|
||||
const created = await repository.addPortType(portType)
|
||||
set((state) => ({ catalog: { ...state.catalog, portTypes: [...state.catalog.portTypes, created] } }))
|
||||
},
|
||||
|
||||
updateCustomPortType: async (id, patch) => {
|
||||
await repository.updatePortType(id, patch)
|
||||
set((state) => ({
|
||||
catalog: {
|
||||
...state.catalog,
|
||||
portTypes: state.catalog.portTypes.map((pt) => (pt.id === id ? { ...pt, ...patch } : pt)),
|
||||
},
|
||||
}))
|
||||
},
|
||||
|
||||
addCustomCableType: async (cableType) => {
|
||||
const created = await repository.addCableType(cableType)
|
||||
set((state) => ({ catalog: { ...state.catalog, cableTypes: [...state.catalog.cableTypes, created] } }))
|
||||
},
|
||||
|
||||
updateCustomCableType: async (id, patch) => {
|
||||
await repository.updateCableType(id, patch)
|
||||
set((state) => ({
|
||||
catalog: {
|
||||
...state.catalog,
|
||||
cableTypes: state.catalog.cableTypes.map((ct) => (ct.id === id ? { ...ct, ...patch } : ct)),
|
||||
},
|
||||
}))
|
||||
},
|
||||
|
||||
addCustomDeviceTemplate: async (template) => {
|
||||
const created = await repository.addDeviceTemplate(template)
|
||||
set((state) => ({ catalog: { ...state.catalog, deviceTemplates: [...state.catalog.deviceTemplates, created] } }))
|
||||
},
|
||||
|
||||
updateCustomDeviceTemplate: async (id, patch) => {
|
||||
await repository.updateDeviceTemplate(id, patch)
|
||||
set((state) => ({
|
||||
catalog: {
|
||||
...state.catalog,
|
||||
deviceTemplates: state.catalog.deviceTemplates.map((t) => (t.id === id ? { ...t, ...patch } : t)),
|
||||
},
|
||||
}))
|
||||
},
|
||||
|
||||
removeCustomDeviceTemplate: async (id) => {
|
||||
await repository.removeDeviceTemplate(id)
|
||||
set((state) => ({
|
||||
catalog: { ...state.catalog, deviceTemplates: state.catalog.deviceTemplates.filter((t) => t.id !== id) },
|
||||
}))
|
||||
},
|
||||
|
||||
hidePublicDeviceTemplate: (id) => {
|
||||
const next = new Set(get().hiddenPublicDeviceTemplateIds)
|
||||
next.add(id)
|
||||
saveHiddenPublicIds(next)
|
||||
set({ hiddenPublicDeviceTemplateIds: next })
|
||||
},
|
||||
|
||||
restorePublicDeviceTemplate: (id) => {
|
||||
const next = new Set(get().hiddenPublicDeviceTemplateIds)
|
||||
next.delete(id)
|
||||
saveHiddenPublicIds(next)
|
||||
set({ hiddenPublicDeviceTemplateIds: next })
|
||||
},
|
||||
}))
|
||||
Reference in New Issue
Block a user