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.

565 lines
19 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:flutter/services.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 rawCategory = item.type_of_water ?? "";
final categoryKey = normalizeType(rawCategory);
final categoryLabel = displayType(rawCategory);
final size = normalizeCap(item.capacity);
final price = item.price.toString();
tankerGroups.putIfAbsent(categoryKey, () => []);
if (!tankerGroups[categoryKey]!.contains(size)) {
tankerGroups[categoryKey]!.add(size);
}
controllers["$categoryKey-$size"] =
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 hint, TextEditingController controller) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: TextFormField(
controller: controller,
cursorColor: primaryColor,
keyboardType: TextInputType.number,
// ✅ blocks -, +, . and all special chars (only digits)
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(5), // adjust if needed
],
decoration: InputDecoration(
counterText: '',
filled: false,
fillColor: Colors.white,
prefixIconConstraints: const BoxConstraints(
minWidth: 24,
minHeight: 24,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0),
borderSide: const BorderSide(
color: Color(0XFFC3C4C4),
width: 1,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0),
borderSide: const BorderSide(
color: Color(0XFF8270DB),
width: 1,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0),
borderSide: const BorderSide(color: Color(0XFFC3C4C4)),
),
hintText: hint,
hintStyle: fontTextStyle(14, const Color(0XFF939495), FontWeight.w400),
),
style: fontTextStyle(14, const Color(0XFF515253), FontWeight.w500),
// ✅ optional: if you later wrap with Form and call validate()
validator: (value) {
final v = (value ?? "").trim();
if (v.isEmpty) return "Enter price";
final n = int.tryParse(v) ?? 0;
if (n <= 0) return "Enter positive value";
return null;
},
),
);
}
Widget labelText(String label) {
return Text(
label,
style: fontTextStyle(12, const Color(0XFF515253), FontWeight.w500),
);
}
String normalizeType(String type) {
return type.trim().toLowerCase();
}
String displayType(String type) {
final t = normalizeType(type);
if (t.contains("drink")) return "Drinking Water";
if (t.contains("bore")) return "Bore Water";
return type.trim();
}
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(
displayType(entry.key), // 👈 shows clean label
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 = [];
for (final tanker in tankersList) {
final categoryKey = normalizeType(tanker.type_of_water);
final size = normalizeCap(tanker.capacity);
final controllerKey = "$categoryKey-$size";
final amount = controllers[controllerKey]?.text.trim() ?? "0";
tankersPayload.add({
"tankerName": tanker.tanker_name,
"amount": amount.isEmpty ? "0" : amount,
});
}
final payload = {
"price_type": "price",
"tankers": tankersPayload,
};
print(payload);
final ok = await AppSettings.setRatesDaily(payload);
if (ok) {
AppSettings.longSuccessToast("Prices updated successfully");
_fetchTankers();
} else {
AppSettings.longFailedToast("Update failed");
}
},
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()
],
),
),
],
),
);
}
}