import React, { useState, useEffect } from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
TextField,
Button,
Box,
FormControl,
InputLabel,
Select,
MenuItem,
ToggleButton,
ToggleButtonGroup,
Typography,
Divider,
Slider,
Switch,
FormControlLabel,
CircularProgress,
IconButton,
Paper,
Chip
} from '@mui/material';
import { useI18n } from './I18nContext';
const DAYS_KEYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];
const OPERATORS = [
{ value: '>', label: '>' },
{ value: '<', label: '<' },
{ value: '>=', label: '≥' },
{ value: '<=', label: '≤' },
{ value: '==', label: '=' }
];
// Single sensor condition component
function SensorCondition({ condition, sensors, onChange, onRemove, disabled }) {
const selectedSensor = sensors.find(s => s.id === condition.sensor);
const isStateSensor = selectedSensor?.type === 'output-state';
// When sensor changes, reset operator to appropriate default
const handleSensorChange = (newSensorId) => {
const newSensor = sensors.find(s => s.id === newSensorId);
const newIsState = newSensor?.type === 'output-state';
onChange({
...condition,
sensor: newSensorId,
operator: newIsState ? '==' : '>',
value: newIsState ? 1 : (condition.value ?? 25)
});
};
return (
Sensor
{isStateSensor ? (
// State sensor: is on / is off
) : (
// Value sensor: numeric comparison
<>
onChange({ ...condition, value: Number(e.target.value) })}
sx={{ width: 80 }}
disabled={disabled}
/>
>
)}
{onRemove && (
❌
)}
);
}
export default function RuleEditor({ open, rule, onSave, onClose, sensors = [], outputs = [], colorTags: availableColorTags = [], saving }) {
const { t } = useI18n();
const [name, setName] = useState('');
const [selectedTags, setSelectedTags] = useState([]); // array of tag ids
// Scheduled time state (trigger at specific time)
const [useScheduledTime, setUseScheduledTime] = useState(false);
const [scheduledTime, setScheduledTime] = useState('08:00');
const [scheduledDays, setScheduledDays] = useState(['mon', 'tue', 'wed', 'thu', 'fri']);
// Time range state (active during window)
const [useTimeRange, setUseTimeRange] = useState(false);
const [timeStart, setTimeStart] = useState('08:00');
const [timeEnd, setTimeEnd] = useState('18:00');
const [timeRangeDays, setTimeRangeDays] = useState(['mon', 'tue', 'wed', 'thu', 'fri']);
// Sensor conditions state
const [useSensors, setUseSensors] = useState(false);
const [sensorConditions, setSensorConditions] = useState([{ sensor: '', operator: '>', value: 25 }]);
const [sensorLogic, setSensorLogic] = useState('and'); // 'and' or 'or'
// Action state
const [actionType, setActionType] = useState('toggle');
const [target, setTarget] = useState('');
const [toggleState, setToggleState] = useState(true);
const [outputLevel, setOutputLevel] = useState(5); // 1-10 for port outputs
const [duration, setDuration] = useState(15);
// Check if target is a binary (on/off) output or level (1-10) output
const selectedOutput = outputs.find(o => o.id === target);
const isBinaryOutput = selectedOutput?.type === 'plug' || selectedOutput?.type === 'virtual';
// Reset form when rule changes or dialog opens
useEffect(() => {
if (rule) {
setName(rule.name);
// colorTags can be array or single value for backwards compat
const tags = rule.colorTags || (rule.colorTag ? [rule.colorTag] : []);
setSelectedTags(Array.isArray(tags) ? tags : []);
// Parse trigger
const trigger = rule.trigger || {};
// Scheduled time
setUseScheduledTime(!!trigger.scheduledTime);
if (trigger.scheduledTime) {
setScheduledTime(trigger.scheduledTime.time || '08:00');
setScheduledDays(trigger.scheduledTime.days || []);
}
// Time range
setUseTimeRange(!!trigger.timeRange);
if (trigger.timeRange) {
setTimeStart(trigger.timeRange.start || '08:00');
setTimeEnd(trigger.timeRange.end || '18:00');
setTimeRangeDays(trigger.timeRange.days || []);
}
setUseSensors(!!trigger.sensors && trigger.sensors.length > 0);
if (trigger.sensors && trigger.sensors.length > 0) {
setSensorConditions(trigger.sensors);
setSensorLogic(trigger.sensorLogic || 'and');
}
// Parse action
setActionType(rule.action?.type || 'toggle');
setTarget(rule.action?.target || '');
if (rule.action?.type === 'toggle') {
setToggleState(rule.action?.state ?? true);
setOutputLevel(rule.action?.level ?? 5);
} else {
setDuration(rule.action?.duration || 15);
}
} else {
// Reset to defaults
setName('');
setSelectedTags([]);
setUseScheduledTime(true);
setScheduledTime('08:00');
setScheduledDays(['mon', 'tue', 'wed', 'thu', 'fri']);
setUseTimeRange(false);
setTimeStart('08:00');
setTimeEnd('18:00');
setTimeRangeDays(['mon', 'tue', 'wed', 'thu', 'fri']);
setUseSensors(false);
setSensorConditions([{ sensor: sensors[0]?.id || '', operator: '>', value: 25 }]);
setSensorLogic('and');
setActionType('toggle');
setTarget(outputs[0]?.id || '');
setToggleState(true);
setOutputLevel(5);
setDuration(15);
}
}, [rule, open, sensors, outputs]);
// Set default sensor/output when lists load
useEffect(() => {
if (sensorConditions[0]?.sensor === '' && sensors.length > 0) {
setSensorConditions([{ ...sensorConditions[0], sensor: sensors[0].id }]);
}
if (!target && outputs.length > 0) setTarget(outputs[0].id);
}, [sensors, outputs, sensorConditions, target]);
const handleScheduledDaysChange = (event, newDays) => {
if (newDays.length > 0) setScheduledDays(newDays);
};
const handleTimeRangeDaysChange = (event, newDays) => {
if (newDays.length > 0) setTimeRangeDays(newDays);
};
const addSensorCondition = () => {
setSensorConditions([...sensorConditions, { sensor: sensors[0]?.id || '', operator: '>', value: 25 }]);
};
const updateSensorCondition = (index, newCondition) => {
const updated = [...sensorConditions];
updated[index] = newCondition;
setSensorConditions(updated);
};
const removeSensorCondition = (index) => {
if (sensorConditions.length > 1) {
setSensorConditions(sensorConditions.filter((_, i) => i !== index));
}
};
const handleSave = () => {
const selectedOutput = outputs.find(o => o.id === target);
// Build trigger object
const trigger = {};
if (useScheduledTime) {
trigger.scheduledTime = { time: scheduledTime, days: scheduledDays };
}
if (useTimeRange) {
trigger.timeRange = { start: timeStart, end: timeEnd, days: timeRangeDays };
}
if (useSensors && sensorConditions.length > 0) {
trigger.sensors = sensorConditions.map(c => ({
...c,
sensorLabel: sensors.find(s => s.id === c.sensor)?.label
}));
trigger.sensorLogic = sensorLogic;
}
const isBinaryTarget = selectedOutput?.type === 'plug' || selectedOutput?.type === 'virtual';
// Build action object based on output type
let action;
if (actionType === 'toggle') {
action = {
type: 'toggle',
target,
targetLabel: selectedOutput?.label,
...(isBinaryTarget ? { state: toggleState } : { level: outputLevel })
};
} else {
action = {
type: 'keepOn',
target,
targetLabel: selectedOutput?.label,
duration,
...(isBinaryTarget ? {} : { level: outputLevel })
};
}
const ruleData = { name, trigger, action, colorTags: selectedTags };
onSave(ruleData);
};
const isValid = name.trim().length > 0 &&
(useScheduledTime || useTimeRange || useSensors) &&
(!useScheduledTime || scheduledDays.length > 0) &&
(!useTimeRange || timeRangeDays.length > 0) &&
(!useSensors || sensorConditions.every(c => c.sensor)) &&
target;
return (
);
}