feat(ui): improve mobile equipment card and unify travel cost dialog
This commit is contained in:
@@ -46,6 +46,8 @@ class _TravelCostDialogState extends State<TravelCostDialog> {
|
||||
int _nbTechnicians = 2;
|
||||
double _hourlyRate = 25.0;
|
||||
bool _applyFreeZone = false;
|
||||
double _freeZoneKm = 20.0;
|
||||
int _freeZoneMinutes = 20;
|
||||
|
||||
// Résultats
|
||||
List<RouteResult> _routes = [];
|
||||
@@ -142,7 +144,8 @@ class _TravelCostDialogState extends State<TravelCostDialog> {
|
||||
maintenanceCostPerKm: _selectedVehicle!.maintenanceCostPerKm,
|
||||
nbTechnicians: _nbTechnicians,
|
||||
hourlyRate: _hourlyRate,
|
||||
applyFreeZone: _applyFreeZone,
|
||||
freeZoneKm: _applyFreeZone ? _freeZoneKm : 0.0,
|
||||
freeZoneMinutes: _applyFreeZone ? _freeZoneMinutes.toDouble() : 0.0,
|
||||
);
|
||||
Navigator.of(context).pop(total);
|
||||
}
|
||||
@@ -230,29 +233,32 @@ class _TravelCostDialogState extends State<TravelCostDialog> {
|
||||
);
|
||||
}
|
||||
|
||||
if (_routes.isEmpty) {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: _buildConfigPanel(),
|
||||
);
|
||||
}
|
||||
|
||||
// Résultats
|
||||
final screenW = MediaQuery.of(context).size.width;
|
||||
final isWide = screenW > 900;
|
||||
|
||||
if (isWide) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: RouteMapWidget(routes: _routes, selectedRoute: _selectedRoute),
|
||||
child: _routes.isEmpty
|
||||
? const Center(child: Text("Configurez le trajet et cliquez sur Calculer"))
|
||||
: RouteMapWidget(routes: _routes, selectedRoute: _selectedRoute),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: _buildResultsPanel(),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildConfigPanel(),
|
||||
if (_routes.isNotEmpty) ...[
|
||||
const Divider(height: 32),
|
||||
_buildResultsPanel(),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -261,13 +267,22 @@ class _TravelCostDialogState extends State<TravelCostDialog> {
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 260,
|
||||
child: RouteMapWidget(routes: _routes, selectedRoute: _selectedRoute),
|
||||
),
|
||||
if (_routes.isNotEmpty)
|
||||
SizedBox(
|
||||
height: 260,
|
||||
child: RouteMapWidget(routes: _routes, selectedRoute: _selectedRoute),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: _buildResultsPanel(),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildConfigPanel(),
|
||||
if (_routes.isNotEmpty) ...[
|
||||
const Divider(height: 32),
|
||||
_buildResultsPanel(),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -413,10 +428,53 @@ class _TravelCostDialogState extends State<TravelCostDialog> {
|
||||
activeColor: AppColors.rouge,
|
||||
title: const Text('Appliquer la zone de gratuité'),
|
||||
subtitle:
|
||||
const Text('Déduit 20 km (carburant + maintenance) et 20 min (main-d\'œuvre)'),
|
||||
Text('Déduit ${_freeZoneKm.toStringAsFixed(0)} km (carburant + maintenance) et $_freeZoneMinutes min (main-d\'œuvre)'),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
),
|
||||
if (_applyFreeZone)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 32, top: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
initialValue: _freeZoneKm.toStringAsFixed(0),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Distance déduite',
|
||||
border: OutlineInputBorder(),
|
||||
suffixText: 'km',
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,2}'))
|
||||
],
|
||||
onChanged: (v) {
|
||||
final parsed = double.tryParse(v);
|
||||
if (parsed != null) setState(() => _freeZoneKm = parsed);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
initialValue: _freeZoneMinutes.toString(),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Temps déduit',
|
||||
border: OutlineInputBorder(),
|
||||
suffixText: 'min',
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
onChanged: (v) {
|
||||
final parsed = int.tryParse(v);
|
||||
if (parsed != null) setState(() => _freeZoneMinutes = parsed);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
@@ -492,21 +550,22 @@ class _TravelCostDialogState extends State<TravelCostDialog> {
|
||||
maintenanceCostPerKm: vehicle.maintenanceCostPerKm,
|
||||
nbTechnicians: _nbTechnicians,
|
||||
hourlyRate: _hourlyRate,
|
||||
applyFreeZone: _applyFreeZone,
|
||||
freeZoneKm: _applyFreeZone ? _freeZoneKm : 0.0,
|
||||
freeZoneMinutes: _applyFreeZone ? _freeZoneMinutes.toDouble() : 0.0,
|
||||
);
|
||||
final fuel = route.fuelCost(
|
||||
consumptionPer100km: vehicle.consumptionPer100km,
|
||||
fuelPricePerLiter: fuelPrice,
|
||||
freeZoneKm: _applyFreeZone ? 20 : 0,
|
||||
freeZoneKm: _applyFreeZone ? _freeZoneKm : 0.0,
|
||||
);
|
||||
final maint = route.maintenanceCost(
|
||||
costPerKm: vehicle.maintenanceCostPerKm,
|
||||
freeZoneKm: _applyFreeZone ? 20 : 0,
|
||||
freeZoneKm: _applyFreeZone ? _freeZoneKm : 0.0,
|
||||
);
|
||||
final labor = route.laborCost(
|
||||
nbTechnicians: _nbTechnicians,
|
||||
hourlyRate: _hourlyRate,
|
||||
freeZoneMinutes: _applyFreeZone ? 20 : 0,
|
||||
freeZoneMinutes: _applyFreeZone ? _freeZoneMinutes.toDouble() : 0.0,
|
||||
);
|
||||
|
||||
final isToll = route.routeType == 'TOLL';
|
||||
|
||||
Reference in New Issue
Block a user