From 3fae53a7d21ecd159fbcef8cce22005fc4a15f8e Mon Sep 17 00:00:00 2001 From: Sneha Date: Fri, 7 Nov 2025 11:11:10 +0530 Subject: [PATCH] changes in resources page --- lib/common/first_char_upper.dart | 30 ++ lib/common/settings.dart | 175 ++++++- lib/orders/all_orders.dart | 46 ++ lib/resources/driver_details.dart | 26 +- lib/resources/drivers_model.dart | 19 +- lib/resources/resources_sources.dart | 12 +- lib/resources/source_loctaions_model.dart | 59 ++- lib/resources/sourcelocation_details.dart | 596 +++++++++++++++++++--- lib/resources/tanker_details.dart | 74 +++ lib/resources/tanker_trips_model.dart | 29 ++ 10 files changed, 951 insertions(+), 115 deletions(-) create mode 100644 lib/common/first_char_upper.dart create mode 100644 lib/resources/tanker_trips_model.dart diff --git a/lib/common/first_char_upper.dart b/lib/common/first_char_upper.dart new file mode 100644 index 0000000..5826916 --- /dev/null +++ b/lib/common/first_char_upper.dart @@ -0,0 +1,30 @@ +import 'package:flutter/services.dart'; + +class FirstCharUppercaseFormatter extends TextInputFormatter { + const FirstCharUppercaseFormatter(); + + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, + TextEditingValue newValue, + ) { + final text = newValue.text; + if (text.isEmpty) return newValue; + + // Find first non-space char + 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, + ); + } +} \ No newline at end of file diff --git a/lib/common/settings.dart b/lib/common/settings.dart index 606ef37..f0b012d 100644 --- a/lib/common/settings.dart +++ b/lib/common/settings.dart @@ -8,7 +8,9 @@ import 'package:fluttertoast/fluttertoast.dart'; import 'package:intl/intl.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:supplier_new/common/preloader.dart'; +import 'package:supplier_new/resources/source_loctaions_model.dart'; +import '../resources/drivers_model.dart'; import '../resources/tankers_model.dart'; const Color primaryColor = Color(0XFF8270DB); @@ -163,16 +165,22 @@ class AppSettings{ static String updateTankerAvailabilityUrl = host + 'updatetankerstatus'; static String deleteTankerUrl = host + 'deleteTanker'; static String updateTankerUrl = host + 'updateTankers'; + static String getTankerTripsUrl = host + 'tankerAccounts'; static String getDriversUrl = host + 'getalldeliveryboys'; static String addDriversUrl = host + 'addDeliveryboys'; static String updateDriversUrl = host + 'updatedeliveryboy'; static String deleteDriverUrl = host + 'deletedeliveryboy'; static String addSourceLocationsUrl = host + 'addSource'; + static String updateSourceLocationsUrl = host + 'sources'; + static String deleteSourceUrl = host + 'sources'; static String getSourceLoctaionsUrl = host + 'getallsourcesofsupplier'; + static String getSourceDetailsByIdUrl = host + 'sources'; static String setRatesDailyUrl = host + 'tankers'; static String getSupplierDetailsUrl = host + 'suppliers'; static String updatePumpFeeUrl = host + 'suppliers'; static String assignTankerUrl = host + 'assign-deliveryboy-tanker'; + static String getDriverDetailsByPhoneUrl = host + 'getsingledeliveryboydetails'; + @@ -694,6 +702,70 @@ class AppSettings{ } } + static Future getTankerTrips(payload) async { + var uri = Uri.parse(getTankerTripsUrl+ '/' + supplierId); + + var response = await http.put(uri, body: json.encode(payload), headers: await buildRequestHeaders()); + if (response.statusCode == 200) { + return response.body; + } else if (response.statusCode == 401) { + bool status = await AppSettings.resetToken(); + if (status) { + response = await http.put(uri, body: json.encode(payload), headers: await buildRequestHeaders()); + if (response.statusCode == 200) { + return response.body; + } else { + return ''; + } + } else { + return ''; + } + } else { + return ''; + } + } + + static Future getDriverDetailsByPhone(String phone) async { + try { + var uri = Uri.parse("$getDriverDetailsByPhoneUrl/$phone"); + + var response = await http.get(uri, headers: await buildRequestHeaders()); + + if (response.statusCode == 200) { + final decoded = jsonDecode(response.body); + + if (decoded['data'] != null && + decoded['data'] is List && + decoded['data'].isNotEmpty) { + return DriversModel.fromJson(decoded['data'][0]); // ✅ Correct model + } else { + debugPrint("⚠️ No driver data found in response"); + return null; + } + } else if (response.statusCode == 401) { + bool status = await AppSettings.resetToken(); + if (status) { + response = await http.get(uri, headers: await buildRequestHeaders()); + if (response.statusCode == 200) { + final decoded = jsonDecode(response.body); + if (decoded['data'] != null && + decoded['data'] is List && + decoded['data'].isNotEmpty) { + return DriversModel.fromJson(decoded['data'][0]); // ✅ again + } + } + } + return null; + } else { + debugPrint("❌ Failed: ${response.statusCode}"); + return null; + } + } catch (e) { + debugPrint("⚠️ getDriverDetailsByPhone() error: $e"); + return null; + } + } + static Future getDrivers() async { var uri = Uri.parse(getDriversUrl+'/'+supplierId); //uri = uri.replace(query: 'supplierId=$supplierId'); @@ -754,7 +826,7 @@ class AppSettings{ var uri =Uri.parse(updateDriversUrl + '/' + supplierId); uri = uri.replace(query: 'phone=$phone'); - var response = await http.post(uri, body: json.encode(payload), headers: await buildRequestHeaders()); + var response = await http.put (uri, body: json.encode(payload), headers: await buildRequestHeaders()); if (response.statusCode == 200) { try { @@ -768,7 +840,7 @@ class AppSettings{ } else if (response.statusCode == 401) { bool status = await AppSettings.resetToken(); if (status) { - response = await http.post(uri, body: json.encode(payload), headers: await buildRequestHeaders()); + response = await http.put (uri, body: json.encode(payload), headers: await buildRequestHeaders()); if (response.statusCode == 200) { return true; } else { @@ -838,6 +910,42 @@ class AppSettings{ } } + static Future getSourceDetailsById(String dbId) async { + try { + var uri = Uri.parse("$getSourceDetailsByIdUrl/$dbId"); + var response = await http.get(uri, headers: await buildRequestHeaders()); + + if (response.statusCode == 200) { + final decoded = jsonDecode(response.body); + + if (decoded['data'] != null) { + return SourceLocationsModel.fromJson(decoded['data']); // ✅ correct + } else { + debugPrint("⚠️ No source data found in response"); + return null; + } + } else if (response.statusCode == 401) { + bool status = await AppSettings.resetToken(); + if (status) { + response = await http.get(uri, headers: await buildRequestHeaders()); + if (response.statusCode == 200) { + final decoded = jsonDecode(response.body); + if (decoded['data'] != null) { + return SourceLocationsModel.fromJson(decoded['data']); // ✅ again + } + } + } + return null; + } else { + debugPrint("❌ Failed: ${response.statusCode}"); + return null; + } + } catch (e) { + debugPrint("⚠️ getSourceDetailsById() error: $e"); + return null; + } + } + static Future addSourceLocations(payload) async { var uri = Uri.parse(addSourceLocationsUrl + '/' + supplierId); @@ -871,6 +979,69 @@ class AppSettings{ } } + static Future updateSourceLocations(payload,dbId) async { + + var uri =Uri.parse(updateSourceLocationsUrl + '/' + dbId); + + var response = await http.put (uri, body: json.encode(payload), headers: await buildRequestHeaders()); + + if (response.statusCode == 200) { + try { + var _response = json.decode(response.body); + print(_response); + return true; + } catch (e) { + // display error toast + return false; + } + } else if (response.statusCode == 401) { + bool status = await AppSettings.resetToken(); + if (status) { + response = await http.put (uri, body: json.encode(payload), headers: await buildRequestHeaders()); + if (response.statusCode == 200) { + return true; + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } + + static Future deleteSource(dbId) async { + + var uri = Uri.parse(deleteSourceUrl + '/' + dbId); + var response = await http.delete (uri, headers: await buildPutRequestHeaders()); + + if (response.statusCode == 200) { + try { + var _response = json.decode(response.body); + print(_response); + return true; + } catch (e) { + // display error toast + return false; + } + } else if (response.statusCode == 401) { + bool status = await AppSettings.resetToken(); + if (status) { + response = await http.delete (uri, headers: await buildPutRequestHeaders()); + if (response.statusCode == 200) { + return true; + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } + static Future setRatesDaily(payload) async { var uri = Uri.parse(setRatesDailyUrl + '/' + supplierId+'/update-prices'); diff --git a/lib/orders/all_orders.dart b/lib/orders/all_orders.dart index 90deaff..0540a65 100644 --- a/lib/orders/all_orders.dart +++ b/lib/orders/all_orders.dart @@ -641,6 +641,52 @@ class OrderCard extends StatelessWidget { ), + Visibility( + visible: st.toLowerCase() == 'completed', + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.asset( + 'images/avatar.png', + fit: BoxFit.cover, + width: 12, + height: 12, + ), + const SizedBox(width: 8), + + // Title + Chips + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + order.delivery_agent_name!=''?"Delivered by ${order.delivery_agent_name}":"Assigned to ${order.tanker_name}", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: fontTextStyle( + 8, const Color(0xFF646566), FontWeight.w400), + ), + ), + ], + ), + + ], + ), + ), + + ], + ), + ], + ) + + ), + ], ), ), diff --git a/lib/resources/driver_details.dart b/lib/resources/driver_details.dart index ce50dba..7d2159e 100644 --- a/lib/resources/driver_details.dart +++ b/lib/resources/driver_details.dart @@ -72,11 +72,11 @@ class _DriverDetailsPageState extends State { return match.isEmpty ? null : match; // return the exact option string } - Future _refreshTankerDetails() async { + Future _refreshDriverDetails() async { try { setState(() => isLoading = true); - final updatedDetails = await AppSettings.getTankerDetailsByName( + final updatedDetails = await AppSettings.getDriverDetailsByPhone( _mobileCtrl.text.trim(), ); @@ -85,11 +85,11 @@ class _DriverDetailsPageState extends State { widget.driverDetails = updatedDetails; }); } else { - AppSettings.longFailedToast("Failed to fetch updated tanker details"); + AppSettings.longFailedToast("Failed to fetch updated driver details"); } } catch (e) { - debugPrint("⚠️ Error refreshing tanker details: $e"); - AppSettings.longFailedToast("Error refreshing tanker details"); + debugPrint("⚠️ Error refreshing driver details: $e"); + AppSettings.longFailedToast("Error refreshing driver details"); } finally { setState(() => isLoading = false); } @@ -105,7 +105,7 @@ class _DriverDetailsPageState extends State { // Build payload (adjust keys to your API if needed) final payload = { - "Name": _nameCtrl.text.trim(), + "name": _nameCtrl.text.trim(), "license_number": selectedLicense ?? "", "address": _locationCtrl.text.trim().isEmpty ? AppSettings.userAddress @@ -124,7 +124,7 @@ class _DriverDetailsPageState extends State { if (created) { AppSettings.longSuccessToast("Driver Updated successfully"); Navigator.pop(context, true); // close sheet - _refreshTankerDetails(); + _refreshDriverDetails(); } else { Navigator.pop(context, true); AppSettings.longFailedToast("failed to update driver details"); @@ -145,14 +145,7 @@ class _DriverDetailsPageState extends State { selectedExperience = fitToOption(widget.driverDetails.years_of_experience, yearOptions); selectedLicense = fitToOption(widget.driverDetails.license_number, licenseNumbers); _status = fitToOption(widget.driverDetails.status, _statusOptions); - /* _capacityCtrl.text = widget.tankerDetails.capacity ?? ''; - _plateCtrl.text = widget.tankerDetails.license_plate ?? ''; - _mfgYearCtrl.text = widget.tankerDetails.manufacturing_year ?? ''; - _insExpiryCtrl.text = widget.tankerDetails.insurance_expiry ?? ''; - - selectedExperience = fitToOption(widget.tankerDetails.tanker_type, tankerTypes); - selectedTypeOfWater = fitToOption(widget.tankerDetails.type_of_water, typeOfWater); - */ + await showModalBottomSheet( context: context, isScrollControlled: true, @@ -472,7 +465,7 @@ class _DriverDetailsPageState extends State { } else{ Navigator.of(context).pop(true); - AppSettings.longFailedToast('Failed to delete tanker'); + AppSettings.longFailedToast('Failed to delete driver'); } @@ -526,7 +519,6 @@ class _DriverDetailsPageState extends State { return WillPopScope( onWillPop: () async { - // ✅ Return true to indicate successful tanker update refresh Navigator.pop(context, true); return false; // prevent default pop since we manually handled it }, diff --git a/lib/resources/drivers_model.dart b/lib/resources/drivers_model.dart index 08f4c8e..d8d5bdd 100644 --- a/lib/resources/drivers_model.dart +++ b/lib/resources/drivers_model.dart @@ -12,18 +12,17 @@ class DriversModel { String license_number=''; DriversModel(); - factory DriversModel.fromJson(Map json){ - DriversModel rtvm = new DriversModel(); + factory DriversModel.fromJson(Map json) { + DriversModel rtvm = DriversModel(); - - rtvm.supplier_name = json['supplier_name'] ?? ''; - rtvm.driver_name = json['name'] ?? ''; - rtvm.status = json['status'] ?? ''; - rtvm.address = json['address'] ?? ''; - rtvm.phone_number =json['phone'] ?? ''; - rtvm.alt_phone_number =json['alternativeContactNumber'] ?? ''; + rtvm.supplier_name = json['suppliername'] ?? ''; + rtvm.driver_name = json['name'] ?? ''; // ✅ Correct + rtvm.status = json['status'] ?? ''; + rtvm.address = json['address'] ?? ''; + rtvm.phone_number = json['phone'] ?? ''; + rtvm.alt_phone_number = json['alternativeContactNumber'] ?? ''; rtvm.years_of_experience = json['years_of_experience'] ?? ''; - rtvm.license_number = json['license_number'] ?? ''; + rtvm.license_number = json['license_number'] ?? ''; return rtvm; } } \ No newline at end of file diff --git a/lib/resources/resources_sources.dart b/lib/resources/resources_sources.dart index fee1866..31e1441 100644 --- a/lib/resources/resources_sources.dart +++ b/lib/resources/resources_sources.dart @@ -601,13 +601,19 @@ class _ResourcesSourceScreenState extends State { itemBuilder: (context, idx) { final it = filtered[idx]; return GestureDetector( - onTap: (){ - Navigator.push( + onTap: ()async{ + + final result = await Navigator.push( context, MaterialPageRoute( - builder: (context) => SourceDetailsScreen(sourceDetails: it), + builder: (context) => + SourceDetailsScreen( + sourceDetails: it), ), ); + if (result == true) { + _fetchSources(); + } }, child: TankCard( title: it.source_name, diff --git a/lib/resources/source_loctaions_model.dart b/lib/resources/source_loctaions_model.dart index 487f997..82c2078 100644 --- a/lib/resources/source_loctaions_model.dart +++ b/lib/resources/source_loctaions_model.dart @@ -1,24 +1,49 @@ -class SourceLocationsModel { - String source_name=''; - String supplierId=''; - String status=''; - String address=''; - String deliveries='13'; - String commision=''; - String phone=''; - List availability= ['filled', 'available']; +class SourceLocationsModel { + String source_name = ''; + String supplierId = ''; + String status = ''; + String address = ''; + String deliveries = '13'; + String commision = ''; + String phone = ''; + String dbId = ''; + String water_type = ''; + double longitude = 0.0; + double latitude = 0.0; + SourceLocationsModel(); - factory SourceLocationsModel.fromJson(Map json){ - SourceLocationsModel rtvm = new SourceLocationsModel(); + factory SourceLocationsModel.fromJson(Map json) { + SourceLocationsModel rtvm = SourceLocationsModel(); + + rtvm.source_name = json['location_name'] ?? ''; + rtvm.dbId = json['_id'] ?? ''; + rtvm.supplierId = json['supplierId'] ?? ''; + rtvm.status = json['status'] ?? ''; + rtvm.address = json['address'] ?? ''; + rtvm.phone = json['phone'] ?? ''; + rtvm.water_type = json['water_type'] ?? ''; + + // ✅ Safely handle both int and double types + var lon = json['longitude']; + var lat = json['latitude']; + if (lon is int) { + rtvm.longitude = lon.toDouble(); + } else if (lon is double) { + rtvm.longitude = lon; + } else { + rtvm.longitude = 0.0; + } - rtvm.source_name = json['location_name'] ?? ''; - rtvm.supplierId = json['supplierId'] ?? ''; - rtvm.status = json['status'] ?? ''; - rtvm.address = json['address'] ?? ''; - rtvm.phone = json['phone'] ?? ''; + if (lat is int) { + rtvm.latitude = lat.toDouble(); + } else if (lat is double) { + rtvm.latitude = lat; + } else { + rtvm.latitude = 0.0; + } return rtvm; } -} \ No newline at end of file +} diff --git a/lib/resources/sourcelocation_details.dart b/lib/resources/sourcelocation_details.dart index 5374ddc..df1f408 100644 --- a/lib/resources/sourcelocation_details.dart +++ b/lib/resources/sourcelocation_details.dart @@ -1,7 +1,18 @@ import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; - +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:supplier_new/resources/source_loctaions_model.dart'; +import '../common/first_char_upper.dart'; import '../common/settings.dart'; +import '../google_maps_place_picker_mb/src/models/pick_result.dart'; +import 'dart:io' show Platform; +import 'package:flutter/services.dart'; +import '../common/keys.dart'; +import '../google_maps_place_picker_mb/src/place_picker.dart'; +import 'package:supplier_new/google_maps_place_picker_mb/google_maps_place_picker.dart'; +import 'dart:io' show File, Platform; +import 'package:google_maps_flutter_android/google_maps_flutter_android.dart'; +import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; +import 'package:location/location.dart' as locationmap; class SourceDetailsScreen extends StatefulWidget { var sourceDetails; @@ -12,6 +23,37 @@ class SourceDetailsScreen extends StatefulWidget { State createState() => _SourceDetailsScreenState(); } +PickResult? selectedPlace; + +bool _mapsInitialized = false; +final String _mapsRenderer = "latest"; + +var kInitialPosition = const LatLng(15.462477, 78.717401); + +locationmap.Location location = locationmap.Location(); + +final GoogleMapsFlutterPlatform mapsImplementation = + GoogleMapsFlutterPlatform.instance; + +void initRenderer() { + if (_mapsInitialized) return; + if (mapsImplementation is GoogleMapsFlutterAndroid) { + switch (_mapsRenderer) { + case "legacy": + (mapsImplementation as GoogleMapsFlutterAndroid) + .initializeWithRenderer(AndroidMapRenderer.legacy); + break; + case "latest": + (mapsImplementation as GoogleMapsFlutterAndroid) + .initializeWithRenderer(AndroidMapRenderer.latest); + break; + } + } + // setState(() { + // _mapsInitialized = true; + // }); +} + class _SourceDetailsScreenState extends State { final List> recentTrips = [ {"type": "Drinking Water", "liters": "10,000 L", "time": "7:02 PM", "date": "28 Jun 2025"}, @@ -24,40 +66,447 @@ class _SourceDetailsScreenState extends State { double _cardTPad = 16; // top inner padding double _avatarOverlap = 28; // how much avatar rises above the card - void _showMenu() { - showModalBottomSheet( + String search = ''; + bool isLoading = false; + List sourceLocationsList = []; + + // ---------- Form state (bottom sheet) ---------- + final _formKey = GlobalKey(); + final _locationNameController = TextEditingController(); + final _mobileCtrl = TextEditingController(); + + bool addBusinessAsSource = false; // toggle if you want to hide map input + String? selectedWaterType; + final List waterTypes = const ['Drinking water', 'Bore water', 'Both']; + + double? lat; + double? lng; + String? address; + + @override + void initState() { + // TODO: implement initState + super.initState(); + lat=widget.sourceDetails.latitude; + lng=widget.sourceDetails.longitude; + + } + + String? _required(String? v, {String field = "This field"}) { + if (v == null || v.trim().isEmpty) return "$field is required"; + return null; + } + + String? _validatePhone(String? v, {String label = "Phone"}) { + if (v == null || v.trim().isEmpty) return "$label is required"; + if (v.trim().length != 10) return "$label must be 10 digits"; + return null; + } + + String? fitToOption(String? incoming, List 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; // return the exact option string + } + + Future _refreshSourceDetails() async { + try { + setState(() => isLoading = true); + + final updatedDetails = await AppSettings.getSourceDetailsById( + widget.sourceDetails.dbId, + ); + + if (updatedDetails != null) { + setState(() { + widget.sourceDetails = updatedDetails; + }); + } else { + AppSettings.longFailedToast("Failed to fetch updated driver details"); + } + } catch (e) { + debugPrint("⚠️ Error refreshing driver details: $e"); + AppSettings.longFailedToast("Error refreshing driver details"); + } finally { + setState(() => isLoading = false); + } + } + + Future _updateSourceLocation() async { + // run all validators, including the dropdowns + final ok = _formKey.currentState?.validate() ?? false; + if (!ok) { + setState(() {}); // ensure error texts render + return; + } + + final payload = { + "location_name": _locationNameController.text.trim(), + "phone": _mobileCtrl.text.trim(), + "water_type": selectedWaterType, + "status": 'active', // or whatever string your backend expects + "address": address, + "city": '', + "state": '', + "zip": '', + "latitude": lat, + "longitude": lng, + }; + try { + final bool created = await AppSettings.updateSourceLocations(payload, widget.sourceDetails.dbId); + if (!mounted) return; + + if (created) { + AppSettings.longSuccessToast("Source Updated successfully"); + Navigator.pop(context, true); // close sheet + _refreshSourceDetails(); + } else { + Navigator.pop(context, true); + AppSettings.longFailedToast("failed to update Source details"); + } + } catch (e) { + debugPrint("⚠️ Source error: $e"); + if (!mounted) return; + AppSettings.longFailedToast("Something went wrong"); + } + } + + Future _openSourceFormSheet(BuildContext context)async { + if (widget.sourceDetails != null) { + _locationNameController.text = widget.sourceDetails.source_name ?? ''; + _mobileCtrl.text = widget.sourceDetails.phone ?? ''; + selectedWaterType = fitToOption(widget.sourceDetails.water_type, waterTypes); + address = widget.sourceDetails.address ?? ''; + lat = widget.sourceDetails.latitude; + lng = widget.sourceDetails.longitude; + } else { + _locationNameController.clear(); + _mobileCtrl.clear(); + selectedWaterType = null; + address = null; + lat = null; + lng = null; + } + await showModalBottomSheet( context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(16)), - ), + isScrollControlled: true, + backgroundColor: Colors.transparent, builder: (context) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - leading: const Icon(Icons.edit, color: Colors.black), - title: const Text('Edit'), - onTap: () => Navigator.pop(context), - ), - ListTile( - leading: const Icon(Icons.block, color: Colors.black), - title: const Text('Disable'), - onTap: () => Navigator.pop(context), - ), - ListTile( - leading: const Icon(Icons.delete_outline, color: Colors.red), - title: const Text('Delete', style: TextStyle(color: Colors.red)), - onTap: () => Navigator.pop(context), + final kbInset = MediaQuery.of(context).viewInsets.bottom; + return FractionallySizedBox( + heightFactor: 0.75, // fixed height + child: Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + child: Padding( + padding: EdgeInsets.fromLTRB(20, 16, 20, 20 + kbInset), + 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: "Location Name *", + child: TextFormField( + controller: _locationNameController, + validator: (v) => _required(v, field: "Location Name"), + textCapitalization: TextCapitalization.none, + inputFormatters: const [ + FirstCharUppercaseFormatter(), // << live first-letter caps + ], + decoration: InputDecoration( + hintText: "Location Name", + hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400), + border: const OutlineInputBorder(), + isDense: true, + ), + textInputAction: TextInputAction.next, + ), + ), + + _LabeledField( + label: "Mobile Number *", + child: TextFormField( + controller: _mobileCtrl, + validator: (v) => _validatePhone(v, label: "Mobile Number"), + keyboardType: TextInputType.phone, + decoration: InputDecoration( + hintText: "Mobile Number", + hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400), + border: const OutlineInputBorder(), + isDense: true, + ), + textInputAction: TextInputAction.next, + ), + ), + + Visibility( + visible: !addBusinessAsSource, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox( + width: double.infinity, + child: OutlinedButton.icon( + onPressed: () { + location.serviceEnabled().then((value) { + if (value) { + initRenderer(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return PlacePicker( + resizeToAvoidBottomInset: false, + hintText: "Find a place ...", + searchingText: "Please wait ...", + selectText: "Select place", + outsideOfPickAreaText: "Place not in area", + initialPosition: (lat != null && lng != null) + ? LatLng(lat!, lng!) + : kInitialPosition, + useCurrentLocation: (lat == null || lng == null), + selectInitialPosition: true, + usePinPointingSearch: true, + usePlaceDetailSearch: true, + zoomGesturesEnabled: true, + zoomControlsEnabled: true, + onMapCreated: (GoogleMapController controller) {}, + onPlacePicked: (PickResult result) { + setState(() { + selectedPlace = result; + lat = selectedPlace!.geometry!.location.lat; + lng = selectedPlace!.geometry!.location.lng; + if (selectedPlace!.types!.length == 1) { + address = selectedPlace!.formattedAddress!; + } else { + address = + '${selectedPlace!.name!}, ${selectedPlace!.formattedAddress!}'; + } + Navigator.of(context).pop(); + }); + }, + onMapTypeChanged: (MapType mapType) {}, + apiKey: Platform.isAndroid + ? APIKeys.androidApiKey + : APIKeys.iosApiKey, + forceAndroidLocationManager: true, + ); + }, + ), + ); + } else { + // same dialog code ... + } + }); + }, + label: Text( + "Change Location on map", + style: fontTextStyle(14, const Color(0xFF646566), FontWeight.w600), + ), + ), + ), + const SizedBox(height: 12), + ], + ), + ), + + _LabeledField( + label: "Water Type *", + child: DropdownButtonFormField( + value: selectedWaterType, + items: waterTypes + .map((w) => DropdownMenuItem(value: w, child: Text(w))) + .toList(), + onChanged: (v) => setState(() => selectedWaterType = v), + validator: (v) => v == null || v.isEmpty ? "Water Type is required" : null, + isExpanded: true, + alignment: Alignment.centerLeft, + hint: Text( + "Select Water 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), + ), + ), + ), + + const SizedBox(height: 20), + + // Actions + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + 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: (){ + _updateSourceLocation(); + }, + child: Text( + "Update", + style: fontTextStyle(14, Colors.white, FontWeight.w600), + ), + ), + ), + ], + ), + ], + ), + ), ), - ], + ), ), ); }, ); } + showDeleteSourceDialog(BuildContext context) async { + + return showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return AlertDialog( + backgroundColor: Color(0XFFFFFFFF),// Set your desired background color + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), // Optional: Rounded corners + ), + title: Center( + child: Text('Delete Driver?' ,style: fontTextStyle(16,Color(0XFF3B3B3B),FontWeight.w600),), + ), + content: SingleChildScrollView( + child: ListBody( + children: [ + Container( + child: Text('Do u want to delete "${widget.sourceDetails.source_name}"',style: fontTextStyle(14,Color(0XFF101214),FontWeight.w600),), + ), + ], + ), + ), + actions: [ + 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: Visibility( + visible: true, + 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.deleteSource(widget.sourceDetails.dbId,); + if(status){ + AppSettings.longSuccessToast('Source deleted successfully'); + Navigator.of(context).pop(true); + Navigator.of(context).pop(true); + } + else{ + Navigator.of(context).pop(true); + AppSettings.longFailedToast('Failed to delete Source'); + } + + + }, + 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: Visibility( + visible: true, + child: Padding( + padding: EdgeInsets.fromLTRB(16,12,16,12), + child: Text( + 'Delete', + style: fontTextStyle( + 12, + Color(0XFFFFFFFF), + FontWeight.w600)), + ), + ), + ) + ),) + + + ], + ), + ), + ], + ); + }); + }, + ); + } + @override Widget build(BuildContext context) { return WillPopScope( @@ -125,7 +574,7 @@ class _SourceDetailsScreenState extends State { const SizedBox(height: 2), Text( - "+91 "+widget.sourceDetails.phone, + widget.sourceDetails.address, style: fontTextStyle( 12, Color(0XFF646566), FontWeight.w400), ), @@ -150,13 +599,13 @@ class _SourceDetailsScreenState extends State { elevation: 4, onSelected: (value) { if (value == 'edit') { - //_openDriverFormSheet(context); + _openSourceFormSheet(context); } else if (value == 'disable') { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Disable selected')), ); } else if (value == 'delete') { - //showDeleteDriverDialog(context); + showDeleteSourceDialog(context); } }, itemBuilder: (context) => [ @@ -257,29 +706,6 @@ class _SourceDetailsScreenState extends State { ), const SizedBox(height: 12), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(widget.sourceDetails.source_name, - style: fontTextStyle(18, Colors.black, FontWeight.w600)), - const SizedBox(height: 4), - Text("Drinking Water", - style: fontTextStyle(14, const Color(0xFF8270DB), FontWeight.w500)), - const SizedBox(height: 4), - Text("+91 789456212", - style: fontTextStyle(14, Colors.black, FontWeight.w400)), - const SizedBox(height: 4), - Text( - "Road No. 12, Krishnadwar Layout,\nGandipet, Hyderabad, 500065", - style: fontTextStyle(13, const Color(0xFF656565), FontWeight.w400), - ), - ], - ), - ), - - const SizedBox(height: 20), // Filling & Wait time cards Padding( @@ -352,25 +778,32 @@ class _SourceDetailsScreenState extends State { child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: const Color(0xFFF7F7F7), - borderRadius: BorderRadius.circular(12), + color: const Color(0xFFFFFFFF), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Color(0XFFC3C4C4)), ), child: Row( children: [ - const Icon(Icons.water_drop, color: Color(0xFF8270DB)), + Image.asset('images/recent_trips.png', width: 28, height: 28), const SizedBox(width: 12), Expanded( - child: Text( - "${trip['type']} - ${trip['liters']}", - style: fontTextStyle( - 14, const Color(0xFF2D2E30), FontWeight.w500), - ), - ), - Text( - "${trip['time']}, ${trip['date']}", - style: fontTextStyle( - 12, const Color(0xFF656565), FontWeight.w400), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${trip['type']} - ${trip['liters']}", + style: fontTextStyle( + 14, const Color(0xFF2D2E30), FontWeight.w500), + ), + Text( + "${trip['time']}, ${trip['date']}", + style: fontTextStyle( + 12, const Color(0xFF656565), FontWeight.w400), + ), + ], + ) ), + ], ), ), @@ -381,3 +814,34 @@ class _SourceDetailsScreenState extends State { ],)))); } } +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, + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/resources/tanker_details.dart b/lib/resources/tanker_details.dart index 392f5ab..428c8a5 100644 --- a/lib/resources/tanker_details.dart +++ b/lib/resources/tanker_details.dart @@ -1,5 +1,8 @@ +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'; @@ -72,9 +75,11 @@ class _TankerDetailsPageState extends State { int selectedTab = 0; String search = ''; bool isLoading = false; + bool isTankerTripsLoading=false; bool isStatusLoading = false; String? currentAvailability; + List tankerTripsList = []; @override void initState() { @@ -87,6 +92,8 @@ class _TankerDetailsPageState extends State { final a = widget.tankerDetails.availability; currentAvailability = "${a[0]}|${a[1]}"; + _fetchTankerTrips(); + // ✅ Automatically show dropdown bottom sheet after short delay /*WidgetsBinding.instance.addPostFrameCallback((_) { _showDropdownBottomSheet(defaultValue: currentAvailability); @@ -94,6 +101,28 @@ class _TankerDetailsPageState extends State { } } + Future _fetchTankerTrips() async { + setState(() => isTankerTripsLoading = true); + try { + var payload = new Map(); + 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); + } + } + // Dropdown options: fill + availability combined final List options = [ "empty|available", @@ -1033,6 +1062,51 @@ class _TankerDetailsPageState extends State { ), ), + Expanded( + 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, + separatorBuilder: (_, __) => const SizedBox(height: 10), + itemBuilder: (context, idx) { + final it = tankerTripsList[idx]; + return GestureDetector( + onTap: () async{ + /*final result = await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => TankerDetailsPage(tankerDetails: it), + ), + ); + if (result == true) { + _fetchTankers(); + }*/ + }, + child: _buildTripCard( + driverName: it.driver_name, + time: "7:02 PM, 28 Jun 2025", + from: "Bachupally Filling Station", + to: "Akriti Heights", + ), + ); + }, + )), + ), + ], ), ), diff --git a/lib/resources/tanker_trips_model.dart b/lib/resources/tanker_trips_model.dart new file mode 100644 index 0000000..b17cae8 --- /dev/null +++ b/lib/resources/tanker_trips_model.dart @@ -0,0 +1,29 @@ +class TankerTripsModel { + String tanker_name = ''; + String address = ''; + String dbId = ''; + String driver_name = ''; + String status = ''; + String building_name = ''; + String water_source_location=''; + String deliveredDate=''; + + TankerTripsModel(); + + factory TankerTripsModel.fromJson(Map json){ + TankerTripsModel rtvm = new TankerTripsModel(); + + rtvm.tanker_name = json['tankerName']?? ''; + rtvm.dbId = json['_id']?? ''; + rtvm.address = json['supplier_address']?? ''; + rtvm.driver_name = json['delivery_agent']?? ''; + rtvm.status = json['orderStatus']?? ''; + rtvm.building_name = json['buildingName']?? ''; + rtvm.water_source_location = json['water_source_location']?? ''; + rtvm.deliveredDate = json['deliveredDate']?? ''; + return rtvm; + } + Map toJson() => { + "boreName":this.tanker_name, + }; +} \ No newline at end of file