changes in resources page

master
Sneha 1 month ago
parent ac734007af
commit 3fae53a7d2

@ -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,
);
}
}

@ -8,7 +8,9 @@ import 'package:fluttertoast/fluttertoast.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:supplier_new/common/preloader.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'; import '../resources/tankers_model.dart';
const Color primaryColor = Color(0XFF8270DB); const Color primaryColor = Color(0XFF8270DB);
@ -163,16 +165,22 @@ class AppSettings{
static String updateTankerAvailabilityUrl = host + 'updatetankerstatus'; static String updateTankerAvailabilityUrl = host + 'updatetankerstatus';
static String deleteTankerUrl = host + 'deleteTanker'; static String deleteTankerUrl = host + 'deleteTanker';
static String updateTankerUrl = host + 'updateTankers'; static String updateTankerUrl = host + 'updateTankers';
static String getTankerTripsUrl = host + 'tankerAccounts';
static String getDriversUrl = host + 'getalldeliveryboys'; static String getDriversUrl = host + 'getalldeliveryboys';
static String addDriversUrl = host + 'addDeliveryboys'; static String addDriversUrl = host + 'addDeliveryboys';
static String updateDriversUrl = host + 'updatedeliveryboy'; static String updateDriversUrl = host + 'updatedeliveryboy';
static String deleteDriverUrl = host + 'deletedeliveryboy'; static String deleteDriverUrl = host + 'deletedeliveryboy';
static String addSourceLocationsUrl = host + 'addSource'; static String addSourceLocationsUrl = host + 'addSource';
static String updateSourceLocationsUrl = host + 'sources';
static String deleteSourceUrl = host + 'sources';
static String getSourceLoctaionsUrl = host + 'getallsourcesofsupplier'; static String getSourceLoctaionsUrl = host + 'getallsourcesofsupplier';
static String getSourceDetailsByIdUrl = host + 'sources';
static String setRatesDailyUrl = host + 'tankers'; static String setRatesDailyUrl = host + 'tankers';
static String getSupplierDetailsUrl = host + 'suppliers'; static String getSupplierDetailsUrl = host + 'suppliers';
static String updatePumpFeeUrl = host + 'suppliers'; static String updatePumpFeeUrl = host + 'suppliers';
static String assignTankerUrl = host + 'assign-deliveryboy-tanker'; static String assignTankerUrl = host + 'assign-deliveryboy-tanker';
static String getDriverDetailsByPhoneUrl = host + 'getsingledeliveryboydetails';
@ -694,6 +702,70 @@ class AppSettings{
} }
} }
static Future<String> 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<DriversModel?> 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<String> getDrivers() async { static Future<String> getDrivers() async {
var uri = Uri.parse(getDriversUrl+'/'+supplierId); var uri = Uri.parse(getDriversUrl+'/'+supplierId);
//uri = uri.replace(query: 'supplierId=$supplierId'); //uri = uri.replace(query: 'supplierId=$supplierId');
@ -754,7 +826,7 @@ class AppSettings{
var uri =Uri.parse(updateDriversUrl + '/' + supplierId); var uri =Uri.parse(updateDriversUrl + '/' + supplierId);
uri = uri.replace(query: 'phone=$phone'); 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) { if (response.statusCode == 200) {
try { try {
@ -768,7 +840,7 @@ class AppSettings{
} else if (response.statusCode == 401) { } else if (response.statusCode == 401) {
bool status = await AppSettings.resetToken(); bool status = await AppSettings.resetToken();
if (status) { 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) { if (response.statusCode == 200) {
return true; return true;
} else { } else {
@ -838,6 +910,42 @@ class AppSettings{
} }
} }
static Future<SourceLocationsModel?> 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<bool> addSourceLocations(payload) async { static Future<bool> addSourceLocations(payload) async {
var uri = Uri.parse(addSourceLocationsUrl + '/' + supplierId); var uri = Uri.parse(addSourceLocationsUrl + '/' + supplierId);
@ -871,6 +979,69 @@ class AppSettings{
} }
} }
static Future<bool> 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<bool> 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<bool> setRatesDaily(payload) async { static Future<bool> setRatesDaily(payload) async {
var uri = Uri.parse(setRatesDailyUrl + '/' + supplierId+'/update-prices'); var uri = Uri.parse(setRatesDailyUrl + '/' + supplierId+'/update-prices');

@ -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),
),
),
],
),
],
),
),
],
),
],
)
),
], ],
), ),
), ),

