Files
mileograph_flutter/lib/components/pages/traction/traction_pending_page.dart
Pete Gregory d5083e1cc7
All checks were successful
Release / meta (push) Successful in 6s
Release / linux-build (push) Successful in 57s
Release / web-build (push) Successful in 1m14s
Release / android-build (push) Successful in 5m33s
Release / release-master (push) Successful in 18s
Release / release-dev (push) Successful in 20s
add ability for non admins to add new traction, pending approval. Various QoL updates
2026-01-05 22:11:02 +00:00

141 lines
3.8 KiB
Dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:mileograph_flutter/components/traction/traction_card.dart';
import 'package:mileograph_flutter/objects/objects.dart';
import 'package:mileograph_flutter/services/api_service.dart';
import 'package:provider/provider.dart';
class TractionPendingPage extends StatefulWidget {
const TractionPendingPage({super.key});
@override
State<TractionPendingPage> createState() => _TractionPendingPageState();
}
class _TractionPendingPageState extends State<TractionPendingPage> {
bool _isLoading = false;
String? _error;
List<LocoSummary> _locos = const [];
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => _load());
}
Future<void> _load() async {
setState(() {
_isLoading = true;
_error = null;
});
try {
final api = context.read<ApiService>();
final params = '?limit=200&offset=0';
final json = await api.get('/loco/pending$params');
if (json is List) {
setState(() {
_locos = json
.whereType<Map>()
.map((e) => LocoSummary.fromJson(
e.map((k, v) => MapEntry(k.toString(), v)),
))
.toList();
});
} else {
setState(() {
_error = 'Unexpected response';
_locos = const [];
});
}
} catch (e) {
setState(() {
_error = e.toString();
_locos = const [];
});
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.of(context).maybePop(),
),
title: const Text('Pending traction'),
),
body: RefreshIndicator(
onRefresh: _load,
child: _buildBody(context),
),
);
}
Widget _buildBody(BuildContext context) {
if (_isLoading && _locos.isEmpty) {
return const Center(child: CircularProgressIndicator());
}
if (_error != null) {
return ListView(
padding: const EdgeInsets.all(16),
children: [
Text(
'Failed to load pending traction: $_error',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(color: Theme.of(context).colorScheme.error),
),
const SizedBox(height: 12),
FilledButton.icon(
onPressed: _load,
icon: const Icon(Icons.refresh),
label: const Text('Retry'),
),
],
);
}
if (_locos.isEmpty) {
return ListView(
padding: const EdgeInsets.all(16),
children: const [
Text('No pending traction found.'),
],
);
}
return ListView.builder(
padding: const EdgeInsets.all(12),
itemCount: _locos.length,
itemBuilder: (context, index) {
final loco = _locos[index];
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6.0),
child: TractionCard(
loco: loco,
selectionMode: false,
isSelected: false,
onShowInfo: () => showTractionDetails(
context,
loco,
onActionComplete: _load,
),
onOpenTimeline: () => context.push(
'/traction/${loco.id}/timeline',
extra: {'label': '${loco.locoClass} ${loco.number}'.trim()},
),
onOpenLegs: () => context.push('/traction/${loco.id}/legs'),
onActionComplete: _load,
),
);
},
);
}
}