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

2 months ago
import 'dart:convert';
3 months ago
import 'package:flutter/material.dart';
import 'package:supplier_new/common/settings.dart';
3 months ago
2 months ago
import '../resources/tankers_model.dart';
3 months ago
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();
2 months ago
List<TankersModel> tankersList = [];
bool isLoading = false;
2 months ago
Future<void> _fetchTankers() async {
setState(() => isLoading = true);
3 months ago
2 months ago
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();
2 months ago
// 🆕 Assign new data
tankersList = data;
isLoading = false;
2 months ago
// 🧭 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();
2 months ago
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);
}
2 months ago
}
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");
}
2 months ago
}
2 months ago
@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();
3 months ago
}
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
),*/
3 months ago
),
style: fontTextStyle(14, Color(0XFF515253), FontWeight.w500),
));
}
Widget labelText(String label) {
return Text(
label,
style: fontTextStyle(12, const Color(0XFF515253), FontWeight.w500),
);
}
Widget WaterCharges() {
2 months ago
return isLoading
? const Center(child: CircularProgressIndicator()):SingleChildScrollView(
padding: const EdgeInsets.all(16),
2 months ago
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,
2 months ago
style:
fontTextStyle(10, const Color(0XFF2D2E30), FontWeight.w600),
),
const SizedBox(height: 8),
for (final size in entry.value)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
2 months ago
labelText("$size L (in ₹)*"),
const SizedBox(height: 4),
2 months ago
buildTextField(
"₹500",
controllers["${entry.key}-$size"]!,
),
const SizedBox(height: 12),
],
),
const SizedBox(height: 10),
],
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
2 months ago
backgroundColor: const Color(0XFF8270DB),
foregroundColor: const Color(0XFFFFFFFF),
padding: const EdgeInsets.symmetric(vertical: 10),
shape: RoundedRectangleBorder(
2 months ago
borderRadius: BorderRadius.circular(24),
),
),
2 months ago
onPressed: () async {
final List<Map<String, String>> tankersPayload = [];
tankerGroups.forEach((category, sizes) {
for (final size in sizes) {
2 months ago
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,
});
}
});
2 months ago
final payload = {
"price_type": "price",
"tankers": tankersPayload,
};
print(payload);
2 months ago
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",
2 months ago
style:
fontTextStyle(14, const Color(0XFFFFFFFF), FontWeight.w600),
),
),
)
],
3 months ago
),
);
}
2 months ago
String normalizeCap(String cap) => cap.replaceAll(',', '').replaceAll(' ', '');
Widget DeliveryFee() {
2 months ago
// 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: [
2 months ago
Text(
"ADDITIONAL RATES",
style: fontTextStyle(10, const Color(0xFF2D2E30), FontWeight.w600),
3 months ago
),
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),
2 months ago
// 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),
2 months ago
buildTextField(
"+ ₹12",
// controller keyed by normalized cap
deliveryControllers[normCap] ?? TextEditingController(),
),
const SizedBox(height: 12),
3 months ago
],
const SizedBox(height: 10),
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
2 months ago
backgroundColor: const Color(0XFF8270DB),
foregroundColor: const Color(0XFFFFFFFF),
padding: const EdgeInsets.symmetric(vertical: 10),
shape: RoundedRectangleBorder(
2 months ago
borderRadius: BorderRadius.circular(24),
),
),
2 months ago
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,
});
}
});
2 months ago
final payload = {
"price_type": "delivery_fee",
"tankers": tankersPayload,
};
print(payload);
2 months ago
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),
),
),
2 months ago
),
],
),
);
}
2 months ago
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),
3 months ago
),
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(
2 months ago
borderRadius:
BorderRadius.circular(24), // <-- set your radius here
),
3 months ago
),
2 months ago
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",
2 months ago
style:
fontTextStyle(14, const Color(0XFFFFFFFF), FontWeight.w600),
3 months ago
),
),
)
]),
);
}
@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),
),
3 months ago
),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
child: Column(
children: [
SizedBox(height: MediaQuery.of(context).size.height * .096),
2 months ago
const CircleAvatar(
radius: 50, backgroundColor: Color(0XFFC9C2F0)),
SizedBox(height: MediaQuery.of(context).size.height * .016),
2 months ago
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,
),
3 months ago
],
),
),
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,
2 months ago
overlayColor: MaterialStateProperty.all(
Colors.transparent), // equal width
tabs: List.generate(3, (index) {
2 months ago
final labels = ['Water Type', 'Delivery Fee', 'Pump'];
final isSelected = _tabController.index == index;
return Container(
decoration: BoxDecoration(
2 months ago
color: isSelected
? const Color(0XFFF1F1F1)
: Colors.transparent,
borderRadius: BorderRadius.circular(27),
),
alignment: Alignment.center,
child: Text(
labels[index],
2 months ago
style: isSelected
? fontTextStyle(
12,
const Color(0XFF101214),
FontWeight.w600,
)
: fontTextStyle(
12,
const Color(0XFF646464),
FontWeight.w600,
),
),
);
}),
);
},
3 months ago
),
),
Expanded(
child: TabBarView(
controller: _tabController,
children: [
// Water Type Tab
WaterCharges(),
// Delivery Fee Tab
DeliveryFee(),
// Pump Tab
PumpFee()
],
),
),
],
3 months ago
),
);
}
}