@ -72,11 +72,11 @@ class _DriverDetailsPageState extends State<DriverDetailsPage> {
return match.isEmpty ? null : match; // return the exact option string return match.isEmpty ? null : match; // return the exact option string
} }
Future<void> _refreshTankerDetails() async { Future<void> _refreshDriverDetails() async {
try { try {
setState(() => isLoading = true); setState(() => isLoading = true);
final updatedDetails = await AppSettings.getTankerDetailsByName( final updatedDetails = await AppSettings.getDriverDetailsByPhone(
_mobileCtrl.text.trim(), _mobileCtrl.text.trim(),
); );
@ -85,11 +85,11 @@ class _DriverDetailsPageState extends State<DriverDetailsPage> {
widget.driverDetails = updatedDetails; widget.driverDetails = updatedDetails;
}); });
} else { } else {
AppSettings.longFailedToast("Failed to fetch updated tanker details"); AppSettings.longFailedToast("Failed to fetch updated driver details");
} }
} catch (e) { } catch (e) {
debugPrint("⚠️ Error refreshing tanker details: $e"); debugPrint("⚠️ Error refreshing driver details: $e");
AppSettings.longFailedToast("Error refreshing tanker details"); AppSettings.longFailedToast("Error refreshing driver details");
} finally { } finally {
setState(() => isLoading = false); setState(() => isLoading = false);
} }
@ -105,7 +105,7 @@ class _DriverDetailsPageState extends State<DriverDetailsPage> {
// Build payload (adjust keys to your API if needed) // Build payload (adjust keys to your API if needed)
final payload = <String, dynamic>{ final payload = <String, dynamic>{
"Name": _nameCtrl.text.trim(), "name": _nameCtrl.text.trim(),
"license_number": selectedLicense ?? "", "license_number": selectedLicense ?? "",
"address": _locationCtrl.text.trim().isEmpty "address": _locationCtrl.text.trim().isEmpty
? AppSettings.userAddress ? AppSettings.userAddress
@ -124,7 +124,7 @@ class _DriverDetailsPageState extends State<DriverDetailsPage> {
if (created) { if (created) {
AppSettings.longSuccessToast("Driver Updated successfully"); AppSettings.longSuccessToast("Driver Updated successfully");
Navigator.pop(context, true); // close sheet Navigator.pop(context, true); // close sheet
_refreshTankerDetails(); _refreshDriverDetails();
} else { } else {
Navigator.pop(context, true); Navigator.pop(context, true);
AppSettings.longFailedToast("failed to update driver details"); AppSettings.longFailedToast("failed to update driver details");
@ -145,14 +145,7 @@ class _DriverDetailsPageState extends State<DriverDetailsPage> {
selectedExperience = fitToOption(widget.driverDetails.years_of_experience, yearOptions); selectedExperience = fitToOption(widget.driverDetails.years_of_experience, yearOptions);
selectedLicense = fitToOption(widget.driverDetails.license_number, licenseNumbers); selectedLicense = fitToOption(widget.driverDetails.license_number, licenseNumbers);
_status = fitToOption(widget.driverDetails.status, _statusOptions); _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( await showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
@ -472,7 +465,7 @@ class _DriverDetailsPageState extends State<DriverDetailsPage> {
} }
else{ else{
Navigator.of(context).pop(true); 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<DriverDetailsPage> {
return WillPopScope( return WillPopScope(
onWillPop: () async { onWillPop: () async {
// Return true to indicate successful tanker update refresh
Navigator.pop(context, true); Navigator.pop(context, true);
return false; // prevent default pop since we manually handled it return false; // prevent default pop since we manually handled it
}, },

@ -12,18 +12,17 @@ class DriversModel {
String license_number=''; String license_number='';
DriversModel(); DriversModel();
factory DriversModel.fromJson(Map<String, dynamic> json){ factory DriversModel.fromJson(Map<String, dynamic> json) {
DriversModel rtvm = new DriversModel(); DriversModel rtvm = DriversModel();
rtvm.supplier_name = json['suppliername'] ?? '';
rtvm.supplier_name = json['supplier_name'] ?? ''; rtvm.driver_name = json['name'] ?? ''; // Correct
rtvm.driver_name = json['name'] ?? ''; rtvm.status = json['status'] ?? '';
rtvm.status = json['status'] ?? ''; rtvm.address = json['address'] ?? '';
rtvm.address = json['address'] ?? ''; rtvm.phone_number = json['phone'] ?? '';
rtvm.phone_number =json['phone'] ?? ''; rtvm.alt_phone_number = json['alternativeContactNumber'] ?? '';
rtvm.alt_phone_number =json['alternativeContactNumber'] ?? '';
rtvm.years_of_experience = json['years_of_experience'] ?? ''; rtvm.years_of_experience = json['years_of_experience'] ?? '';
rtvm.license_number = json['license_number'] ?? ''; rtvm.license_number = json['license_number'] ?? '';
return rtvm; return rtvm;
} }
} }

@ -601,13 +601,19 @@ class _ResourcesSourceScreenState extends State<ResourcesSourceScreen> {
itemBuilder: (context, idx) { itemBuilder: (context, idx) {
final it = filtered[idx]; final it = filtered[idx];
return GestureDetector( return GestureDetector(
onTap: (){ onTap: ()async{
Navigator.push(
final result = await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => SourceDetailsScreen(sourceDetails: it), builder: (context) =>
SourceDetailsScreen(
sourceDetails: it),
), ),
); );
if (result == true) {
_fetchSources();
}
}, },
child: TankCard( child: TankCard(
title: it.source_name, title: it.source_name,

@ -1,23 +1,48 @@
class SourceLocationsModel { class SourceLocationsModel {
String source_name=''; String source_name = '';
String supplierId=''; String supplierId = '';
String status=''; String status = '';
String address=''; String address = '';
String deliveries='13'; String deliveries = '13';
String commision=''; String commision = '';
String phone=''; String phone = '';
List<String> availability= ['filled', 'available']; String dbId = '';
String water_type = '';
double longitude = 0.0;
double latitude = 0.0;
SourceLocationsModel(); SourceLocationsModel();
factory SourceLocationsModel.fromJson(Map<String, dynamic> json){ factory SourceLocationsModel.fromJson(Map<String, dynamic> json) {
SourceLocationsModel rtvm = new SourceLocationsModel(); 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'] ?? ''; if (lat is int) {
rtvm.supplierId = json['supplierId'] ?? ''; rtvm.latitude = lat.toDouble();
rtvm.status = json['status'] ?? ''; } else if (lat is double) {
rtvm.address = json['address'] ?? ''; rtvm.latitude = lat;
rtvm.phone = json['phone'] ?? ''; } else {
rtvm.latitude = 0.0;
}
return rtvm; return rtvm;
} }

@ -1,7 +1,18 @@
import 'package:flutter/material.dart'; 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 '../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 { class SourceDetailsScreen extends StatefulWidget {
var sourceDetails; var sourceDetails;
@ -12,6 +23,37 @@ class SourceDetailsScreen extends StatefulWidget {
State<SourceDetailsScreen> createState() => _SourceDetailsScreenState(); State<SourceDetailsScreen> 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<SourceDetailsScreen> { class _SourceDetailsScreenState extends State<SourceDetailsScreen> {
final List<Map<String, dynamic>> recentTrips = [ final List<Map<String, dynamic>> recentTrips = [
{"type": "Drinking Water", "liters": "10,000 L", "time": "7:02 PM", "date": "28 Jun 2025"}, {"type": "Drinking Water", "liters": "10,000 L", "time": "7:02 PM", "date": "28 Jun 2025"},
@ -24,40 +66,447 @@ class _SourceDetailsScreenState extends State<SourceDetailsScreen> {
double _cardTPad = 16; // top inner padding double _cardTPad = 16; // top inner padding
double _avatarOverlap = 28; // how much avatar rises above the card double _avatarOverlap = 28; // how much avatar rises above the card
void _showMenu() { String search = '';
showModalBottomSheet( bool isLoading = false;
List<SourceLocationsModel> sourceLocationsList = [];
// ---------- Form state (bottom sheet) ----------
final _formKey = GlobalKey<FormState>();
final _locationNameController = TextEditingController();
final _mobileCtrl = TextEditingController();
bool addBusinessAsSource = false; // toggle if you want to hide map input
String? selectedWaterType;
final List<String> 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<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; // return the exact option string
}
Future<void> _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<void> _updateSourceLocation() async {
// run all validators, including the dropdowns
final ok = _formKey.currentState?.validate() ?? false;
if (!ok) {
setState(() {}); // ensure error texts render
return;
}
final payload = <String, dynamic>{
"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<void> _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, context: context,
shape: const RoundedRectangleBorder( isScrollControlled: true,
borderRadius: BorderRadius.vertical(top: Radius.circular(16)), backgroundColor: Colors.transparent,
),
builder: (context) { builder: (context) {
return Padding( final kbInset = MediaQuery.of(context).viewInsets.bottom;
padding: const EdgeInsets.symmetric(vertical: 16.0), return FractionallySizedBox(
child: Column( heightFactor: 0.75, // fixed height
mainAxisSize: MainAxisSize.min, child: Container(
children: [ decoration: const BoxDecoration(
ListTile( color: Colors.white,
leading: const Icon(Icons.edit, color: Colors.black), borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
title: const Text('Edit'), ),
onTap: () => Navigator.pop(context), child: Padding(
), padding: EdgeInsets.fromLTRB(20, 16, 20, 20 + kbInset),
ListTile( child: Form(
leading: const Icon(Icons.block, color: Colors.black), key: _formKey,
title: const Text('Disable'), child: SingleChildScrollView(
onTap: () => Navigator.pop(context), child: Column(
), crossAxisAlignment: CrossAxisAlignment.stretch,
ListTile( children: [
leading: const Icon(Icons.delete_outline, color: Colors.red), Row(
title: const Text('Delete', style: TextStyle(color: Colors.red)), children: [
onTap: () => Navigator.pop(context), 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<String>(
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: <Widget>[
Container(
child: Text('Do u want to delete "${widget.sourceDetails.source_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: 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return WillPopScope( return WillPopScope(
@ -125,7 +574,7 @@ class _SourceDetailsScreenState extends State<SourceDetailsScreen> {
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
"+91 "+widget.sourceDetails.phone, widget.sourceDetails.address,
style: fontTextStyle( style: fontTextStyle(
12, Color(0XFF646566), FontWeight.w400), 12, Color(0XFF646566), FontWeight.w400),
), ),
@ -150,13 +599,13 @@ class _SourceDetailsScreenState extends State<SourceDetailsScreen> {
elevation: 4, elevation: 4,
onSelected: (value) { onSelected: (value) {
if (value == 'edit') { if (value == 'edit') {
//_openDriverFormSheet(context); _openSourceFormSheet(context);
} else if (value == 'disable') { } else if (value == 'disable') {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Disable selected')), const SnackBar(content: Text('Disable selected')),
); );
} else if (value == 'delete') { } else if (value == 'delete') {
//showDeleteDriverDialog(context); showDeleteSourceDialog(context);
} }
}, },
itemBuilder: (context) => [ itemBuilder: (context) => [
@ -257,29 +706,6 @@ class _SourceDetailsScreenState extends State<SourceDetailsScreen> {
), ),
const SizedBox(height: 12), 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 // Filling & Wait time cards
Padding( Padding(
@ -352,25 +778,32 @@ class _SourceDetailsScreenState extends State<SourceDetailsScreen> {
child: Container( child: Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF7F7F7), color: const Color(0xFFFFFFFF),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(8),
border: Border.all(color: Color(0XFFC3C4C4)),
), ),
child: Row( child: Row(
children: [ children: [
const Icon(Icons.water_drop, color: Color(0xFF8270DB)), Image.asset('images/recent_trips.png', width: 28, height: 28),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: Text( child: Column(
"${trip['type']} - ${trip['liters']}", crossAxisAlignment: CrossAxisAlignment.start,
style: fontTextStyle( children: [
14, const Color(0xFF2D2E30), FontWeight.w500), Text(
), "${trip['type']} - ${trip['liters']}",
), style: fontTextStyle(
Text( 14, const Color(0xFF2D2E30), FontWeight.w500),
"${trip['time']}, ${trip['date']}", ),
style: fontTextStyle( Text(
12, const Color(0xFF656565), FontWeight.w400), "${trip['time']}, ${trip['date']}",
style: fontTextStyle(
12, const Color(0xFF656565), FontWeight.w400),
),
],
)
), ),
], ],
), ),
), ),
@ -381,3 +814,34 @@ class _SourceDetailsScreenState extends State<SourceDetailsScreen> {
],)))); ],))));
} }
} }
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,
],
),
);
}
}

@ -1,5 +1,8 @@
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:supplier_new/resources/tanker_trips_model.dart';
import '../common/settings.dart'; import '../common/settings.dart';
@ -72,9 +75,11 @@ class _TankerDetailsPageState extends State<TankerDetailsPage> {
int selectedTab = 0; int selectedTab = 0;
String search = ''; String search = '';
bool isLoading = false; bool isLoading = false;
bool isTankerTripsLoading=false;
bool isStatusLoading = false; bool isStatusLoading = false;
String? currentAvailability; String? currentAvailability;
List<TankerTripsModel> tankerTripsList = [];
@override @override
void initState() { void initState() {
@ -87,6 +92,8 @@ class _TankerDetailsPageState extends State<TankerDetailsPage> {
final a = widget.tankerDetails.availability; final a = widget.tankerDetails.availability;
currentAvailability = "${a[0]}|${a[1]}"; currentAvailability = "${a[0]}|${a[1]}";
_fetchTankerTrips();
// Automatically show dropdown bottom sheet after short delay // Automatically show dropdown bottom sheet after short delay
/*WidgetsBinding.instance.addPostFrameCallback((_) { /*WidgetsBinding.instance.addPostFrameCallback((_) {
_showDropdownBottomSheet(defaultValue: currentAvailability); _showDropdownBottomSheet(defaultValue: currentAvailability);
@ -94,6 +101,28 @@ class _TankerDetailsPageState extends State<TankerDetailsPage> {
} }
} }
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);
}
}
// Dropdown options: fill + availability combined // Dropdown options: fill + availability combined
final List<String> options = [ final List<String> options = [
"empty|available", "empty|available",
@ -1033,6 +1062,51 @@ class _TankerDetailsPageState extends State<TankerDetailsPage> {
), ),
), ),
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",
),
);
},
)),
),
], ],
), ),
), ),

@ -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<String, dynamic> 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<String, dynamic> toJson() => {
"boreName":this.tanker_name,
};
}
Loading…
Cancel
Save