import { useState, useEffect } from 'react'; import { groupApi } from '../api/groups'; import { deviceApi } from '../api/devices'; import { Group, Device } from '../api/types'; export function GroupsPage() { const [groups, setGroups] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [showModal, setShowModal] = useState(false); const [editingGroup, setEditingGroup] = useState(null); const [selectedGroup, setSelectedGroup] = useState(null); useEffect(() => { loadGroups(); }, []); const loadGroups = async () => { try { setLoading(true); const data = await groupApi.getAll(); setGroups(data); setError(null); } catch (err) { setError('Failed to load groups'); } finally { setLoading(false); } }; const handleDelete = async (id: string) => { if (!confirm('Are you sure you want to delete this group?')) return; try { await groupApi.delete(id); loadGroups(); } catch (err) { alert('Failed to delete group. It may have active schedules.'); } }; const openCreateModal = () => { setEditingGroup(null); setShowModal(true); }; const openEditModal = (group: Group) => { setEditingGroup(group); setShowModal(true); }; if (loading) return
Loading groups...
; return (

Groups

{error &&
{error}
} {groups.map((group) => ( ))}
Name Devices Actions
{group.name} {group.devices.length} device(s)
{showModal && ( setShowModal(false)} onSave={() => { setShowModal(false); loadGroups(); }} /> )} {selectedGroup && ( setSelectedGroup(null)} /> )}
); } interface GroupModalProps { group: Group | null; onClose: () => void; onSave: () => void; } function GroupModal({ group, onClose, onSave }: GroupModalProps) { const [name, setName] = useState(group?.name || ''); const [devices, setDevices] = useState([]); const [selectedDeviceIds, setSelectedDeviceIds] = useState( group?.devices.map(d => d.id) || [] ); const [submitting, setSubmitting] = useState(false); useEffect(() => { loadDevices(); }, []); const loadDevices = async () => { const data = await deviceApi.getAll(); setDevices(data); }; const handleToggleDevice = (deviceId: string) => { setSelectedDeviceIds(prev => prev.includes(deviceId) ? prev.filter(id => id !== deviceId) : [...prev, deviceId] ); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (selectedDeviceIds.length === 0) { alert('Please select at least one device'); return; } try { setSubmitting(true); if (group) { await groupApi.update(group.id, { name, deviceIds: selectedDeviceIds }); } else { await groupApi.create({ name, deviceIds: selectedDeviceIds }); } onSave(); } catch (err) { alert('Failed to save group'); } finally { setSubmitting(false); } }; return (
e.stopPropagation()}>

{group ? 'Edit Group' : 'Create Group'}

setName(e.target.value)} required />
{devices.map(device => ( ))}
); } interface GroupControlModalProps { group: Group; onClose: () => void; } function GroupControlModal({ group, onClose }: GroupControlModalProps) { const [presetId, setPresetId] = useState(''); const [playlistPresets, setPlaylistPresets] = useState(''); const [playlistDur, setPlaylistDur] = useState(''); const [playlistTransition, setPlaylistTransition] = useState(''); const [playlistRepeat, setPlaylistRepeat] = useState('0'); const [playlistEnd, setPlaylistEnd] = useState(''); const handleApplyPreset = async () => { if (!presetId) { alert('Please enter a preset ID'); return; } try { const result = await groupApi.applyPreset(group.id, parseInt(presetId)); alert(`Success: ${result.results.success.length}, Failed: ${result.results.failed.length}`); } catch (err) { alert('Failed to apply preset'); } }; const handleApplyPlaylist = async () => { if (!playlistPresets) { alert('Please enter preset IDs'); return; } try { const ps = playlistPresets.split(',').map(s => parseInt(s.trim())); const dur = playlistDur ? playlistDur.split(',').map(s => parseInt(s.trim())) : undefined; const transition = playlistTransition ? playlistTransition.split(',').map(s => parseInt(s.trim())) : undefined; const result = await groupApi.applyPlaylist(group.id, { ps, dur: dur && dur.length === 1 ? dur[0] : dur, transition: transition && transition.length === 1 ? transition[0] : transition, repeat: parseInt(playlistRepeat), end: playlistEnd ? parseInt(playlistEnd) : undefined, }); alert(`Success: ${result.results.success.length}, Failed: ${result.results.failed.length}`); } catch (err) { alert('Failed to apply playlist'); } }; return (
e.stopPropagation()}>

Control Group: {group.name}

Apply Preset

setPresetId(e.target.value)} placeholder="e.g., 1" />

Apply Playlist

setPlaylistPresets(e.target.value)} placeholder="e.g., 1,2,3" />
setPlaylistDur(e.target.value)} placeholder="e.g., 30,30,30 or 30" />
setPlaylistTransition(e.target.value)} placeholder="e.g., 0" />
setPlaylistRepeat(e.target.value)} />
setPlaylistEnd(e.target.value)} placeholder="Optional" />
); }