// AutomationsPanel - Spatial automation builder UI
window.AutomationsPanel = (function() {
'use strict';
let automations = [];
let triggerVolumes = [];
let zones = [];
let people = [];
let editingId = null;
let testMode = false;
// Trigger type labels
const triggerLabels = {
'zone_enter': 'When someone enters a zone',
'zone_leave': 'When someone leaves a zone',
'zone_dwell': 'When someone stays in a zone for a while',
'zone_vacant': 'When a zone becomes empty',
'person_count_change': 'When occupant count changes',
'fall_detected': 'When a fall is detected',
'anomaly': 'When an anomaly is detected',
'ble_device_present': 'When a BLE device arrives',
'ble_device_absent': 'When a BLE device leaves',
'volume_enter': 'When someone enters a trigger volume',
'volume_leave': 'When someone leaves a trigger volume'
};
// Action type labels
const actionLabels = {
'webhook': 'HTTP Webhook',
'mqtt_publish': 'MQTT Publish',
'ntfy': 'Ntfy Notification',
'pushover': 'Pushover Notification'
};
// Initialize panel
function init() {
createPanelHTML();
loadInitialData();
setupEventListeners();
}
function createPanelHTML() {
const container = document.getElementById('automations-panel');
if (!container) return;
container.innerHTML = `
Automations
New Automation
1. Choose Trigger
2. Configure Trigger
3. Add Conditions (Optional)
4. Add Actions
5. Summary
Add Condition
Add Action
`;
}
function setupEventListeners() {
// New automation button
document.getElementById('new-automation-btn')?.addEventListener('click', () => openEditor());
// Modal controls
document.getElementById('modal-close')?.addEventListener('click', closeEditor);
document.getElementById('modal-cancel')?.addEventListener('click', closeEditor);
document.getElementById('modal-save')?.addEventListener('click', saveAutomation);
document.getElementById('test-fire-btn')?.addEventListener('click', testFire);
// Trigger type change
document.getElementById('trigger-type')?.addEventListener('change', onTriggerTypeChange);
// Condition modal
document.getElementById('add-condition-btn')?.addEventListener('click', openConditionModal);
document.getElementById('condition-modal-close')?.addEventListener('click', closeConditionModal);
document.getElementById('condition-cancel')?.addEventListener('click', closeConditionModal);
document.getElementById('condition-save')?.addEventListener('click', addCondition);
document.getElementById('condition-type')?.addEventListener('change', onConditionTypeChange);
// Action modal
document.getElementById('add-action-btn')?.addEventListener('click', openActionModal);
document.getElementById('action-modal-close')?.addEventListener('click', closeActionModal);
document.getElementById('action-cancel')?.addEventListener('click', closeActionModal);
document.getElementById('action-save')?.addEventListener('click', addAction);
document.getElementById('action-type')?.addEventListener('change', onActionTypeChange);
}
async function loadInitialData() {
try {
// Load automations
const autoRes = await fetch('/api/automations');
if (autoRes.ok) {
automations = await autoRes.json();
}
// Load zones
const zonesRes = await fetch('/api/zones');
if (zonesRes.ok) {
zones = await zonesRes.json();
}
// Load people
const peopleRes = await fetch('/api/people');
if (peopleRes.ok) {
people = await peopleRes.json();
}
// Load trigger volumes
const volumesRes = await fetch('/api/automations/volumes');
if (volumesRes.ok) {
triggerVolumes = await volumesRes.json();
}
renderAutomationsList();
} catch (err) {
console.error('Failed to load data:', err);
}
}
function renderAutomationsList() {
const list = document.getElementById('automations-list');
if (!list) return;
if (automations.length === 0) {
list.innerHTML = `
No automations configured yet.
Click "New Automation" to create your first automation.
`).join('');
}
function openConditionModal() {
document.getElementById('condition-type').value = 'person_filter';
onConditionTypeChange();
document.getElementById('condition-modal').style.display = 'flex';
}
function closeConditionModal() {
document.getElementById('condition-modal').style.display = 'none';
}
function onConditionTypeChange() {
const type = document.getElementById('condition-type').value;
const form = document.getElementById('condition-value-form');
if (!form) return;
switch (type) {
case 'person_filter':
form.innerHTML = `
`;
break;
case 'time_window':
form.innerHTML = `
Supports overnight ranges (e.g., 22:00-07:00)
`;
break;
case 'day_of_week':
form.innerHTML = `
`;
break;
case 'system_mode':
form.innerHTML = `
`;
break;
case 'zone_occupancy':
form.innerHTML = `
`;
break;
}
}
function addCondition() {
const type = document.getElementById('condition-type').value;
let value = '';
switch (type) {
case 'person_filter':
value = document.getElementById('condition-person').value;
break;
case 'time_window':
const start = document.getElementById('condition-time-start').value;
const end = document.getElementById('condition-time-end').value;
value = `${start}-${end}`;
break;
case 'day_of_week':
const days = [];
document.querySelectorAll('#condition-value-form input:checked').forEach(cb => {
days.push(cb.value);
});
value = days.join(',');
break;
case 'system_mode':
value = document.getElementById('condition-mode').value;
break;
case 'zone_occupancy':
const zone = document.getElementById('condition-occ-zone').value;
const op = document.getElementById('condition-occ-op').value;
const count = document.getElementById('condition-occ-count').value;
value = `${zone}:${op}:${count}`;
break;
}
// Add to current automation being edited
const conditionsList = document.getElementById('conditions-list');
const conditionItem = document.createElement('div');
conditionItem.className = 'condition-item';
conditionItem.innerHTML = `
${type}: ${escapeHtml(value)}
`;
conditionsList.appendChild(conditionItem);
closeConditionModal();
updateSummary();
}
function renderActions(actions) {
const list = document.getElementById('actions-list');
if (!list) return;
if (actions.length === 0) {
list.innerHTML = '