import { api } from '../api.js'; import { showToast } from '../components/toast.js'; import { t } from '../i18n.js'; const API = (url, opts = {}) => fetch('/api' + url, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${localStorage.getItem('token')}`, ...opts.headers }, ...opts }).then(r => r.json()); const HOURS = Array.from({ length: 24 }, (_, i) => i); function esc(str) { const d = document.createElement('div'); d.textContent = str; return d.innerHTML; } export async function render(container) { const [devices, content, groups, playlists, layoutsRaw] = await Promise.all([ api.getDevices(), api.getContent(), api.getGroups(), api.getPlaylists(), API('/layouts'), ]); const layouts = (Array.isArray(layoutsRaw) ? layoutsRaw : []).filter(l => !l.is_template); const today = new Date(); const weekStart = new Date(today); weekStart.setDate(today.getDate() - today.getDay()); weekStart.setHours(0, 0, 0, 0); const DAYS = [ t('schedule.day.sun'), t('schedule.day.mon'), t('schedule.day.tue'), t('schedule.day.wed'), t('schedule.day.thu'), t('schedule.day.fri'), t('schedule.day.sat'), ]; container.innerHTML = `
`; let currentWeekStart = new Date(weekStart); let editingId = null; const deviceRadio = document.getElementById('schedTargetDevice'); const groupRadio = document.getElementById('schedTargetGroup'); const deviceSelect = document.getElementById('schedDeviceSelect'); const groupSelect = document.getElementById('schedGroupSelect'); const noGroupsMsg = document.getElementById('schedNoGroups'); const zoneNote = document.getElementById('schedZoneNote'); function updateTargetVisibility() { const isGroup = groupRadio.checked; deviceSelect.style.display = isGroup ? 'none' : ''; groupSelect.style.display = isGroup ? '' : 'none'; if (noGroupsMsg) noGroupsMsg.style.display = (isGroup && groups.length === 0) ? '' : 'none'; zoneNote.style.display = isGroup ? '' : 'none'; } deviceRadio.addEventListener('change', updateTargetVisibility); groupRadio.addEventListener('change', updateTargetVisibility); function updateWeekLabel() { const end = new Date(currentWeekStart); end.setDate(end.getDate() + 6); document.getElementById('weekLabel').textContent = `${currentWeekStart.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })} - ${end.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}`; } async function loadCalendar() { const deviceId = document.getElementById('schedDevice').value; if (!deviceId) return; updateWeekLabel(); const events = await API(`/schedules/week?date=${currentWeekStart.toISOString()}&device_id=${deviceId}`); const cal = document.getElementById('calendar'); let html = '
'; for (let d = 0; d < 7; d++) { const date = new Date(currentWeekStart); date.setDate(date.getDate() + d); const isToday = date.toDateString() === new Date().toDateString(); html += `
${DAYS[d]}
${date.getDate()}
`; } for (const h of HOURS) { html += `
${h === 0 ? t('schedule.hour_12am') : h < 12 ? h + t('schedule.hour_am') : h === 12 ? t('schedule.hour_12pm') : (h - 12) + t('schedule.hour_pm')}
`; for (let d = 0; d < 7; d++) { html += `
`; } } cal.innerHTML = html; events.forEach(ev => { const start = new Date(ev.instance_start || ev.start_time); const end = new Date(ev.instance_end || ev.end_time); const dayIdx = start.getDay(); const startHour = start.getHours() + start.getMinutes() / 60; const endHour = end.getHours() + end.getMinutes() / 60; const duration = endHour - startHour; const cell = cal.querySelector(`[data-hour="${Math.floor(startHour)}"][data-day="${dayIdx}"]`); if (!cell) return; const isGroupSchedule = !!ev.group_id; const block = document.createElement('div'); const topOffset = (startHour - Math.floor(startHour)) * 28; block.style.cssText = `position:absolute;top:${topOffset}px;left:2px;right:2px;height:${Math.max(20, duration * 28)}px; background:${ev.color || '#3B82F6'};border-radius:3px;padding:2px 4px;font-size:10px;color:white;overflow:hidden;cursor:pointer;z-index:1;opacity:0.85; ${isGroupSchedule ? 'border:1.5px dashed rgba(255,255,255,0.6);' : ''}`; const label = ev.title || ev.playlist_name || ev.content_name || ev.widget_name || t('schedule.scheduled_label'); const prefix = isGroupSchedule ? `[${esc(ev.group_name || t('schedule.target_group'))}] ` : ''; block.textContent = prefix + label; block.title = `${isGroupSchedule ? t('schedule.tooltip_group_prefix') + (ev.group_name || '') + '\n' : ''}${start.toLocaleTimeString()} - ${end.toLocaleTimeString()}\n${t('schedule.tooltip_priority', { n: ev.priority })}`; block.onclick = () => editSchedule(ev); cell.appendChild(block); }); } function editSchedule(ev) { editingId = ev.id; document.getElementById('schedModalTitle').textContent = t('schedule.edit_schedule'); document.getElementById('schedPlaylist').value = ev.playlist_id || ''; document.getElementById('schedLayout').value = ev.layout_id || ''; document.getElementById('schedContent').value = ev.content_id || ''; document.getElementById('schedTitle').value = ev.title || ''; const start = new Date(ev.start_time); const end = new Date(ev.end_time); document.getElementById('schedStart').value = `${String(start.getHours()).padStart(2,'0')}:${String(start.getMinutes()).padStart(2,'0')}`; document.getElementById('schedEnd').value = `${String(end.getHours()).padStart(2,'0')}:${String(end.getMinutes()).padStart(2,'0')}`; document.getElementById('schedRepeat').value = ev.recurrence || ''; document.getElementById('schedPriority').value = ev.priority || 0; document.getElementById('schedColor').value = ev.color || '#3B82F6'; if (ev.group_id) { groupRadio.checked = true; groupSelect.value = ev.group_id; } else { deviceRadio.checked = true; deviceSelect.value = ev.device_id || document.getElementById('schedDevice').value; } updateTargetVisibility(); document.getElementById('deleteScheduleBtn').style.display = ''; document.getElementById('scheduleModal').style.display = 'flex'; } document.getElementById('addScheduleBtn').onclick = () => { editingId = null; document.getElementById('schedModalTitle').textContent = t('schedule.add_schedule'); document.getElementById('schedTitle').value = ''; document.getElementById('schedPlaylist').value = ''; document.getElementById('schedLayout').value = ''; document.getElementById('schedContent').value = ''; deviceRadio.checked = true; deviceSelect.value = document.getElementById('schedDevice').value; updateTargetVisibility(); document.getElementById('deleteScheduleBtn').style.display = 'none'; document.getElementById('scheduleModal').style.display = 'flex'; }; document.getElementById('deleteScheduleBtn').onclick = async () => { if (!editingId) return; if (!confirm(t('schedule.confirm_delete') || 'Delete this schedule?')) return; try { await API(`/schedules/${editingId}`, { method: 'DELETE' }); document.getElementById('scheduleModal').style.display = 'none'; showToast(t('schedule.toast.deleted') || 'Schedule deleted', 'success'); loadCalendar(); } catch (err) { showToast(err.message, 'error'); } }; document.getElementById('saveScheduleBtn').onclick = async () => { const isGroup = groupRadio.checked; const contentId = document.getElementById('schedContent').value; const startTime = document.getElementById('schedStart').value; const endTime = document.getElementById('schedEnd').value; if (isGroup && groups.length === 0) { showToast(t('schedule.toast.no_groups'), 'error'); return; } const playlistId = document.getElementById('schedPlaylist').value; const layoutId = document.getElementById('schedLayout').value; const today = new Date().toISOString().split('T')[0]; const data = { content_id: contentId || null, playlist_id: playlistId || null, layout_id: layoutId || null, title: document.getElementById('schedTitle').value, start_time: `${today}T${startTime}:00`, end_time: `${today}T${endTime}:00`, recurrence: document.getElementById('schedRepeat').value || null, priority: parseInt(document.getElementById('schedPriority').value) || 0, color: document.getElementById('schedColor').value, }; if (isGroup) { data.group_id = groupSelect.value; } else { data.device_id = deviceSelect.value; } try { if (editingId) { await API(`/schedules/${editingId}`, { method: 'PUT', body: JSON.stringify(data) }); } else { await API('/schedules', { method: 'POST', body: JSON.stringify(data) }); } document.getElementById('scheduleModal').style.display = 'none'; showToast(t('schedule.toast.saved'), 'success'); loadCalendar(); } catch (err) { showToast(err.message, 'error'); } }; document.getElementById('schedDevice').onchange = loadCalendar; document.getElementById('prevWeek').onclick = () => { currentWeekStart.setDate(currentWeekStart.getDate() - 7); loadCalendar(); }; document.getElementById('nextWeek').onclick = () => { currentWeekStart.setDate(currentWeekStart.getDate() + 7); loadCalendar(); }; loadCalendar(); } export function cleanup() {}