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.

552 lines
18 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:supplier_new/common/settings.dart';
import '../resources/tankers_model.dart';
class SetRatesScreen extends StatefulWidget {
const SetRatesScreen({super.key});
@override
State<SetRatesScreen> createState() => _SetRatesScreenState();
}
class _SetRatesScreenState extends State<SetRatesScreen>
with SingleTickerProviderStateMixin {
late TabController _tabController;
final Map<String, TextEditingController> controllers = {};
// Define categories + tankers
final Map<String, List<String>> tankerGroups = {};
final Map<String, TextEditingController> deliveryControllers = {};
/*delivery fee controllers*/
final TextEditingController pumpFeeController = TextEditingController();
List<TankersModel> tankersList = [];
bool isLoading = false;
Future<void> _fetchTankers() async {
setState(() => isLoading = true);
try {
final response = await AppSettings.getTankers();
final data = (jsonDecode(response)['data'] as List)
.map((e) => TankersModel.fromJson(e))
.toList();
if (!mounted) return;
setState(() {
// 🧹 Clear old data before adding new
tankersList.clear();
tankerGroups.clear();
controllers.clear();
deliveryControllers.clear();
// 🆕 Assign new data
tankersList = data;
isLoading = false;
// 🧭 Group data and create controllers
for (final item in data) {
final category = item.type_of_water;
final size = item.capacity;
final price = item.price.toString();
if (!tankerGroups.containsKey(category)) {
tankerGroups[category] = [];
}
// avoid duplicate sizes for same category
if (!tankerGroups[category]!.contains(size)) {
tankerGroups[category]!.add(size);
}
final controllerKey = "$category-$size";
controllers[controllerKey] = TextEditingController(text: price);
}
final capacities = tankersList.map((t) => normalizeCap(t.capacity)).toSet();
for (final cap in capacities) {
// take the first tanker of this capacity to prefill (optional)
final matched = tankersList.firstWhere(
(t) => normalizeCap(t.capacity) == cap,
orElse: () => TankersModel(),
);
final deliveryFee = matched.delivery_fee.toString() ?? "";
deliveryControllers.putIfAbsent(
cap,
() => TextEditingController(text: deliveryFee),
);
}
});
} catch (e) {
debugPrint("⚠️ Error fetching tankers: $e");
setState(() => isLoading = false);
}
}
Future<void> _fetchPumpFee() async {
try {
final response = await AppSettings.getSupplierDetails();
final data = jsonDecode(response);
if (!mounted) return;
setState(() {
pumpFeeController.text=data['pumping_fee'];
});
} catch (e) {
debugPrint("⚠️ Error fetching tankers: $e");
}
}
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
_fetchTankers();
_fetchPumpFee();
}
@override
void dispose() {
// Dispose all controllers
for (final controller in controllers.values) {
controller.dispose();
}
for (final controller in deliveryControllers.values) {
controller.dispose();
}
super.dispose();
}
Widget buildTextField(String label, TextEditingController controller) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: TextField(
controller: controller,
cursorColor: primaryColor,
readOnly: false,
keyboardType: TextInputType.number,
decoration: InputDecoration(
counterText: '',
filled: false,
fillColor: Colors.white,
prefixIconConstraints: BoxConstraints(
minWidth: 24,
minHeight: 24,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0),
borderSide: BorderSide(
color: Color(0XFFC3C4C4),
width: 1,
)),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0),
borderSide: BorderSide(
color: Color(0XFF8270DB),
width: 1,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0),
borderSide: BorderSide(color: Color(0XFFC3C4C4)),
),
hintText: label,
hintStyle: fontTextStyle(14, Color(0XFF939495), FontWeight.w400),
/* TextStyle(color: greyColor, fontWeight: FontWeight.bold //<-- SEE HERE
),*/
),
style: fontTextStyle(14, Color(0XFF515253), FontWeight.w500),
));
}
Widget labelText(String label) {
return Text(
label,
style: fontTextStyle(12, const Color(0XFF515253), FontWeight.w500),
);
}
Widget WaterCharges() {
return isLoading
? const Center(child: CircularProgressIndicator()):SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: tankersList.isEmpty?Center(
child: Text(
'No Data Available',
style: fontTextStyle(16,Color(0XFF000000),FontWeight.w700),
),
)
:Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (final entry in tankerGroups.entries) ...[
Text(
entry.key,
style:
fontTextStyle(10, const Color(0XFF2D2E30), FontWeight.w600),
),
const SizedBox(height: 8),
for (final size in entry.value)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
labelText("$size L (in ₹)*"),
const SizedBox(height: 4),
buildTextField(
"₹500",
controllers["${entry.key}-$size"]!,
),
const SizedBox(height: 12),
],
),
const SizedBox(height: 10),
],
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0XFF8270DB),
foregroundColor: const Color(0XFFFFFFFF),
padding: const EdgeInsets.symmetric(vertical: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
),
onPressed: () async {
final List<Map<String, String>> tankersPayload = [];
tankerGroups.forEach((category, sizes) {
for (final size in sizes) {
final key = "$category-$size";
final amount = controllers[key]?.text.trim() ?? "0";
// find the tanker in tankersList by capacity (size)
final matchedTanker = tankersList.firstWhere(
(t) => t.capacity == size,
orElse: () => TankersModel(),
);
final tankerName = matchedTanker.tanker_name.isNotEmpty
? matchedTanker.tanker_name
: "$category $size L"; // fallback if not found
tankersPayload.add({
"tankerName": tankerName,
"amount": amount.isEmpty ? "0" : amount,
});
}
});
final payload = {
"price_type": "price",
"tankers": tankersPayload,
};
print(payload);
try {
final ok = await AppSettings.setRatesDaily(payload);
if (ok) {
AppSettings.longSuccessToast("Prices updated successfully");
_fetchTankers();
} else {
AppSettings.longFailedToast("Update failed");
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Error: $e")),
);
}
},
child: Text(
"Save",
style:
fontTextStyle(14, const Color(0XFFFFFFFF), FontWeight.w600),
),
),
)
],
),
);
}
String normalizeCap(String cap) => cap.replaceAll(',', '').replaceAll(' ', '');
Widget DeliveryFee() {
// Group by normalized capacity
final Map<String, List<TankersModel>> capacityGroups = {};
for (final t in tankersList) {
final normCap = normalizeCap(t.capacity);
capacityGroups.putIfAbsent(normCap, () => []).add(t);
}
// Sorted list by numeric capacity (if parseable), else lexicographic
final capacities = capacityGroups.keys.toList()
..sort((a, b) {
final ai = int.tryParse(a);
final bi = int.tryParse(b);
if (ai != null && bi != null) return ai.compareTo(bi);
return a.compareTo(b);
});
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"ADDITIONAL RATES",
style: fontTextStyle(10, const Color(0xFF2D2E30), FontWeight.w600),
),
SizedBox(height: MediaQuery.of(context).size.height * .004),
Text(
"Add your price per Kilometer for every tanker capacity",
style: fontTextStyle(12, const Color(0xFF939495), FontWeight.w400),
),
SizedBox(height: MediaQuery.of(context).size.height * .020),
// one field per normalized capacity
for (final normCap in capacities) ...[
// show without commas (normalized already)
labelText('${normCap} L tanker (per KM)*'),
SizedBox(height: MediaQuery.of(context).size.height * .004),
buildTextField(
"+ ₹12",
// controller keyed by normalized cap
deliveryControllers[normCap] ?? TextEditingController(),
),
const SizedBox(height: 12),
],
const SizedBox(height: 10),
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0XFF8270DB),
foregroundColor: const Color(0XFFFFFFFF),
padding: const EdgeInsets.symmetric(vertical: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
),
onPressed: () async {
final List<Map<String, String>> tankersPayload = [];
// loop groups; use the same fee for all tankers in the group
capacityGroups.forEach((normCap, list) {
final fee = (deliveryControllers[normCap]?.text ?? "").trim();
final amt = fee.isEmpty ? "0" : fee;
for (final t in list) {
final tankerName = t.tanker_name.isNotEmpty
? t.tanker_name
: "Tanker ${normalizeCap(t.capacity)} L";
tankersPayload.add({
"tankerName": tankerName,
"amount": amt,
});
}
});
final payload = {
"price_type": "delivery_fee",
"tankers": tankersPayload,
};
print(payload);
try {
final ok = await AppSettings.setRatesDaily(payload);
if (ok) {
AppSettings.longSuccessToast("Delivery fees updated successfully");
_fetchTankers();
} else {
AppSettings.longFailedToast("Update failed");
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Error: $e")),
);
}
},
child: Text(
"Save",
style: fontTextStyle(14, const Color(0XFFFFFFFF), FontWeight.w600),
),
),
),
],
),
);
}
Widget PumpFee() {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(
"TANKER TYPE",
style: fontTextStyle(10, Color(0XFF2D2E30), FontWeight.w600),
),
SizedBox(height: MediaQuery.of(context).size.height * .004),
Text(
"Add your price addition for tankers with pump",
style: fontTextStyle(12, Color(0XFF939495), FontWeight.w400),
),
SizedBox(height: MediaQuery.of(context).size.height * .024),
labelText('Tanker with pump*'),
SizedBox(height: MediaQuery.of(context).size.height * .004),
buildTextField("+ ₹50 ", pumpFeeController),
SizedBox(height: MediaQuery.of(context).size.height * .024),
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0XFF8270DB),
foregroundColor: Color(0XFFFFFFFF),
padding: EdgeInsets.symmetric(vertical: 10),
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(24), // <-- set your radius here
),
),
onPressed: () async{
var payload = new Map<String, dynamic>();
payload["pumping_fee"] = pumpFeeController.text.toString();
bool tankStatus = await AppSettings.updatePumpFee(payload);
try {
if (tankStatus) {
AppSettings.longSuccessToast("Pump fee Updated Successfully");
_fetchPumpFee();
}
else {
AppSettings.longFailedToast("Pump fee updation failed");
}
} catch (exception) {
print(exception);
}
},
child: Text(
"Save",
style:
fontTextStyle(14, const Color(0XFFFFFFFF), FontWeight.w600),
),
),
)
]),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0XFFFFFFFF),
appBar: AppSettings.supplierAppBarWithActionsText('Set Rates', context),
body: Column(
children: [
Container(
width: double.infinity,
decoration: const BoxDecoration(
color: Color(0XFFF3F1FB),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24),
),
),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
child: Column(
children: [
SizedBox(height: MediaQuery.of(context).size.height * .096),
const CircleAvatar(
radius: 50, backgroundColor: Color(0XFFC9C2F0)),
SizedBox(height: MediaQuery.of(context).size.height * .016),
Text(
"Whats todays water price?",
style: fontTextStyle(20, Color(0XFF343637), FontWeight.w600),
),
SizedBox(height: MediaQuery.of(context).size.height * .008),
Text(
"Set your daily rate so customers know what to expect",
style: fontTextStyle(12, Color(0XFF343637), FontWeight.w400),
textAlign: TextAlign.center,
),
],
),
),
const SizedBox(height: 20),
Container(
height: 30,
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: AnimatedBuilder(
animation: _tabController,
builder: (context, _) {
return TabBar(
controller: _tabController,
indicatorColor: Colors.transparent, // remove underline
dividerColor: Colors.transparent,
isScrollable: false,
overlayColor: MaterialStateProperty.all(
Colors.transparent), // equal width
tabs: List.generate(3, (index) {
final labels = ['Water Type', 'Delivery Fee', 'Pump'];
final isSelected = _tabController.index == index;
return Container(
decoration: BoxDecoration(
color: isSelected
? const Color(0XFFF1F1F1)
: Colors.transparent,
borderRadius: BorderRadius.circular(27),
),
alignment: Alignment.center,
child: Text(
labels[index],
style: isSelected
? fontTextStyle(
12,
const Color(0XFF101214),
FontWeight.w600,
)
: fontTextStyle(
12,
const Color(0XFF646464),
FontWeight.w600,
),
),
);
}),
);
},
),
),
Expanded(
child: TabBarView(
controller: _tabController,
children: [
// Water Type Tab
WaterCharges(),
// Delivery Fee Tab
DeliveryFee(),
// Pump Tab
PumpFee()
],
),
),
],
),
);
}
}