import { useRef, useState } from 'react' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog' import { Button } from '@/components/ui/button' import { Label } from '@/components/ui/label' import { Input } from '@/components/ui/input' import { DESIGN_ICONS, DEFAULT_DESIGN_ICON, resolveDesignIcon } from '@/utils/designIcons' import type { Design, FloorMapConfig } from '@/types' export interface DesignFormData { name: string icon: string /** * Floor plan for THIS canvas. Only present when the floor-plan section is * shown (edit mode on the active canvas). `null` means "remove the floor * plan"; `undefined` means "leave it untouched". */ floorMap?: FloorMapConfig | null /** * When set, create the new canvas by deep-copying this existing design instead * of starting blank. Only offered in create mode with `sourceDesigns` present. */ sourceId?: string } interface DesignModalProps { open: boolean onClose: () => void onSubmit: (data: DesignFormData) => void initial?: DesignFormData title?: string submitLabel?: string /** Show the floor-plan section (only meaningful when editing the active canvas). */ showFloorMap?: boolean /** Current floor plan of the canvas being edited (position preserved on save). */ initialFloorMap?: FloorMapConfig | null /** * Upload a selected image and resolve to its server URL. Required whenever * the floor-plan section is shown — images are stored server-side, never as * base64. Rejects on failure (caller surfaces the error). */ onUploadImage?: (file: File) => Promise /** * Existing designs offered as a copy source (create mode only). When non-empty, * a "Copy from existing" option appears; choosing it clones the picked canvas. */ sourceDesigns?: Design[] } export function DesignModal({ open, onClose, onSubmit, initial, title = 'New Canvas', submitLabel = 'Create', showFloorMap = false, initialFloorMap = null, onUploadImage, sourceDesigns = [], }: DesignModalProps) { const [name, setName] = useState(initial?.name ?? '') const [icon, setIcon] = useState(initial?.icon ?? DEFAULT_DESIGN_ICON) // "Copy from existing" is create-mode only (no floor-plan section shown). const canCopy = !showFloorMap && sourceDesigns.length > 0 const [fromExisting, setFromExisting] = useState(false) const [sourceId, setSourceId] = useState(sourceDesigns[0]?.id ?? '') // Floor plan state (only used when showFloorMap) const [imageData, setImageData] = useState(initialFloorMap?.imageData ?? '') const [width, setWidth] = useState(initialFloorMap?.width ?? 800) const [height, setHeight] = useState(initialFloorMap?.height ?? 600) const [opacity, setOpacity] = useState(initialFloorMap?.opacity ?? 0.8) const [locked, setLocked] = useState(initialFloorMap?.locked ?? false) const [enabled, setEnabled] = useState(initialFloorMap?.enabled ?? true) const [uploading, setUploading] = useState(false) const fileRef = useRef(null) const handleFile = async (e: React.ChangeEvent) => { const file = e.target.files?.[0] e.target.value = '' // allow re-selecting the same file after a failure if (!file || !onUploadImage) return setUploading(true) try { const url = await onUploadImage(file) setImageData(url) // Read natural dimensions from the served image (non-blocking). const img = new Image() img.onload = () => { setWidth(img.naturalWidth) setHeight(img.naturalHeight) } img.src = url } catch { // Caller surfaces the error toast; leave existing state untouched. } finally { setUploading(false) } } const handleSubmit = () => { const trimmed = name.trim() if (!trimmed) return if (canCopy && fromExisting && !sourceId) return const data: DesignFormData = { name: trimmed, icon } if (canCopy && fromExisting && sourceId) { data.sourceId = sourceId } if (showFloorMap) { data.floorMap = imageData ? { imageData, // Preserve position from the existing config; new plans start at 0,0. posX: initialFloorMap?.posX ?? 0, posY: initialFloorMap?.posY ?? 0, width, height, opacity, locked, enabled, } : null } onSubmit(data) } const hasImage = !!imageData return ( !o && onClose()}> {title}
setName(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') handleSubmit() }} placeholder="e.g. Home Network, Rack Power" autoFocus />
{DESIGN_ICONS.map((entry) => { const Icon = entry.icon const selected = entry.key === icon return ( ) })}
{canCopy && (
{fromExisting && (
{sourceDesigns.map((d) => { const Icon = resolveDesignIcon(d.icon) const selected = d.id === sourceId return ( ) })}
)}
)} {showFloorMap && (
{!hasImage ? (
fileRef.current?.click()} > {uploading ? 'Uploading…' : 'Click to select a floor plan image'} PNG, JPEG or WebP · max 10 MB
) : ( <>
Floor plan preview
setWidth(Math.max(80, Number(e.target.value)))} className="bg-[#21262d] border-[#30363d] text-xs h-8" />
setHeight(Math.max(80, Number(e.target.value)))} className="bg-[#21262d] border-[#30363d] text-xs h-8" />
setOpacity(Number(e.target.value))} className="w-full accent-[#00d4ff]" />
)}
)}
) }