|
|
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(
|
|
|
"What’s today’s 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()
|
|
|
],
|
|
|
),
|
|
|
),
|
|
|
],
|
|
|
),
|
|
|
);
|
|
|
}
|
|
|
}
|