12d527aad6
Make designs (canvases) fully user-manageable: create with a chosen name and icon, rename, change icon, and delete. Replaces the hardcoded "New Electrical Design" button with a generic "New Canvas" flow. - Add Design.icon column + migration that backfills legacy rows (electrical -> zap, others -> dashboard) - DesignModal: name input + curated lucide icon picker (create + edit) - Sidebar switcher gains per-canvas edit/delete; delete guards the last canvas and confirms - designStore: addDesign/updateDesign/removeDesign with active reassignment - Fix data loss on design switch: abort load when the save fails and keep unsaved edits; skip the save-old step when the previous canvas was deleted - designsApi create/update carry icon; design_type kept for back-compat Tests: backend design CRUD (icon + cascade + last-canvas guard), designStore actions, designIcons resolver, DesignModal create/edit/validation. ha-relevant: yes
73 lines
2.2 KiB
TypeScript
73 lines
2.2 KiB
TypeScript
import { create } from 'zustand'
|
|
import type { Design, DesignType } from '@/types'
|
|
|
|
interface DesignState {
|
|
designs: Design[]
|
|
activeDesignId: string | null
|
|
activeDesignType: DesignType | null
|
|
loaded: boolean
|
|
setDesigns: (designs: Design[]) => void
|
|
setActiveDesign: (id: string) => void
|
|
getActiveDesign: () => Design | null
|
|
/** Append a new design and make it active. */
|
|
addDesign: (design: Design) => void
|
|
/** Patch an existing design in place (name/icon edits). */
|
|
updateDesign: (id: string, patch: Partial<Pick<Design, 'name' | 'icon'>>) => void
|
|
/** Remove a design; if it was active, fall back to the first remaining one. */
|
|
removeDesign: (id: string) => void
|
|
}
|
|
|
|
export const useDesignStore = create<DesignState>((set, get) => ({
|
|
designs: [],
|
|
activeDesignId: null,
|
|
activeDesignType: null,
|
|
loaded: false,
|
|
|
|
setDesigns: (designs) =>
|
|
set((state) => {
|
|
const nextId = state.activeDesignId && designs.find((d) => d.id === state.activeDesignId)
|
|
? state.activeDesignId
|
|
: designs[0]?.id ?? null
|
|
const nextType = nextId ? designs.find((d) => d.id === nextId)?.design_type ?? null : null
|
|
return { designs, activeDesignId: nextId, activeDesignType: nextType, loaded: true }
|
|
}),
|
|
|
|
setActiveDesign: (id) =>
|
|
set((state) => {
|
|
const design = state.designs.find((d) => d.id === id)
|
|
return {
|
|
activeDesignId: id,
|
|
activeDesignType: design?.design_type ?? null,
|
|
}
|
|
}),
|
|
|
|
getActiveDesign: () => {
|
|
const { designs, activeDesignId } = get()
|
|
return designs.find((d) => d.id === activeDesignId) ?? null
|
|
},
|
|
|
|
addDesign: (design) =>
|
|
set((state) => ({
|
|
designs: [...state.designs, design],
|
|
activeDesignId: design.id,
|
|
activeDesignType: design.design_type,
|
|
})),
|
|
|
|
updateDesign: (id, patch) =>
|
|
set((state) => ({
|
|
designs: state.designs.map((d) => (d.id === id ? { ...d, ...patch } : d)),
|
|
})),
|
|
|
|
removeDesign: (id) =>
|
|
set((state) => {
|
|
const designs = state.designs.filter((d) => d.id !== id)
|
|
if (state.activeDesignId !== id) return { designs }
|
|
const next = designs[0] ?? null
|
|
return {
|
|
designs,
|
|
activeDesignId: next?.id ?? null,
|
|
activeDesignType: next?.design_type ?? null,
|
|
}
|
|
}),
|
|
}))
|