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.

369 lines
12 KiB

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:supplier_new/common/settings.dart';
import 'package:supplier_new/orders/order_requests_model.dart';
import 'package:supplier_new/plans/accept_plan_requests.dart';
import 'package:supplier_new/plans/plan_requests_model.dart';
class PlanRequestsPage extends StatefulWidget {
const PlanRequestsPage({super.key});
@override
State<PlanRequestsPage> createState() => _PlanRequestsPageState();
}
class _PlanRequestsPageState extends State<PlanRequestsPage> {
final TextEditingController searchController = TextEditingController();
List<PlanRequestsModel> planRequestsList = [];
List<PlanRequestsModel> filteredList = [];
bool isLoading = false;
@override
void initState() {
super.initState();
_fetchPlanRequests();
// 🔹 Add listener for search
searchController.addListener(() {
_filterOrders(searchController.text);
});
}
void _filterOrders(String query) {
final lowerQuery = query.toLowerCase();
setState(() {
if (lowerQuery.isEmpty) {
filteredList = planRequestsList;
} else {
filteredList = planRequestsList.where((order) {
final title = order.building_name.toLowerCase() ?? '';
final address = order.displayAddress.toLowerCase() ?? '';
final waterType = order.type_of_water.toLowerCase() ?? '';
final capacity = order.capacity.toLowerCase() ?? '';
return title.contains(lowerQuery) ||
address.contains(lowerQuery) ||
waterType.contains(lowerQuery) ||
capacity.contains(lowerQuery);
}).toList();
}
});
}
Future<void> _fetchPlanRequests() async {
setState(() => isLoading = true);
try {
final response = await AppSettings.getPlanRequestsFromUsers();
final data = (jsonDecode(response)['data'] as List)
.map((e) => PlanRequestsModel.fromJson(e))
.toList();
if (!mounted) return;
setState(() {
planRequestsList = data;
filteredList = data;
isLoading = false;
});
} catch (e) {
debugPrint("⚠️ Error fetching orders: $e");
setState(() => isLoading = false);
}
}
/// 🔹 Helper to get status based on order time
Map<String, dynamic> getOrderStatus(String orderTimeStr, String dbStatus) {
String status = "Invalid time";
Color color = Colors.grey;
final dbLower = (dbStatus ?? "").toLowerCase().trim();
// ✅ Statuses that ignore time
if (dbLower == "reject") return {"status": "Rejected", "color": const Color(0XFFE2483D)};
if (dbLower == "accept") return {"status": "Accepted", "color": const Color(0XFF0A9E04)};
if (dbLower == "advance_paid") return {"status": "Accepted", "color": const Color(0XFF0A9E04)};
if (dbLower == "cancelled" || dbLower == "cancelled_by_user" || dbLower == "cancelled_by_supplier") {
return {"status": "Cancelled", "color": const Color(0XFF757575)};
}
// ✅ Time-based status (only if not rejected/accepted/etc.)
if (orderTimeStr.isEmpty) {
// fallback for pending without time
return {"status": "Pending", "color": const Color(0XFFE56910)};
}
try {
final format = DateFormat("dd-MM-yyyy HH:mm");
final orderTime = format.parse(orderTimeStr);
final now = DateTime.now();
final difference = now.difference(orderTime);
if (difference.inDays < 2) {
status = "New";
color = const Color(0XFF1D7AFC); // Blue
} else if (difference.inDays < 10) {
final remaining = 20 - difference.inDays;
status = "Expires in ${remaining} D"; // show time for pending
color = difference.inDays < 10
? const Color(0XFFE56910)
: const Color(0XFFE2483D);
} else {
// Expired overrides pending
status = "Expired";
color = const Color(0XFF757575);
}
} catch (e) {
debugPrint("⚠️ Error parsing time: $e");
status = "Invalid time";
color = Colors.grey;
}
return {"status": status, "color": color};
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppSettings.SupplierAppBarWithHelpAction('Plan Requests', context),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
/// 🔹 Search Bar
_buildSearchBar(),
const SizedBox(height: 16),
/// 🔹 Orders List
Expanded(
child: isLoading
? const Center(child: CircularProgressIndicator())
: filteredList .isEmpty?Center(
child: Text(
'No Data Available',
style: fontTextStyle(16,Color(0XFF000000),FontWeight.w700),
),
):ListView.builder(
itemCount: filteredList .length,
itemBuilder: (context, index) {
final order = filteredList [index];
final statusMap = getOrderStatus(order.time ?? "", order.status ?? "");
final status = statusMap['status'];
final color = statusMap['color'];
final cardModel = OrderCardModel(
status: status,
statusColor: color,
title: order.building_name ?? "",
location: order.displayAddress ?? "",
description: "${order.capacity ?? ''} - ${order.type_of_water ?? ''}",
price: "${AppSettings.formDouble(order.quoted_amount) ?? ''}",
);
final noNavigateStatuses = ["expired", "rejected", "accepted", "advance_paid", "cancelled"];
final disableNavigation = noNavigateStatuses.contains(status.toLowerCase());
final card = OrderCard(order: cardModel);
return disableNavigation
? card // just show the card, no tap
: GestureDetector(
onTap: () async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AcceptPlanRequests(order: order, status: cardModel),
),
);
// If result indicates API reload
if (result == true) {
_fetchPlanRequests();
}
},
child: card,
);
},
),
),
],
),
),
);
}
Widget _buildSearchBar() {
return Row(
children: [
Expanded(
child: Container(
height: 42,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(22),
border: Border.all(color: const Color(0XFF939495)),
),
child: TextField(
controller: searchController,
decoration: InputDecoration(
hintText: "Search",
hintStyle: fontTextStyle(16, const Color(0XFF646566), FontWeight.w400),
prefixIcon: SizedBox(
height: 20,
width: 20,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Image.asset(
'images/search.png',
fit: BoxFit.contain,
),
),
),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 10),
),
),
),
),
const SizedBox(width: 10),
Container(
height: 42,
width: 42,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade300),
color: Color(0XFFF5F6F6),
),
child: IconButton(
icon: Image.asset(
'images/filter.png', // your image path
height: 20,
width: 20,
fit: BoxFit.contain,
),
onPressed: () {
// Your onPressed action
},
)
),
const SizedBox(width: 5),
Container(
height: 42,
width: 42,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade300),
color: Color(0XFFF5F6F6),
),
child: IconButton(
icon: Image.asset(
'images/sort.png', // your image path
height: 20,
width: 20,
fit: BoxFit.contain,
),
onPressed: () {
// Your onPressed action
},
)
),
],
);
}
}
/// 🔹 UI Model for rendering OrderCard
class OrderCardModel {
final String status;
final Color statusColor;
final String title;
final String location;
final String description;
final String price;
OrderCardModel({
required this.status,
required this.statusColor,
required this.title,
required this.location,
required this.description,
required this.price,
});
}
/// 🔹 Card widget
class OrderCard extends StatelessWidget {
final OrderCardModel order;
const OrderCard({super.key, required this.order});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Color(0XFFF6F6F6),
borderRadius: BorderRadius.circular(12),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
/// 🔹 Left content
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Status chip
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Color(0XFFF6F6F6),
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: order.statusColor,
width: 0.5,
),
),
child: Text(
order.status,
style: fontTextStyle(12, order.statusColor, FontWeight.w500)
),
),
SizedBox(height:MediaQuery.of(context).size.height * .008,),
/// Title
Text(
order.title,
style: fontTextStyle(16, Color(0XFF2D2E30), FontWeight.w600)
),
/// Location
Text(
order.location,
style: fontTextStyle(10, Color(0XFF939495), FontWeight.w400)
),
const SizedBox(height: 4),
/// Description
Text(
order.description,
style: fontTextStyle(14, Color(0XFF8270DB), FontWeight.w500)
),
],
),
),
/// 🔹 Price
Text(
order.price,
style: fontTextStyle(16, Color(0XFF444444), FontWeight.w600)
),
],
),
);
}
}