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.

494 lines
14 KiB

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:supplier_new/common/settings.dart';
import 'package:supplier_new/plans/plan_details.dart';
import 'package:supplier_new/plans/plan_requests.dart';
import 'package:supplier_new/plans/search_plan_appbar.dart';
/// =====================
/// MODEL
/// =====================
class PlansModel {
final String id;
final String status;
final String apartment;
final String liters;
final String price;
final String advance;
final String deliveries;
final String frequency;
final String waterType;
PlansModel({
required this.id,
required this.status,
required this.apartment,
required this.liters,
required this.price,
required this.advance,
required this.deliveries,
required this.frequency,
required this.waterType,
});
factory PlansModel.fromJson(Map<String, dynamic> json) {
final supplier = json["my_supplier"] ?? {};
final List dates = json["dates"] ?? [];
return PlansModel(
id: json["_id"],
status: json["status"] == "processed" ? "Active" : "Pending",
apartment: json["customerId"],
liters: "${json["capacity"]} - ${json["type_of_water"]}",
price: "${supplier["quoted_amount"] ?? "--"}",
advance: "--",
deliveries: "${dates.length} Deliveries",
frequency: "${json["weekly_count"]}/week",
waterType: json["type_of_water"] ?? "",
);
}
}
/// =====================
/// SCREEN
/// =====================
///
Future<bool> showAcceptConfirmDialog(BuildContext context) async {
return await showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (ctx) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
title: const Text("Confirm Acceptance"),
content: const Text(
"Are you sure you want to accept this recurring plan?",
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: const Text("Cancel"),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0XFF8270DB),
),
onPressed: () => Navigator.pop(ctx, true),
child: const Text("Accept"),
),
],
);
},
) ??
false;
}
///
class AllPlans extends StatefulWidget {
final String navigationFrom;
const AllPlans({super.key, required this.navigationFrom});
@override
State<AllPlans> createState() => _AllPlansState();
}
class _AllPlansState extends State<AllPlans> {
final TextEditingController searchController = TextEditingController();
bool isLoading = true;
/// UI MODELS
List<PlansModel> plans = [];
/// RAW API DATA (IMPORTANT)
List<Map<String, dynamic>> rawPlans = [];
int activeCount = 0;
int boreCount = 0;
int drinkingCount = 0;
@override
void initState() {
super.initState();
fetchPlans();
}
/// =====================
/// FETCH API
/// =====================
Future<void> fetchPlans() async {
try {
final response = await AppSettings.getAcceptedRecurringBookings();
final decoded = jsonDecode(response);
final List data = decoded["data"] ?? [];
/// ✅ FILTER ONLY payment_completed
rawPlans = List<Map<String, dynamic>>.from(
data.where((e) =>
["processed", "payment_completed"]
.contains((e["status"] ?? "").toString().toLowerCase())),
);
plans = rawPlans.map((e) => PlansModel.fromJson(e)).toList();
/// COUNTS (NOW ONLY payment_completed DATA)
activeCount = plans.length;
boreCount =
plans.where((p) => p.waterType.toLowerCase().contains("bore")).length;
drinkingCount = plans
.where((p) => p.waterType.toLowerCase().contains("drinking"))
.length;
setState(() => isLoading = false);
} catch (e) {
setState(() => isLoading = false);
}
}
/// =====================
/// UI
/// =====================
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: widget.navigationFrom.toLowerCase() == 'dashboard'
? SearchPlanAppBar(
controller: searchController,
onBack: () => Navigator.pop(context),
onHelp: () {},
)
: null,
body: isLoading
? const Center(child: CircularProgressIndicator())
: SingleChildScrollView(
child: Column(
children: [
/// 🔹 TOP SECTION
Container(
decoration: const BoxDecoration(
color: Color(0XFFF2F2F2),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24),
),
),
padding: const EdgeInsets.all(24),
child: Column(
children: [
Text(
activeCount.toString().padLeft(2, '0'),
style: fontTextStyle(
64, const Color(0XFF2D2E30), FontWeight.w700),
),
Text(
"Active Plans",
style: fontTextStyle(
24, const Color(0XFF2D2E30), FontWeight.w600),
),
const SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
PlanCategoryCard(
image: Image.asset('images/bore-water.png',
height: 40, width: 40),
value: boreCount.toString().padLeft(2, '0'),
label: "Bore Water",
),
PlanCategoryCard(
image: Image.asset('images/drinking-water.png',
height: 40, width: 40),
value:
drinkingCount.toString().padLeft(2, '0'),
label: "Drinking Water",
),
],
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0XFF8270DB),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(32),
),
padding: const EdgeInsets.all(16),
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) =>
const PlanRequestsPage()),
);
},
child: Text(
"View Plan Requests",
style: fontTextStyle(
16, Colors.white, FontWeight.w500),
),
),
),
],
),
),
/// 🔹 PLAN LIST
isLoading
? Center(
child: CircularProgressIndicator())
: (plans.isEmpty
? Center(
child: Padding(
padding:
const EdgeInsets.symmetric(
vertical: 50),
child: Text(
'No Data Available',
style: fontTextStyle(
12,
const Color(0XFF2D2E30),
FontWeight.w500),
),
),
)
: ListView.builder(
padding: const EdgeInsets.all(12),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: plans.length,
itemBuilder: (_, index) {
return PlansCard(
delivery: plans[index],
planJson: rawPlans[index], // ✅ FIXED
);
},
)),
],
),
),
);
}
}
/// =====================
/// PLAN CARD
/// =====================
class PlansCard extends StatelessWidget {
final PlansModel delivery;
final Map<String, dynamic> planJson;
const PlansCard({
super.key,
required this.delivery,
required this.planJson,
});
@override
Widget build(BuildContext context) {
bool isActive = delivery.status == "Active";
return GestureDetector(
onTap: () {
if (delivery.status != "Active") return;
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => PlanDetails(plan: planJson),
),
);
},
child: Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFE5E5E5)),
color: Colors.white,
),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: isActive ? const Color(0xFFE9F9EE) : Colors.grey.shade200,
borderRadius: BorderRadius.circular(6),
),
child: Text(
delivery.status,
style: TextStyle(
fontSize: 12,
color: isActive
? const Color(0xFF3BB273)
: Colors.grey,
),
),
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(delivery.apartment,
style: const TextStyle(
fontSize: 15, fontWeight: FontWeight.w600)),
Text(delivery.price,
style: const TextStyle(
fontSize: 15, fontWeight: FontWeight.w600)),
],
),
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(delivery.liters,
style: const TextStyle(
fontSize: 14, color: Color(0xFF7B5AF4))),
Text(delivery.advance,
style: const TextStyle(fontSize: 12)),
],
),
Visibility(
visible: delivery.status=='payment_completed',
child: Row(
children: [
GestureDetector(
onTap: (){
},
child:
ColorFiltered(
colorFilter: ColorFilter.mode(
Colors.transparent,
BlendMode.multiply),
child: Image
.asset(
'images/wrong_button.png',
width:
44,
height:
44,
),
),
),
SizedBox(
width: MediaQuery.of(context)
.size
.width *
.012,
),
GestureDetector(
onTap: () async {
final confirmed = await showAcceptConfirmDialog(context);
if (!confirmed) return;
try {
final res = await AppSettings.respondRecurringBooking(
bookingId: planJson["_id"],
supplierId: planJson["my_supplier"]["supplierId"],
action: "accept",
);
final List dates = res["data"]["dates"] ?? [];
if (dates.isEmpty) return;
/* Navigator.push(
context,
MaterialPageRoute(
builder: (_) => PlanCalendarScreen(dates: dates),
),
);*/
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Failed to accept plan")),
);
}
},
child: Image.asset(
'images/right_button.png',
width: 44,
height: 44,
),
),
],
),),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: const Color(0xFFF8F6FF),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(delivery.deliveries,
style: const TextStyle(fontSize: 13)),
Text(delivery.frequency,
style: const TextStyle(
fontSize: 13, color: Color(0xFF7B5AF4))),
],
),
),
]),
),
);
}
}
/// =====================
/// CATEGORY CARD
/// =====================
class PlanCategoryCard extends StatelessWidget {
final Image image;
final String value;
final String label;
const PlanCategoryCard({
super.key,
required this.image,
required this.value,
required this.label,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(width: 40, height: 40, child: image),
const SizedBox(height: 8),
Text(value,
style:
fontTextStyle(20, const Color(0XFF515253), FontWeight.w700)),
Text(label,
style:
fontTextStyle(16, const Color(0XFF515253), FontWeight.w400)),
],
);
}
}