|
|
|
|
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 'accept_order_requests.dart';
|
|
|
|
|
|
|
|
|
|
class OrderRequestsPage extends StatefulWidget {
|
|
|
|
|
const OrderRequestsPage({super.key});
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
State<OrderRequestsPage> createState() => _OrderRequestsPageState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _OrderRequestsPageState extends State<OrderRequestsPage> {
|
|
|
|
|
final TextEditingController searchController = TextEditingController();
|
|
|
|
|
List<OrderRequestsModel> orderRequestsList = [];
|
|
|
|
|
bool isLoading = false;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void initState() {
|
|
|
|
|
super.initState();
|
|
|
|
|
_fetchOrders();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> _fetchOrders() async {
|
|
|
|
|
setState(() => isLoading = true);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
final response = await AppSettings.getOrderRequestsFromUsers();
|
|
|
|
|
final data = (jsonDecode(response)['data'] as List)
|
|
|
|
|
.map((e) => OrderRequestsModel.fromJson(e))
|
|
|
|
|
.toList();
|
|
|
|
|
|
|
|
|
|
setState(() {
|
|
|
|
|
orderRequestsList = 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 status = "Invalid time";
|
|
|
|
|
Color color = Colors.grey;
|
|
|
|
|
|
|
|
|
|
if (orderTimeStr.isEmpty) return {"status": status, "color": color};
|
|
|
|
|
|
|
|
|
|
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.inHours < 2) {
|
|
|
|
|
status = "New";
|
|
|
|
|
color = const Color(0XFF1D7AFC); // Blue
|
|
|
|
|
} else if (difference.inHours < 24) {
|
|
|
|
|
int remaining = 24 - difference.inHours;
|
|
|
|
|
status = "Expires in ${remaining}h";
|
|
|
|
|
if (difference.inHours < 6) {
|
|
|
|
|
color = const Color(0XFFE56910); // Less urgent
|
|
|
|
|
} else {
|
|
|
|
|
color = const Color(0XFFE2483D); // More urgent
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
status = "Expired";
|
|
|
|
|
color = const Color(0XFF757575); // Grey
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
debugPrint("⚠️ Error parsing time: $e");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {"status": status, "color": color};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
return Scaffold(
|
|
|
|
|
backgroundColor: Colors.white,
|
|
|
|
|
appBar: AppSettings.SupplierAppBarWithHelpAction('Order 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())
|
|
|
|
|
: ListView.builder(
|
|
|
|
|
itemCount: orderRequestsList.length,
|
|
|
|
|
itemBuilder: (context, index) {
|
|
|
|
|
final order = orderRequestsList[index];
|
|
|
|
|
final statusMap = getOrderStatus(order.time ?? "");
|
|
|
|
|
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 isExpired = status.toLowerCase() == "expired";
|
|
|
|
|
|
|
|
|
|
final card = OrderCard(order: cardModel);
|
|
|
|
|
|
|
|
|
|
return isExpired
|
|
|
|
|
? card
|
|
|
|
|
: GestureDetector(
|
|
|
|
|
onTap: () {
|
|
|
|
|
Navigator.push(
|
|
|
|
|
context,
|
|
|
|
|
MaterialPageRoute(
|
|
|
|
|
builder: (_) => AcceptOrderRequests(order: order),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
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
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
),
|
|
|
|
|
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)
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|