Layout changes, fix bugs in new entry page

This commit is contained in:
2025-12-22 17:23:21 +00:00
parent 63b545c7a3
commit 45d543498f
20 changed files with 779 additions and 192 deletions

View File

@@ -1,11 +1,11 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
int _asInt(dynamic value, [int fallback = 0]) {
int _asInt(dynamic value, [int? fallback]) {
if (value is int) return value;
if (value is num) return value.toInt();
final parsed = int.tryParse(value?.toString() ?? '');
return parsed ?? fallback;
return parsed ?? fallback ?? 0;
}
double _asDouble(dynamic value, [double fallback = 0]) {
@@ -20,6 +20,15 @@ String _asString(dynamic value, [String fallback = '']) {
return (str == null) ? fallback : str;
}
bool _asBool(dynamic value, [bool fallback = false]) {
if (value is bool) return value;
if (value is num) return value != 0;
final lower = value?.toString().toLowerCase();
if (lower == 'true' || lower == 'yes' || lower == '1') return true;
if (lower == 'false' || lower == 'no' || lower == '0') return false;
return fallback;
}
DateTime _asDateTime(dynamic value, [DateTime? fallback]) {
if (value is DateTime) return value;
final parsed = DateTime.tryParse(value?.toString() ?? '');
@@ -67,6 +76,7 @@ class HomepageStats {
final List<LocoSummary> topLocos;
final List<LeaderboardEntry> leaderboard;
final List<TripSummary> trips;
final int legCount;
final UserData? user;
HomepageStats({
@@ -75,6 +85,7 @@ class HomepageStats {
required this.topLocos,
required this.leaderboard,
required this.trips,
required this.legCount,
this.user,
});
@@ -98,6 +109,10 @@ class HomepageStats {
trips: (json['trip_data'] as List? ?? [])
.map((e) => TripSummary.fromJson(e))
.toList(),
legCount: _asInt(
json['leg_count'],
(json['trip_legs'] as List?)?.length ?? 0,
),
user: userData == null
? null
: UserData(
@@ -126,6 +141,7 @@ class Loco {
final int id;
final String type, number, locoClass;
final String? name, operator, notes, evn;
final bool powering;
Loco({
required this.id,
@@ -136,6 +152,7 @@ class Loco {
required this.operator,
this.notes,
this.evn,
this.powering = true,
});
factory Loco.fromJson(Map<String, dynamic> json) => Loco(
@@ -147,6 +164,7 @@ class Loco {
operator: json['operator'],
notes: json['notes'],
evn: json['evn'],
powering: _asBool(json['alloc_powering'] ?? json['powering'], true),
);
}
@@ -179,6 +197,7 @@ class LocoSummary extends Loco {
this.livery,
this.location,
Map<String, dynamic>? extra,
bool powering = true,
}) : extra = extra ?? const {},
super(
id: locoId,
@@ -188,6 +207,7 @@ class LocoSummary extends Loco {
operator: locoOperator,
notes: locoNotes,
evn: locoEvn,
powering: powering,
);
factory LocoSummary.fromJson(Map<String, dynamic> json) => LocoSummary(
@@ -213,6 +233,7 @@ class LocoSummary extends Loco {
livery: json['livery'],
location: json['location'],
extra: Map<String, dynamic>.from(json),
powering: _asBool(json['alloc_powering'] ?? json['powering'], true),
);
}
@@ -353,6 +374,96 @@ class LocoAttrVersion {
}
}
class LocoChange {
final int locoId;
final String locoClass;
final String locoNumber;
final String locoName;
final String attrCode;
final String attrDisplay;
final String valueDisplay;
final DateTime? validFrom;
final DateTime? approvedAt;
final String approvedBy;
const LocoChange({
required this.locoId,
required this.locoClass,
required this.locoNumber,
required this.locoName,
required this.attrCode,
required this.attrDisplay,
required this.valueDisplay,
required this.validFrom,
required this.approvedAt,
required this.approvedBy,
});
factory LocoChange.fromJson(Map<String, dynamic> json) {
String _clean(dynamic value) {
final str = value?.toString().trim() ?? '';
if (str.isEmpty || str == '-' || str == '?') return '';
return str;
}
final valueLabel = json['value_norm'] ??
json['value_display'] ??
json['value_label'] ??
json['value_str'] ??
json['value_enum'] ??
json['value_norm'] ??
json['value'];
final approvedRaw = json['approved_at'] ?? json['approvedAt'];
final validFromRaw = json['valid_from'] ?? json['validFrom'];
return LocoChange(
locoId: _asInt(json['loco_id']),
locoClass: _clean(json['loco_class']),
locoNumber: _clean(json['loco_number']),
locoName: _clean(json['loco_name']),
attrCode: _asString(json['attr_code']),
attrDisplay: _clean(json['attr_display']),
valueDisplay: _clean(valueLabel),
validFrom: DateTime.tryParse(validFromRaw?.toString() ?? ''),
approvedAt: DateTime.tryParse(approvedRaw?.toString() ?? ''),
approvedBy: _clean(json['approved_by']),
);
}
String get locoLabel {
final parts = [locoClass, locoNumber]
.map((e) => e.trim())
.where((e) => e.isNotEmpty && e != '-')
.toList();
final label = parts.join(' ');
if (label.isEmpty) return locoName.isNotEmpty ? locoName : 'Loco $locoId';
return locoName.trim().isEmpty ? label : '$label${locoName.trim()}';
}
String get changeLabel =>
_cleanLabel(attrDisplay).isNotEmpty
? _cleanLabel(attrDisplay)
: _cleanLabel(attrCode).toUpperCase();
String get approvedDateLabel {
final date = approvedAt ?? validFrom;
if (date == null) return 'Pending date';
return DateFormat('yyyy-MM-dd').format(date);
}
String get valueLabel {
final value = _cleanLabel(valueDisplay);
if (value.isNotEmpty) return value;
return 'Unknown value';
}
String _cleanLabel(String raw) {
final trimmed = raw.trim();
if (trimmed.isEmpty) return '';
if (trimmed == '-' || trimmed == '?') return '';
return trimmed;
}
}
class LeaderboardEntry {
final String userId, username, userFullName;
final double mileage;