Files
mileograph_flutter/lib/components/dashboard/latest_loco_changes_panel.dart
Pete Gregory b1a8f7baf4
All checks were successful
Release / meta (push) Successful in 8s
Release / linux-build (push) Successful in 7m15s
Release / android-build (push) Successful in 18m37s
Release / release-master (push) Successful in 23s
Release / release-dev (push) Successful in 26s
dashboard overhaul
2025-12-22 19:39:50 +00:00

124 lines
4.1 KiB
Dart

import 'package:flutter/material.dart';
import 'package:mileograph_flutter/services/data_service.dart';
import 'package:provider/provider.dart';
class LatestLocoChangesPanel extends StatefulWidget {
const LatestLocoChangesPanel({super.key});
@override
State<LatestLocoChangesPanel> createState() => _LatestLocoChangesPanelState();
}
class _LatestLocoChangesPanelState extends State<LatestLocoChangesPanel> {
late final ScrollController _controller;
@override
void initState() {
super.initState();
_controller = ScrollController();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final data = context.watch<DataService>();
final changes = data.latestLocoChanges;
final isLoading = data.isLatestLocoChangesLoading;
final textTheme = Theme.of(context).textTheme;
return Card(
clipBehavior: Clip.antiAlias,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
const Icon(Icons.bolt, size: 20),
const SizedBox(width: 8),
Expanded(
child: Text(
'Latest loco changes',
style: textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w800,
),
),
),
if (isLoading && changes.isNotEmpty)
const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(strokeWidth: 2),
),
],
),
const SizedBox(height: 8),
if (isLoading && changes.isEmpty)
const Padding(
padding: EdgeInsets.all(12.0),
child: Center(child: CircularProgressIndicator()),
)
else if (changes.isEmpty)
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text(
'No recent loco changes yet.',
style: textTheme.bodyMedium,
),
)
else
SizedBox(
height: 260,
child: Scrollbar(
controller: _controller,
child: ListView.separated(
controller: _controller,
itemCount: changes.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, index) {
final change = changes[index];
return ListTile(
dense: true,
contentPadding: EdgeInsets.zero,
title: Text(
change.locoLabel,
style: textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w600,
),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${change.changeLabel}: ${change.valueLabel}'),
Text(
change.approvedDateLabel,
style: textTheme.labelSmall?.copyWith(
color: textTheme.bodySmall?.color?.withValues(alpha: 0.7),
),
),
],
),
trailing: change.approvedBy.isEmpty
? null
: Text(
change.approvedBy,
style: textTheme.labelSmall,
),
);
},
),
),
),
],
),
),
);
}
}