Use server-side date filtering for device event history chart

The device event history chart previously fetched all events and filtered
client-side. This wires the existing created_from API parameter to the
frontend so filtering happens in the backend.

Also fixes a bug where the Today filter returned no results: rusqlite was
storing datetimes with a space separator ("YYYY-MM-DD HH:MM:SS+00:00")
while SQL filters used RFC 3339 with a T separator, causing string
comparisons to fail for same-day events. All datetime storage across
device_events, devices, and notifications is now consistently RFC 3339.
A migration converts existing records.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
rzuasti
2026-05-28 10:51:19 -04:00
co-authored by Claude Sonnet 4.6
parent 2e8604a354
commit e1287e5a0f
9 changed files with 42 additions and 49 deletions
@@ -70,7 +70,7 @@ class DeviceEventHistory extends StatefulWidget {
}
class _DeviceEventHistoryState extends State<DeviceEventHistory> {
List<DeviceEvent>? _allEvents;
List<DeviceEvent>? _events;
bool _isLoading = true;
String? _error;
_TimeRange _selectedRange = _TimeRange.lastWeek;
@@ -89,10 +89,11 @@ class _DeviceEventHistoryState extends State<DeviceEventHistory> {
try {
final events = await BackendAPI.instance.getDeviceEvents(
widget.device.macAddress,
createdFrom: _selectedRange.cutoff,
);
if (!mounted) return;
setState(() {
_allEvents = events;
_events = events;
_isLoading = false;
});
} catch (e) {
@@ -104,12 +105,6 @@ class _DeviceEventHistoryState extends State<DeviceEventHistory> {
}
}
List<DeviceEvent> get _filteredEvents {
if (_allEvents == null) return [];
final cutoff = _selectedRange.cutoff;
return _allEvents!.where((e) => e.createdOn.isAfter(cutoff)).toList();
}
@override
Widget build(BuildContext context) {
if (_isLoading) {
@@ -126,7 +121,7 @@ class _DeviceEventHistoryState extends State<DeviceEventHistory> {
);
}
final events = _filteredEvents;
final events = _events ?? [];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -143,6 +138,7 @@ class _DeviceEventHistoryState extends State<DeviceEventHistory> {
selected: {_selectedRange},
onSelectionChanged: (selection) {
setState(() => _selectedRange = selection.first);
_loadEvents();
},
),
),
+12 -2
View File
@@ -137,9 +137,19 @@ class BackendAPI {
await _dio.delete('/devices/$macAddress');
}
Future<List<DeviceEvent>> getDeviceEvents(String macAddress) async {
Future<List<DeviceEvent>> getDeviceEvents(
String macAddress, {
DateTime? createdFrom,
}) async {
debugPrint('About to call GET /devices/$macAddress/events');
final response = await _dio.get('/devices/$macAddress/events');
final queryParams = <String, dynamic>{};
if (createdFrom != null) {
queryParams['created_from'] = createdFrom.toUtc().toIso8601String();
}
final response = await _dio.get(
'/devices/$macAddress/events',
queryParameters: queryParams.isEmpty ? null : queryParams,
);
debugPrint('Received: ${response.data}');
return (response.data as List)
.map((item) => DeviceEvent.fromJson(item as Map<String, dynamic>))