part of 'data_service.dart'; extension DataServiceTraction on DataService { Future fetchHadTraction({int offset = 0, int limit = 100}) async { await fetchTraction( hadOnly: true, offset: offset, limit: limit, append: offset > 0, ); } Future fetchTraction({ bool hadOnly = false, int offset = 0, int limit = 50, String? locoClass, String? locoNumber, bool mileageFirst = true, bool append = false, Map? filters, }) async { _isTractionLoading = true; try { final params = StringBuffer('?limit=$limit&offset=$offset'); if (hadOnly) params.write('&had_only=true'); if (!mileageFirst) params.write('&mileage_first=false'); final payload = {}; if (locoClass != null && locoClass.isNotEmpty) { payload['class'] = locoClass; } if (locoNumber != null && locoNumber.isNotEmpty) { payload['number'] = locoNumber; } if (filters != null) { filters.forEach((key, value) { if (value == null) return; if (value is String && value.trim().isEmpty) return; payload[key] = value; }); } final json = await api.post( '/locos/search/v2${params.toString()}', payload.isEmpty ? null : payload, ); if (json is List) { final newItems = json.map((e) => LocoSummary.fromJson(e)).toList(); _traction = append ? [..._traction, ...newItems] : newItems; _tractionHasMore = newItems.length >= limit - 1; } else { throw Exception('Unexpected traction response: $json'); } } catch (e) { debugPrint('Failed to fetch traction: $e'); if (!append) { _traction = []; } _tractionHasMore = false; } finally { _isTractionLoading = false; _notifyAsync(); } } Future> fetchLocoTimeline(int locoId) async { _isLocoTimelineLoading[locoId] = true; _notifyAsync(); try { final json = await api.get('/loco/get-timeline/$locoId'); final timeline = LocoAttrVersion.fromGroupedJson(json); _locoTimelines[locoId] = timeline; return timeline; } catch (e) { debugPrint('Failed to fetch loco timeline for $locoId: $e'); _locoTimelines[locoId] = []; return []; } finally { _isLocoTimelineLoading[locoId] = false; _notifyAsync(); } } Future createLoco(Map payload) async { try { final response = await api.put('/loco/new', payload); final locoClass = payload['class']?.toString(); if (locoClass != null && locoClass.isNotEmpty && !_locoClasses.contains(locoClass)) { _locoClasses = [..._locoClasses, locoClass]; } _notifyAsync(); return response; } catch (e) { debugPrint('Failed to create loco: $e'); rethrow; } } Future> fetchClassList() async { if (_locoClasses.isNotEmpty) return _locoClasses; try { final json = await api.get('/loco/classlist'); if (json is List) { _locoClasses = json.map((e) => e.toString()).toList(); _notifyAsync(); } } catch (e) { debugPrint('Failed to fetch class list: $e'); } return _locoClasses; } }