You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1361 lines
51 KiB

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:supplier_new/resources/tanker_trips_model.dart';
import '../common/settings.dart';
class FirstCharUppercaseFormatter extends TextInputFormatter {
const FirstCharUppercaseFormatter();
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
final text = newValue.text;
if (text.isEmpty) return newValue;
final i = text.indexOf(RegExp(r'\S'));
if (i == -1) return newValue;
final first = text[i];
final upper = first.toUpperCase();
if (first == upper) return newValue;
final newText = text.replaceRange(i, i + 1, upper);
return newValue.copyWith(
text: newText,
selection: newValue.selection,
composing: TextRange.empty,
);
}
}
class TankerDetailsPage extends StatefulWidget {
var tankerDetails;
var status;
TankerDetailsPage({this.tankerDetails, this.status});
@override
State<TankerDetailsPage> createState() => _TankerDetailsPageState();
}
class _TankerDetailsPageState extends State<TankerDetailsPage> {
final _formKey = GlobalKey<FormState>();
final _nameCtrl = TextEditingController();
final _capacityCtrl = TextEditingController();
final _plateCtrl = TextEditingController();
final _mfgYearCtrl = TextEditingController();
final _insExpiryCtrl = TextEditingController();
String? selectedType;
String? selectedTypeOfWater;
final List<String> tankerTypes = const [
"Standard Tanker",
"High-Capacity Tanker",
"Small Tanker",
];
final List<String> typeOfWater = const [
"Drinking water",
"Bore water",
];
String? _required(String? v, {String field = "This field"}) {
if (v == null || v.trim().isEmpty) return "$field is required";
return null;
}
int selectedTab = 0;
String search = '';
bool isLoading = false;
bool isTankerTripsLoading = false;
bool isStatusLoading = false;
String? currentAvailability;
List<TankerTripsModel> tankerTripsList = [];
@override
void initState() {
super.initState();
_nameCtrl.text = widget.tankerDetails.tanker_name ?? '';
if (widget.tankerDetails.availability != null &&
widget.tankerDetails.availability is List &&
widget.tankerDetails.availability.length == 2) {
final a = widget.tankerDetails.availability;
currentAvailability = "${a[0]}|${a[1]}";
_fetchTankerTrips();
}
}
Future<void> _fetchTankerTrips() async {
setState(() => isTankerTripsLoading = true);
try {
var payload = new Map<String, dynamic>();
payload["customerId"] = widget.tankerDetails.tanker_name;
payload["tankerName"] = widget.tankerDetails.tanker_name;
final response = await AppSettings.getTankerTrips(payload);
final data = (jsonDecode(response)['data'] as List)
.map((e) => TankerTripsModel.fromJson(e))
.toList();
if (!mounted) return;
setState(() {
tankerTripsList = data;
isTankerTripsLoading = false;
});
} catch (e) {
debugPrint("⚠️ Error fetching tankers: $e");
setState(() => isTankerTripsLoading = false);
}
}
final List<String> options = [
"empty|available",
"empty|blocked",
"empty|undermaintanence",
"filled|available",
"filled|blocked",
"filled|undermaintanence",
];
String _getLabel(String value) {
final parts = value.split('|');
return "${parts[0].toUpperCase()}${parts[1].toUpperCase()}";
}
Future<void> _updateAvailability(String selectedValue) async {
final parts = selectedValue.split('|');
final availability = [parts[0], parts[1]];
final payload = <String, dynamic>{
"tankerName": widget.tankerDetails.tanker_name,
"availability": availability,
};
try {
setState(() => isStatusLoading = true);
final bool tankStatus =
await AppSettings.updateTankerAvailability(payload);
if (!mounted) return;
if (tankStatus) {
AppSettings.longSuccessToast("Tanker status updated successfully");
Navigator.pop(context, true);
await _refreshTankerDetails();
} else {
Navigator.pop(context, true);
AppSettings.longFailedToast("Tanker status update failed");
}
} catch (e) {
debugPrint("⚠️ update tanker error: $e");
if (!mounted) return;
AppSettings.longFailedToast("Something went wrong: $e");
} finally {
if (mounted) setState(() => isStatusLoading = false);
}
}
void _showDropdownBottomSheet({String? defaultValue}) {
String? selectedValue = defaultValue;
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
builder: (context) {
final viewInsets = MediaQuery.of(context).viewInsets.bottom;
return FractionallySizedBox(
heightFactor: 0.6,
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
padding: EdgeInsets.fromLTRB(20, 16, 20, 16 + viewInsets),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Container(
width: 60,
height: 4,
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
color: const Color(0xFFE0E0E0),
borderRadius: BorderRadius.circular(2),
),
),
),
const Text(
"Change Tanker Status",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
),
const SizedBox(height: 20),
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
DropdownButtonFormField<String>(
value: selectedValue,
decoration: InputDecoration(
labelText: "Select Availability",
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
contentPadding: const EdgeInsets.symmetric(
vertical: 10,
horizontal: 12,
),
),
items: options
.map((opt) => DropdownMenuItem(
value: opt,
child: Text(_getLabel(opt)),
))
.toList(),
onChanged: (val) => selectedValue = val,
),
const SizedBox(height: 20),
],
),
),
),
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF515253),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
padding: const EdgeInsets.symmetric(vertical: 12),
),
onPressed: () {
if (selectedValue != null) {
_updateAvailability(selectedValue!);
} else {
AppSettings.longFailedToast("Please select an option");
}
},
child: isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
color: Colors.white, strokeWidth: 2))
: const Text("Submit"),
),
),
],
),
),
);
},
);
}
showDeleteTankerDialog(BuildContext context) async {
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return StatefulBuilder(builder:
(BuildContext context, StateSetter setState) {
return AlertDialog(
backgroundColor: Color(0XFFFFFFFF),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
title: Center(
child: Text(
'Delete Tanker?',
style:
fontTextStyle(16, Color(0XFF3B3B3B), FontWeight.w600),
),
),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Container(
child: Text(
'Do u want to delete "${widget.tankerDetails.tanker_name}"',
style: fontTextStyle(
14, Color(0XFF101214), FontWeight.w600),
),
),
],
),
),
actions: <Widget>[
Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Container(
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Color(0XFFFFFFFF),
border: Border.all(
width: 1, color: Color(0XFF1D7AFC)),
borderRadius: BorderRadius.circular(12)),
alignment: Alignment.center,
child: Padding(
padding: EdgeInsets.fromLTRB(
16, 12, 16, 12),
child: Text(
'Cancel',
style: fontTextStyle(
12,
Color(0XFF1D7AFC),
FontWeight.w600),
),
),
),
),
),
SizedBox(
width:
MediaQuery.of(context).size.width * .016),
Expanded(
child: GestureDetector(
onTap: () async {
bool status = await AppSettings.deleteTanker(
widget.tankerDetails.tanker_name,
);
if (status) {
AppSettings.longSuccessToast(
'Tanker deleted successfully');
Navigator.of(context).pop(true);
Navigator.of(context).pop(true);
} else {
Navigator.of(context).pop(true);
AppSettings.longFailedToast(
'Failed to delete tanker');
}
},
child: Container(
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Color(0XFFE2483D),
border: Border.all(
width: 1,
color: Color(0XFFE2483D)),
borderRadius:
BorderRadius.circular(12)),
alignment: Alignment.center,
child: Padding(
padding: EdgeInsets.fromLTRB(
16, 12, 16, 12),
child: Text(
'Delete',
style: fontTextStyle(
12,
Color(0XFFFFFFFF),
FontWeight.w600),
),
),
),
)),
],
),
),
],
);
});
},
);
}
Color _chipColor(String s) {
switch (s) {
case 'filled':
return const Color(0xFFFFFFFF);
case 'available':
return const Color(0xFFE8F0FF);
case 'empty':
return const Color(0xFFFFEEEE);
case 'in-use':
return const Color(0xFFFFF0E6);
case 'maintenance':
return const Color(0xFFFFF4E6);
default:
return const Color(0xFFECECEC);
}
}
Color _chipTextColor(String s) {
switch (s) {
case 'filled':
return const Color(0xFF1D7AFC);
case 'available':
return const Color(0xFF0A9E04);
case 'empty':
return const Color(0xFFE2483D);
case 'in-use':
return const Color(0xFFEA843B);
case 'maintenance':
return const Color(0xFFD0AE3C);
default:
return Colors.black87;
}
}
Future<void> _pickInsuranceDate() async {
final picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2100),
builder: (context, child) {
return Theme(
data: Theme.of(context).copyWith(
dialogBackgroundColor: Colors.white,
colorScheme: Theme.of(context).colorScheme.copyWith(
primary: const Color(0xFF8270DB),
surface: Colors.white,
onSurface: const Color(0xFF101214),
),
),
child: child!,
);
},
);
if (picked != null) {
final dd = picked.day.toString().padLeft(2, '0');
final mm = picked.month.toString().padLeft(2, '0');
final yyyy = picked.year.toString();
_insExpiryCtrl.text = "$dd-$mm-$yyyy";
setState(() {});
}
}
void _resetForm() {
_formKey.currentState?.reset();
_nameCtrl.clear();
_capacityCtrl.clear();
_plateCtrl.clear();
_mfgYearCtrl.clear();
_insExpiryCtrl.clear();
selectedType = null;
selectedTypeOfWater = null;
}
Future<void> _refreshTankerDetails() async {
try {
setState(() => isLoading = true);
final updatedDetails = await AppSettings.getTankerDetailsByName(
_nameCtrl.text.trim(),
);
if (updatedDetails != null) {
setState(() {
widget.tankerDetails = updatedDetails;
});
} else {
AppSettings.longFailedToast("Failed to fetch updated tanker details");
}
} catch (e) {
debugPrint("⚠️ Error refreshing tanker details: $e");
AppSettings.longFailedToast("Error refreshing tanker details");
} finally {
setState(() => isLoading = false);
}
}
String? fitToOption(String? incoming, List<String> options) {
if (incoming == null) return null;
final inc = incoming.trim();
if (inc.isEmpty) return null;
final match = options.firstWhere(
(o) => o.toLowerCase() == inc.toLowerCase(),
orElse: () => '',
);
return match.isEmpty ? null : match;
}
Future<void> _updateTanker() async {
final ok = _formKey.currentState?.validate() ?? false;
if (!ok) {
setState(() {});
return;
}
final payload = <String, dynamic>{
"tankerName": _nameCtrl.text.trim(),
"capacity": _capacityCtrl.text.trim(),
"typeofwater": selectedTypeOfWater ?? "",
"supplier_address": AppSettings.userAddress,
"supplier_name": AppSettings.userName,
"phoneNumber": AppSettings.phoneNumber,
"tanker_type": selectedType ?? "",
"license_plate": _plateCtrl.text.trim(),
"manufacturing_year": _mfgYearCtrl.text.trim(),
"insurance_exp_date": _insExpiryCtrl.text.trim(),
"delivery_fee": widget.tankerDetails.delivery_fee,
"pumping_fee": widget.tankerDetails.pumping_fee,
"price": widget.tankerDetails.price,
};
try {
final bool tankStatus = await AppSettings.updateTanker(
payload, widget.tankerDetails.tanker_name);
if (!mounted) return;
if (tankStatus) {
AppSettings.longSuccessToast("Tanker Updated Successfully");
Navigator.pop(context, true);
_refreshTankerDetails();
_resetForm();
} else {
AppSettings.longFailedToast("Tanker update failed");
}
} catch (e) {
debugPrint("⚠️ update tanker error: $e");
if (!mounted) return;
AppSettings.longFailedToast("Something went wrong");
}
}
Future<void> openTankerSimpleSheet(BuildContext context) async {
_nameCtrl.text = widget.tankerDetails.tanker_name ?? '';
_capacityCtrl.text = widget.tankerDetails.capacity ?? '';
_plateCtrl.text = widget.tankerDetails.license_plate ?? '';
_mfgYearCtrl.text = widget.tankerDetails.manufacturing_year ?? '';
_insExpiryCtrl.text = widget.tankerDetails.insurance_expiry ?? '';
selectedType = fitToOption(widget.tankerDetails.tanker_type, tankerTypes);
selectedTypeOfWater =
fitToOption(widget.tankerDetails.type_of_water, typeOfWater);
await showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) {
final viewInsets = MediaQuery.of(context).viewInsets.bottom;
return FractionallySizedBox(
heightFactor: 0.75,
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
child: Padding(
padding: EdgeInsets.fromLTRB(20, 16, 20, 20 + viewInsets),
child: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
Expanded(
child: Center(
child: Container(
width: 86,
height: 4,
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: const Color(0xFFE0E0E0),
borderRadius: BorderRadius.circular(2),
),
),
),
),
],
),
_LabeledField(
label: "Tanker Name *",
child: TextFormField(
controller: _nameCtrl,
validator: (v) =>
_required(v, field: "Tanker Name"),
textCapitalization: TextCapitalization.none,
inputFormatters: const [
FirstCharUppercaseFormatter(),
],
decoration: InputDecoration(
hintText: "Enter Tanker Name",
hintStyle: fontTextStyle(
14,
const Color(0xFF939495),
FontWeight.w400),
border: const OutlineInputBorder(),
isDense: true,
),
textInputAction: TextInputAction.next,
),
),
_LabeledField(
label: "Tanker Capacity (in L) *",
child: TextFormField(
controller: _capacityCtrl,
validator: (v) =>
_required(v, field: "Tanker Capacity"),
decoration: InputDecoration(
hintText: "10,000",
hintStyle: fontTextStyle(
14,
const Color(0xFF939495),
FontWeight.w400),
border: const OutlineInputBorder(),
isDense: true,
),
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.allow(
RegExp(r'[0-9,]')),
],
textInputAction: TextInputAction.next,
),
),
_LabeledField(
label: "Tanker Type *",
child: DropdownButtonFormField<String>(
value: selectedType,
items: tankerTypes
.map((t) =>
DropdownMenuItem(value: t, child: Text(t)))
.toList(),
onChanged: (v) =>
setState(() => selectedType = v),
validator: (v) =>
v == null || v.isEmpty
? "Tanker Type is required"
: null,
isExpanded: true,
alignment: Alignment.centerLeft,
hint: Text(
"Select Type",
style: fontTextStyle(
14,
const Color(0xFF939495),
FontWeight.w400),
),
icon: Image.asset('images/downarrow.png',
width: 16, height: 16),
decoration: const InputDecoration(
border: OutlineInputBorder(),
isDense: false,
contentPadding: EdgeInsets.symmetric(
horizontal: 12, vertical: 14),
),
),
),
_LabeledField(
label: "Type of water *",
child: DropdownButtonFormField<String>(
value: (selectedTypeOfWater != null &&
typeOfWater
.contains(selectedTypeOfWater))
? selectedTypeOfWater
: null,
items: typeOfWater
.map((t) =>
DropdownMenuItem(value: t, child: Text(t)))
.toList(),
onChanged: (v) =>
setState(() => selectedTypeOfWater = v),
validator: (v) =>
v == null || v.isEmpty
? "Type of water is required"
: null,
isExpanded: true,
alignment: Alignment.centerLeft,
hint: Text(
"Select type of water",
style: fontTextStyle(
14,
const Color(0xFF939495),
FontWeight.w400),
),
icon: Image.asset('images/downarrow.png',
width: 16, height: 16),
decoration: const InputDecoration(
border: OutlineInputBorder(),
isDense: false,
contentPadding: EdgeInsets.symmetric(
horizontal: 12, vertical: 14),
),
),
),
_LabeledField(
label: "License Plate *",
child: TextFormField(
controller: _plateCtrl,
validator: (v) =>
_required(v, field: "License Plate"),
decoration: InputDecoration(
hintText: "AB 05 H 4948",
hintStyle: fontTextStyle(
14,
const Color(0xFF939495),
FontWeight.w400),
border: const OutlineInputBorder(),
isDense: true,
),
textCapitalization:
TextCapitalization.characters,
textInputAction: TextInputAction.next,
),
),
_LabeledField(
label: "Manufacturing Year (opt)",
child: TextFormField(
controller: _mfgYearCtrl,
decoration: InputDecoration(
hintText: "YYYY",
hintStyle: fontTextStyle(
14,
const Color(0xFF939495),
FontWeight.w400),
border: const OutlineInputBorder(),
isDense: true,
),
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(4),
],
textInputAction: TextInputAction.next,
),
),
_LabeledField(
label: "Insurance Expiry Date (opt)",
child: TextFormField(
controller: _insExpiryCtrl,
readOnly: true,
decoration: InputDecoration(
hintText: "DD-MM-YYYY",
hintStyle: fontTextStyle(
14,
const Color(0xFF939495),
FontWeight.w400),
border: const OutlineInputBorder(),
isDense: true,
suffixIcon: const Icon(
Icons.calendar_today_outlined,
size: 18),
),
onTap: _pickInsuranceDate,
),
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF8270DB),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
),
onPressed: () {
_updateTanker();
},
child: Text(
"Update",
style: fontTextStyle(
14,
Colors.white,
FontWeight.w600),
),
),
),
],
),
),
),
),
),
);
},
);
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
Navigator.pop(context, true);
return false;
},
child: Scaffold(
backgroundColor: Color(0XFFF1F1F1),
appBar: AppSettings.supplierAppBarWithActionsText(
widget.tankerDetails.tanker_name.isNotEmpty
? widget.tankerDetails.tanker_name[0].toUpperCase() +
widget.tankerDetails.tanker_name.substring(1)
: '',
context),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: const BoxDecoration(
color: Color(0XFFFFFFFF),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24),
),
),
padding: const EdgeInsets.all(16),
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.asset(
'images/tanker_image.jpeg',
width: double.infinity,
height: 180,
fit: BoxFit.cover,
),
),
const SizedBox(height: 16),
Row(
children: widget.tankerDetails.availability
.map<Widget>((s) {
final chipTextColor = _chipTextColor(s);
return Container(
margin:
const EdgeInsets.only(right: 6),
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: _chipColor(s),
borderRadius:
BorderRadius.circular(8),
border: Border.all(
color: chipTextColor, width: 1),
),
child: Text(
s,
style: fontTextStyle(
10,
chipTextColor,
FontWeight.w400),
),
);
}).toList(),
),
const SizedBox(height: 16),
Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
widget.tankerDetails.tanker_name,
style: fontTextStyle(
16,
const Color(0xFF2A2A2A),
FontWeight.w600),
),
SizedBox(height: 2),
Text(
widget.tankerDetails
.type_of_water +
' - ' +
widget.tankerDetails
.capacity +
' L',
style: fontTextStyle(
10,
const Color(0xFF646566),
FontWeight.w400),
),
SizedBox(height: 2),
Text(
widget.tankerDetails
.license_plate,
style: fontTextStyle(
10,
const Color(0xFF646566),
FontWeight.w400),
),
],
),
),
PopupMenuButton<String>(
child: Padding(
padding:
const EdgeInsets.symmetric(
horizontal: 8.0,
vertical: 8.0),
child: Image.asset(
'images/popup_menu.png',
width: 22,
height: 22,
color: Color(0XFF939495),
colorBlendMode:
BlendMode.srcIn,
),
),
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(12)),
offset: const Offset(0, 40),
color: Colors.white,
elevation: 4,
onSelected: (value) {
if (value == 'edit') {
openTankerSimpleSheet(context);
} else if (value ==
'disable') {
ScaffoldMessenger.of(context)
.showSnackBar(
const SnackBar(
content: Text(
'Disable selected')),
);
} else if (value ==
'delete') {
showDeleteTankerDialog(
context);
}
},
itemBuilder: (context) => [
PopupMenuItem(
value: 'edit',
child: Row(
children: [
Image.asset(
'images/edit.png',
width: 20,
height: 20,
color: Color(
0XFF646566),
colorBlendMode:
BlendMode.srcIn,
),
const SizedBox(
width: 12),
Text(
'Edit',
style: fontTextStyle(
14,
const Color(
0XFF646566),
FontWeight
.w400),
),
],
),
),
PopupMenuItem(
value: 'disable',
child: Row(
children: [
Image.asset(
'images/disable.png',
width: 20,
height: 20,
color: Color(
0XFF646566),
colorBlendMode:
BlendMode.srcIn,
),
const SizedBox(
width: 12),
Text(
'Disable',
style: fontTextStyle(
14,
const Color(
0XFF646566),
FontWeight
.w400),
),
],
),
),
PopupMenuItem(
value: 'delete',
child: Row(
children: [
Image.asset(
'images/delete.png',
width: 20,
height: 20,
color: Color(
0XFFE2483D),
colorBlendMode:
BlendMode.srcIn,
),
const SizedBox(
width: 12),
Text(
'Delete',
style: fontTextStyle(
14,
const Color(
0xFFE2483D),
FontWeight
.w400),
),
],
),
),
],
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor:
const Color(0xFFFFFFFF),
foregroundColor:
const Color(0xFF515253),
padding:
const EdgeInsets.symmetric(
vertical: 10),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(
24),
side: const BorderSide(
color:
Color(0xFF939495),
width: 0.5),
),
),
onPressed: () async {
_showDropdownBottomSheet(
defaultValue:
currentAvailability);
},
child: Text(
"Change Status",
style: fontTextStyle(
14,
const Color(0xFF515253),
FontWeight.w500),
),
)),
SizedBox(width: 8),
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor:
const Color(0xFF8270DB),
foregroundColor:
const Color(0xFFFFFFFF),
padding:
const EdgeInsets.symmetric(
vertical: 10),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(
24),
side: const BorderSide(
color:
Color(0xFF8270DB),
width: 0.5),
),
),
onPressed: () async {},
child: Text(
"Assign",
style: fontTextStyle(
14,
const Color(0xFFFFFFFF),
FontWeight.w500),
),
)),
],
),
],
),
),
Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
"RECENT TRIPS",
style: fontTextStyle(
10,
const Color(0xFF343637),
FontWeight.w600),
),
const SizedBox(height: 12),
_buildTripCard(
driverName: "Ramesh Krishna",
time:
"7:02 PM, 28 Jun 2025",
from:
"Bachupally Filling Station",
to: "Akriti Heights",
),
const SizedBox(height: 8),
_buildTripCard(
driverName: "Ramesh Krishna",
time:
"12:44 PM, 26 Jun 2025",
from:
"Bachupally Filling Station",
to: "Akriti Heights",
),
],
),
),
// ⭐️ FIX — Removed Expanded (illegal inside ScrollView)
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16),
child: isTankerTripsLoading
? const Center(
child:
CircularProgressIndicator())
: tankerTripsList.isEmpty
? Center(
child: Padding(
padding:
const EdgeInsets
.symmetric(
vertical:
12),
child: Text(
'No Data Available',
style: fontTextStyle(
12,
const Color(
0xFF939495),
FontWeight
.w500),
),
),
)
: ListView.separated(
itemCount:
tankerTripsList
.length,
shrinkWrap: true, // ⭐ Fix
physics:
NeverScrollableScrollPhysics(), // ⭐ Fix
separatorBuilder:
(_, __) =>
const SizedBox(
height:
10),
itemBuilder:
(context, idx) {
final it =
tankerTripsList[
idx];
return GestureDetector(
onTap: () async {},
child: _buildTripCard(
driverName:
it.driver_name,
time:
"7:02 PM, 28 Jun 2025",
from:
"Bachupally Filling Station",
to:
"Akriti Heights",
),
);
},
),
),
],
),
),
),
));
}
Widget _buildTripCard({
required String driverName,
required String time,
required String from,
required String to,
}) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFFF8F8F8),
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
const CircleAvatar(
radius: 14,
backgroundImage:
AssetImage('images/avatar.png'),
),
const SizedBox(width: 8),
Text(
driverName,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 13,
),
),
],
),
Text(
time,
style: const TextStyle(
fontSize: 11,
color: Colors.black54,
),
),
],
),
const SizedBox(height: 8),
Row(
children: [
const Icon(Icons.location_on,
color: Color(0xFF4F46E5), size: 16),
const SizedBox(width: 6),
Expanded(
child: Text(
from,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
),
],
),
const SizedBox(height: 4),
Row(
children: [
const Icon(Icons.location_on,
color: Colors.red, size: 16),
const SizedBox(width: 6),
Expanded(
child: Text(
to,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
),
],
),
],
),
);
}
}
class _LabeledField extends StatelessWidget {
final String label;
final Widget child;
const _LabeledField({required this.label, required this.child});
String _capFirstWord(String input) {
if (input.isEmpty) return input;
final i = input.indexOf(RegExp(r'\S'));
if (i == -1) return input;
return input.replaceRange(
i, i + 1, input[i].toUpperCase());
}
@override
Widget build(BuildContext context) {
return Padding(
padding:
const EdgeInsets.only(bottom: 14.0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
_capFirstWord(label),
style: fontTextStyle(
12,
const Color(0xFF515253),
FontWeight.w600),
),
const SizedBox(height: 6),
child,
],
),
);
}
}