first commit
This commit is contained in:
186
app/lib/presentation/widgets/counter_grid.dart
Normal file
186
app/lib/presentation/widgets/counter_grid.dart
Normal file
@@ -0,0 +1,186 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../domain/entities/counter.dart';
|
||||
|
||||
/// Zähler-Grid wie in Lua CreateLoadingStockStartView etc.
|
||||
class CounterGrid extends StatelessWidget {
|
||||
final List<CounterGroup> groups;
|
||||
final bool showDifferences;
|
||||
|
||||
const CounterGrid({
|
||||
super.key,
|
||||
required this.groups,
|
||||
this.showDifferences = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: groups.map((group) => _buildGroup(context, group)).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGroup(BuildContext context, CounterGroup group) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: _getGroupColor(group.title),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Titel
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Text(
|
||||
group.title,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Zähler
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Row(
|
||||
children: group.counters.map((counter) {
|
||||
return Expanded(
|
||||
child: _buildCounterCell(context, counter),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCounterCell(BuildContext context, ObjectCounter counter) {
|
||||
final color = counter.isOver
|
||||
? Colors.red.shade100
|
||||
: counter.isComplete
|
||||
? Colors.green.shade100
|
||||
: Colors.white;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 2),
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
counter.label,
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'${counter.currentCount}',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: counter.isOver ? Colors.red : Colors.black87,
|
||||
),
|
||||
),
|
||||
if (showDifferences && counter.targetCount > 0) ...[
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'${counter.difference > 0 ? "+" : ""}${counter.difference}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: counter.difference == 0
|
||||
? Colors.green
|
||||
: counter.difference > 0
|
||||
? Colors.orange
|
||||
: Colors.red,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Color _getGroupColor(String title) {
|
||||
switch (title.toLowerCase()) {
|
||||
case 'bestand fzg':
|
||||
return const Color(0xFFA4D4F0); // Hellblau wie in Lua
|
||||
case 'beladezähler':
|
||||
case 'hadag':
|
||||
case 'sst':
|
||||
case 'cr':
|
||||
return Colors.grey.shade200;
|
||||
default:
|
||||
return Colors.grey.shade200;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Vereinfachte Zähler-Anzeige für eine Zeile
|
||||
class CounterRow extends StatelessWidget {
|
||||
final String label;
|
||||
final int count;
|
||||
final Color? backgroundColor;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const CounterRow({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.count,
|
||||
this.backgroundColor,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor ?? Colors.grey.shade100,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: Colors.grey.shade300),
|
||||
),
|
||||
child: Text(
|
||||
'$count',
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
141
app/lib/presentation/widgets/error_view.dart
Normal file
141
app/lib/presentation/widgets/error_view.dart
Normal file
@@ -0,0 +1,141 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ErrorView extends StatelessWidget {
|
||||
final String message;
|
||||
final VoidCallback? onRetry;
|
||||
final String? retryText;
|
||||
final IconData? icon;
|
||||
|
||||
const ErrorView({
|
||||
super.key,
|
||||
required this.message,
|
||||
this.onRetry,
|
||||
this.retryText,
|
||||
this.icon,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.error.withValues(alpha: 26),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
icon ?? Icons.error_outline,
|
||||
size: 48,
|
||||
color: theme.colorScheme.error,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
'Oops!',
|
||||
style: theme.textTheme.headlineMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
message,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 179),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (onRetry != null) ...[
|
||||
const SizedBox(height: 32),
|
||||
ElevatedButton.icon(
|
||||
onPressed: onRetry,
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: Text(retryText ?? 'Erneut versuchen'),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EmptyStateView extends StatelessWidget {
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final IconData icon;
|
||||
final VoidCallback? onAction;
|
||||
final String? actionText;
|
||||
|
||||
const EmptyStateView({
|
||||
super.key,
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
this.icon = Icons.inbox,
|
||||
this.onAction,
|
||||
this.actionText,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade200,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
size: 48,
|
||||
color: Colors.grey.shade500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
title,
|
||||
style: theme.textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (subtitle != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
subtitle!,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 153),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
if (onAction != null && actionText != null) ...[
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton(
|
||||
onPressed: onAction,
|
||||
child: Text(actionText!),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
134
app/lib/presentation/widgets/loading_indicator.dart
Normal file
134
app/lib/presentation/widgets/loading_indicator.dart
Normal file
@@ -0,0 +1,134 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LoadingIndicator extends StatelessWidget {
|
||||
final String? message;
|
||||
final bool showAnimation;
|
||||
|
||||
const LoadingIndicator({
|
||||
super.key,
|
||||
this.message,
|
||||
this.showAnimation = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (showAnimation) ...[
|
||||
// Optional: Lottie Animation für Loading
|
||||
SizedBox(
|
||||
width: 120,
|
||||
height: 120,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 3,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
SizedBox(
|
||||
width: 48,
|
||||
height: 48,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 3,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (message != null) ...[
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
message!,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 179),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SkeletonLoading extends StatelessWidget {
|
||||
final int itemCount;
|
||||
final EdgeInsets padding;
|
||||
|
||||
const SkeletonLoading({
|
||||
super.key,
|
||||
this.itemCount = 5,
|
||||
this.padding = const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView.builder(
|
||||
padding: padding,
|
||||
itemCount: itemCount,
|
||||
itemBuilder: (context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: _SkeletonCard(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SkeletonCard extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade200,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 56,
|
||||
height: 56,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade300,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: 16,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade300,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
width: 120,
|
||||
height: 12,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade300,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
173
app/lib/presentation/widgets/recent_scans_list.dart
Normal file
173
app/lib/presentation/widgets/recent_scans_list.dart
Normal file
@@ -0,0 +1,173 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../domain/entities/counter.dart';
|
||||
|
||||
/// Zeigt zuletzt gescannte Objekte an
|
||||
/// Entspricht Lua: Die Liste in ShowStockStartScreen etc.
|
||||
class RecentScansList extends StatelessWidget {
|
||||
final List<RecentScan>? scans;
|
||||
|
||||
const RecentScansList({
|
||||
super.key,
|
||||
this.scans,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Demo-Daten falls keine vorhanden
|
||||
final demoScans = scans ?? const [
|
||||
// RecentScan(
|
||||
// objectCode: 'MEKA123456',
|
||||
// objectName: 'MEK A',
|
||||
// state: 'to_delivery',
|
||||
// scanTime: '',
|
||||
// ),
|
||||
];
|
||||
|
||||
if (demoScans.isEmpty) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade100,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'Noch keine Objekte gescannt',
|
||||
style: TextStyle(
|
||||
color: Colors.grey,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Zuletzt gescannt',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
...demoScans.map((scan) => _buildScanItem(context, scan)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildScanItem(BuildContext context, RecentScan scan) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade200,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// Objekt-Icon
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
_getIconForObject(scan.objectName),
|
||||
color: Colors.blue,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
|
||||
// Objekt-Info
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
scan.objectName,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'#${scan.objectCode}',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Status
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: _getColorForState(scan.state),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
_getShortStateName(scan.state),
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
IconData _getIconForObject(String objectName) {
|
||||
final name = objectName.toLowerCase();
|
||||
if (name.contains('mek')) return Icons.money;
|
||||
if (name.contains('bek')) return Icons.money_off;
|
||||
if (name.contains('hp')) return Icons.print;
|
||||
if (name.contains('fr')) return Icons.receipt;
|
||||
if (name.contains('sb')) return Icons.shopping_bag;
|
||||
if (name.contains('abs')) return Icons.delete;
|
||||
return Icons.inventory_2;
|
||||
}
|
||||
|
||||
Color _getColorForState(String state) {
|
||||
switch (state) {
|
||||
case 'to_delivery':
|
||||
return Colors.grey.shade300;
|
||||
case 'delivery':
|
||||
return Colors.grey.shade400;
|
||||
case 'station':
|
||||
return const Color(0xFFFFDD00);
|
||||
case 'in_fa':
|
||||
return const Color(0xFF9CDA7A);
|
||||
case 'ret_fail':
|
||||
return const Color(0xFFFF9081);
|
||||
default:
|
||||
return Colors.grey.shade200;
|
||||
}
|
||||
}
|
||||
|
||||
String _getShortStateName(String state) {
|
||||
switch (state) {
|
||||
case 'to_delivery':
|
||||
return 'Zum Fzg';
|
||||
case 'delivery':
|
||||
return 'Im Fzg';
|
||||
case 'station':
|
||||
return 'Station';
|
||||
case 'in_fa':
|
||||
return 'Im FA';
|
||||
case 'ret_fail':
|
||||
return 'Fehler';
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
231
app/lib/presentation/widgets/tour_list_item.dart
Normal file
231
app/lib/presentation/widgets/tour_list_item.dart
Normal file
@@ -0,0 +1,231 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import '../../domain/entities/tour.dart';
|
||||
import '../../core/constants/app_constants.dart';
|
||||
|
||||
class TourListItem extends StatelessWidget {
|
||||
final Tour tour;
|
||||
final VoidCallback onTap;
|
||||
final bool isCompleted;
|
||||
|
||||
const TourListItem({
|
||||
super.key,
|
||||
required this.tour,
|
||||
required this.onTap,
|
||||
this.isCompleted = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final iconData = _getIconForTourType(tour.type);
|
||||
final color = _getColorForTourType(tour.type);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
||||
child: Card(
|
||||
elevation: isCompleted ? 0 : 2,
|
||||
shadowColor: Colors.black.withValues(alpha: 26),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: isCompleted
|
||||
? BorderSide(color: Colors.grey.shade300)
|
||||
: BorderSide.none,
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: isCompleted ? null : onTap,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
// Icon Container
|
||||
Container(
|
||||
width: 56,
|
||||
height: 56,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
color,
|
||||
color.withValues(alpha: 204),
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: color.withValues(alpha: 77),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Icon(
|
||||
iconData,
|
||||
color: Colors.white,
|
||||
size: 28,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
|
||||
// Content
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
tour.locationName ?? 'Station ${tour.locationId}',
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isCompleted ? Colors.grey : null,
|
||||
decoration: isCompleted ? TextDecoration.lineThrough : null,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
_getTypeLabel(tour.type),
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: color,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
if (tour.remark != null && tour.remark!.isNotEmpty) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
tour.remark!,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Status Indicator
|
||||
if (isCompleted)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green.withValues(alpha: 26),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.check,
|
||||
color: Colors.green,
|
||||
size: 20,
|
||||
),
|
||||
)
|
||||
else
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade100,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.chevron_right,
|
||||
color: Colors.grey.shade600,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
).animate().fadeIn(duration: 300.ms).slideX(begin: 0.1, end: 0);
|
||||
}
|
||||
|
||||
IconData _getIconForTourType(String type) {
|
||||
switch (type) {
|
||||
case TourTypes.stockStart:
|
||||
return Icons.warehouse;
|
||||
case TourTypes.stockEnd:
|
||||
return Icons.archive;
|
||||
case TourTypes.start:
|
||||
return Icons.business;
|
||||
case TourTypes.end:
|
||||
return Icons.flag;
|
||||
case TourTypes.station:
|
||||
return Icons.directions_bus;
|
||||
case TourTypes.hls:
|
||||
return Icons.train;
|
||||
case TourTypes.fsa:
|
||||
return Icons.confirmation_number;
|
||||
case TourTypes.vs:
|
||||
return Icons.store;
|
||||
case TourTypes.gi:
|
||||
return Icons.account_balance;
|
||||
case TourTypes.veh:
|
||||
case TourTypes.vehStart:
|
||||
case TourTypes.vehEnd:
|
||||
return Icons.local_shipping;
|
||||
default:
|
||||
return Icons.location_on;
|
||||
}
|
||||
}
|
||||
|
||||
Color _getColorForTourType(String type) {
|
||||
switch (type) {
|
||||
case TourTypes.stockStart:
|
||||
case TourTypes.stockEnd:
|
||||
return const Color(0xFF2196F3);
|
||||
case TourTypes.start:
|
||||
case TourTypes.end:
|
||||
return const Color(0xFF4CAF50);
|
||||
case TourTypes.station:
|
||||
return const Color(0xFFFF9800);
|
||||
case TourTypes.hls:
|
||||
return const Color(0xFF9C27B0);
|
||||
case TourTypes.fsa:
|
||||
return const Color(0xFFE91E63);
|
||||
case TourTypes.vs:
|
||||
return const Color(0xFF00BCD4);
|
||||
case TourTypes.gi:
|
||||
return const Color(0xFF3F51B5);
|
||||
case TourTypes.veh:
|
||||
case TourTypes.vehStart:
|
||||
case TourTypes.vehEnd:
|
||||
return const Color(0xFF795548);
|
||||
default:
|
||||
return const Color(0xFF607D8B);
|
||||
}
|
||||
}
|
||||
|
||||
String _getTypeLabel(String type) {
|
||||
switch (type) {
|
||||
case TourTypes.stockStart:
|
||||
return 'Lager - Beladung';
|
||||
case TourTypes.stockEnd:
|
||||
return 'Lager - Rückgabe';
|
||||
case TourTypes.start:
|
||||
return 'Dienststelle';
|
||||
case TourTypes.end:
|
||||
return 'Tour Ende';
|
||||
case TourTypes.station:
|
||||
return 'Haltestelle';
|
||||
case TourTypes.hls:
|
||||
return 'Hochbahnstation';
|
||||
case TourTypes.fsa:
|
||||
return 'Fahrscheinautomat';
|
||||
case TourTypes.vs:
|
||||
return 'Versorgungsstelle';
|
||||
case TourTypes.gi:
|
||||
return 'Geldinstitut';
|
||||
case TourTypes.veh:
|
||||
return 'Fahrzeug';
|
||||
case TourTypes.vehStart:
|
||||
return 'Fahrzeug - Beladung';
|
||||
case TourTypes.vehEnd:
|
||||
return 'Fahrzeug - Entladung';
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user