import { useState, useEffect } from 'react' import { useQuery } from '@tanstack/react-query' import { devicesApi } from '../api' import type { WidgetType, WidgetConfig } from '../hooks' import { createDefaultWidgetTitle } from '../utils/formatters' interface AddWidgetModalProps { isOpen: boolean onClose: () => void onAdd: (widget: WidgetConfig) => void } export default function AddWidgetModal({ isOpen, onClose, onAdd }: AddWidgetModalProps) { const [step, setStep] = useState(1) const [widgetType, setWidgetType] = useState('stat') const [title, setTitle] = useState('') const [selectedDevices, setSelectedDevices] = useState([]) const [selectedMetrics, setSelectedMetrics] = useState([]) const [timeframeHours, setTimeframeHours] = useState(24) const [widgetWidth, setWidgetWidth] = useState(1) const [widgetHeight, setWidgetHeight] = useState(3) const [city, setCity] = useState('Skopje') const [calendarUrl, setCalendarUrl] = useState('') const [showCalendarUrl, setShowCalendarUrl] = useState(false) const [calendarRangeHours, setCalendarRangeHours] = useState(72) const [briefingType, setBriefingType] = useState<'schedule' | 'environment' | 'full'>('full') // Fetch devices const { data: devicesData } = useQuery({ queryKey: ['devices'], queryFn: async () => { const response = await devicesApi.getAll() return response.data }, }) // Fetch metrics for selected device(s) const { data: deviceMetricsData } = useQuery({ queryKey: ['device-metrics', selectedDevices[0]], queryFn: async () => { if (selectedDevices.length === 0) return [] const response = await devicesApi.getMetrics(selectedDevices[0]) return response.data.metrics }, enabled: selectedDevices.length > 0, }) const devices = devicesData?.results || [] const availableMetrics = deviceMetricsData || [] // Validation helpers const needsDevice = !['weather', 'air-quality', 'calendar', 'daily-briefing'].includes(widgetType) const needsMetrics = !['weather', 'air-quality', 'run-suitability', 'health-stats', 'calendar', 'daily-briefing'].includes(widgetType) // Reset form when modal opens useEffect(() => { if (isOpen) { setStep(1) setWidgetType('stat') setTitle('') setSelectedDevices([]) setSelectedMetrics([]) setTimeframeHours(24) setWidgetWidth(1) setWidgetHeight(3) setCalendarUrl('') setShowCalendarUrl(false) setCalendarRangeHours(72) setBriefingType('full') } }, [isOpen]) // Reset metrics when device changes useEffect(() => { setSelectedMetrics([]) }, [selectedDevices]) const handleSubmit = () => { // Validation if (needsDevice && selectedDevices.length === 0) { alert('Please select a device') return } if (needsMetrics && selectedMetrics.length === 0) { alert('Please select at least one metric') return } const wellnessWidgets = ['run-suitability', 'health-stats'] if (wellnessWidgets.includes(widgetType) && !city) { alert('Please enter a city for wellness widgets') return } if (widgetType === 'calendar' && !calendarUrl.trim()) { alert('Please provide an iCal URL') return } if (widgetType === 'daily-briefing' && !city) { alert('Please enter a city for daily briefing') return } // Create title let defaultTitle = '' if (widgetType === 'weather') { defaultTitle = `Weather - ${city}` } else if (widgetType === 'air-quality') { defaultTitle = `Air Quality - ${city}` } else if (widgetType === 'run-suitability') { defaultTitle = `Run Suitability - ${city}` } else if (widgetType === 'health-stats') { const selectedDevice = devices.find(d => d.id === selectedDevices[0]) defaultTitle = `Health StaTts - ${selectedDevice?.name || 'Device'}` } else if (widgetType === 'calendar') { defaultTitle = 'Calendar Agenda' } else if (widgetType === 'daily-briefing') { const briefingLabels = { schedule: 'Schedule', environment: 'Environment', full: 'Full' } defaultTitle = `${briefingLabels[briefingType]} Briefing` } else { const selectedDevice = devices.find(d => d.id === selectedDevices[0]) defaultTitle = createDefaultWidgetTitle(widgetType, selectedDevice?.name, selectedMetrics) } const newWidget: WidgetConfig = { id: `widget-${Date.now()}`, type: widgetType, title: title || defaultTitle, deviceIds: needsDevice ? selectedDevices : [], metricIds: needsMetrics ? selectedMetrics : [], timeframe: { hours: timeframeHours, }, visualization: { showLegend: true, showGrid: true, height: widgetType === 'line-chart' ? 300 : undefined, city: (widgetType === 'weather' || widgetType === 'air-quality' || wellnessWidgets.includes(widgetType) || widgetType === 'daily-briefing') ? city : undefined, }, calendar: widgetType === 'calendar' ? { icalUrl: calendarUrl.trim(), timeRangeHours: calendarRangeHours, } : undefined, briefing: widgetType === 'daily-briefing' ? { briefingType, city, healthDeviceId: selectedDevices[0], calendarUrl: calendarUrl.trim() || undefined, calendarRangeHours: calendarRangeHours, } : undefined, position: { x: 0, y: 0, w: widgetWidth, h: ['run-suitability', 'health-stats', 'daily-briefing'].includes(widgetType) ? 2 : widgetHeight, }, } onAdd(newWidget) onClose() } const toggleDevice = (deviceId: string) => { // Only allow single device selection for better UX setSelectedDevices([deviceId]) } const toggleMetric = (metric: string) => { // Stat and gauge widgets only allow one metric const singleMetricWidgets = ['stat', 'gauge'] const maxMetrics = singleMetricWidgets.includes(widgetType) ? 1 : 5 setSelectedMetrics((prev) => { if (prev.includes(metric)) { return prev.filter((m) => m !== metric) } // If adding would exceed max, replace last or prevent if (prev.length >= maxMetrics) { if (maxMetrics === 1) { return [metric] // Replace for single-metric widgets } return prev // Don't add more for multi-metric widgets } return [...prev, metric] }) } if (!isOpen) return null return (

Add Widget

{/* Progress steps */}
  • = 1 ? 'step-primary' : ''}`}>Type
  • = 2 ? 'step-primary' : ''}`}>Data Source
  • = 3 ? 'step-primary' : ''}`}>Configure
{/* Step 1: Widget Type */} {step === 1 && (
)} {/* Step 2: Data Source */} {step === 2 && widgetType !== 'weather' && widgetType !== 'air-quality' && widgetType !== 'calendar' && widgetType !== 'daily-briefing' && (
{/* City input for wellness widgets */} {['run-suitability', 'health-stats'].includes(widgetType) && (
setCity(e.target.value)} />
)}
{devices.length === 0 ? (
No devices found
) : ( devices.map((device) => ( )) )}
{/* Metric Selection - Skip for widgets with hardcoded metrics */} {!['run-suitability', 'health-stats'].includes(widgetType) && (
{selectedDevices.length === 0 ? (
Please select a device first
) : ( <> {(['stat', 'gauge'].includes(widgetType)) && (
This widget type supports only one metric
)}
{availableMetrics.length === 0 ? (
No metrics found for this device
) : ( availableMetrics.map((metric) => ( )) )}
)}
)}
)} {/* Step 3: Configure */} {step === 3 && (
{/* City input for weather and air-quality widgets */} {(widgetType === 'weather' || widgetType === 'air-quality') ? ( <>
setCity(e.target.value)} />
setTitle(e.target.value)} />
) : widgetType === 'calendar' ? ( <>
setCalendarUrl(e.target.value)} />
setTitle(e.target.value)} />
) : widgetType === 'daily-briefing' ? ( <>
setCity(e.target.value)} />
{(briefingType === 'schedule' || briefingType === 'full') && ( <>
setCalendarUrl(e.target.value)} />
{calendarUrl && (
)} {devices.length > 0 && (
)} )}
setTitle(e.target.value)} />
) : ( // Original configuration for sensor-based widgets <>
setTitle(e.target.value)} />
)}
Widget Summary
Type: {widgetType}
Device: {needsDevice ? (devices.find(d => d.id === selectedDevices[0])?.name || 'Not selected') : 'Not required'}
Metrics: {needsMetrics ? (selectedMetrics.join(', ') || 'Not selected') : 'Not required'}
Size: {widgetWidth} × {widgetHeight}
{widgetType === 'calendar' && (
Range: Next {calendarRangeHours}h
)} {widgetType === 'daily-briefing' && (
Briefing: {briefingType.charAt(0).toUpperCase() + briefingType.slice(1)}
)}
)}
) }