405 lines
12 KiB
Dart
405 lines
12 KiB
Dart
import 'dart:ui';
|
|
import 'package:flutter/material.dart';
|
|
import '../services/dio_client.dart';
|
|
import '../services/order_service.dart';
|
|
|
|
class OrderDetailScreen extends StatefulWidget {
|
|
final String orderId;
|
|
const OrderDetailScreen({super.key, required this.orderId});
|
|
|
|
@override
|
|
State<OrderDetailScreen> createState() => _OrderDetailScreenState();
|
|
}
|
|
|
|
class _OrderDetailScreenState extends State<OrderDetailScreen> {
|
|
bool loading = true;
|
|
Map order = {};
|
|
final Map<String, bool> _expanded = {};
|
|
|
|
bool confirming = false;
|
|
|
|
bool get isOrderPlaced => order['status'] == 'order_placed';
|
|
bool get isConfirmed => order['status'] != 'order_placed';
|
|
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
load();
|
|
}
|
|
|
|
Future<void> load() async {
|
|
final service = OrderService(DioClient.getInstance(context));
|
|
final res = await service.getOrderDetails(widget.orderId);
|
|
|
|
if (res['success'] == true) {
|
|
order = res['order'];
|
|
}
|
|
|
|
loading = false;
|
|
setState(() {});
|
|
}
|
|
|
|
String _initials(String? s) {
|
|
if (s == null || s.isEmpty) return "I";
|
|
final parts = s.split(" ");
|
|
return parts.take(2).map((e) => e[0].toUpperCase()).join();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final items = order['items'] ?? [];
|
|
|
|
final width = MediaQuery.of(context).size.width;
|
|
final scale = (width / 430).clamp(0.85, 1.20);
|
|
|
|
return Scaffold(
|
|
backgroundColor: const Color(0xFFF0F6FF),
|
|
appBar: AppBar(
|
|
title: const Text("Order Details"),
|
|
elevation: 0,
|
|
),
|
|
body: loading
|
|
? const Center(child: CircularProgressIndicator())
|
|
: Padding(
|
|
padding: EdgeInsets.all(16 * scale),
|
|
child: SingleChildScrollView(
|
|
child: Column(
|
|
children: [
|
|
_summaryCard(scale),
|
|
SizedBox(height: 18 * scale),
|
|
_itemsSection(items, scale),
|
|
SizedBox(height: 18 * scale),
|
|
_totalsSection(scale),
|
|
|
|
SizedBox(height: 24 * scale),
|
|
_confirmOrderButton(scale),
|
|
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _confirmOrderButton(double scale) {
|
|
final isPlaced = order['status'] == 'order_placed';
|
|
|
|
return SizedBox(
|
|
width: double.infinity,
|
|
height: 52 * scale,
|
|
child: ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: isPlaced ? Colors.orange : Colors.green,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(14 * scale),
|
|
),
|
|
),
|
|
onPressed: (!isPlaced || confirming)
|
|
? null
|
|
: () async {
|
|
setState(() => confirming = true);
|
|
|
|
final service =
|
|
OrderService(DioClient.getInstance(context));
|
|
|
|
final res =
|
|
await service.confirmOrder(order['order_id']);
|
|
|
|
confirming = false;
|
|
|
|
if (res['success'] == true) {
|
|
setState(() {
|
|
order['status'] = 'order_confirmed';
|
|
});
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('Order confirmed successfully'),
|
|
backgroundColor: Colors.green,
|
|
),
|
|
);
|
|
} else {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(res['message'] ?? 'Failed to confirm order'),
|
|
backgroundColor: Colors.red,
|
|
),
|
|
);
|
|
}
|
|
|
|
setState(() {});
|
|
},
|
|
child: confirming
|
|
? const CircularProgressIndicator(color: Colors.white)
|
|
: Text(
|
|
isPlaced ? 'Confirm Order' : 'Order Confirmed',
|
|
style: TextStyle(
|
|
fontSize: 16 * scale,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
|
|
// -----------------------------
|
|
// SUMMARY CARD
|
|
// -----------------------------
|
|
Widget _summaryCard(double scale) {
|
|
return Container(
|
|
padding: EdgeInsets.all(18 * scale),
|
|
decoration: _cardDecoration(scale),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text("Order Summary",
|
|
style: TextStyle(fontSize: 20 * scale, fontWeight: FontWeight.bold)),
|
|
SizedBox(height: 12 * scale),
|
|
_infoRow("Order ID", order['order_id'], scale),
|
|
_infoRow("Mark No", order['mark_no'], scale),
|
|
_infoRow("Origin", order['origin'], scale),
|
|
_infoRow("Destination", order['destination'], scale),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _infoRow(String title, dynamic value, double scale) {
|
|
return Padding(
|
|
padding: EdgeInsets.symmetric(vertical: 6 * scale),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(title,
|
|
style: TextStyle(color: Colors.grey, fontSize: 14 * scale)),
|
|
Text(value?.toString() ?? "-",
|
|
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15 * scale)),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// -----------------------------
|
|
// ORDER ITEMS SECTION
|
|
// -----------------------------
|
|
Widget _itemsSection(List items, double scale) {
|
|
return Container(
|
|
padding: EdgeInsets.all(18 * scale),
|
|
decoration: _cardDecoration(scale),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text("Order Items",
|
|
style: TextStyle(fontSize: 18 * scale, fontWeight: FontWeight.bold)),
|
|
SizedBox(height: 16 * scale),
|
|
...List.generate(items.length, (i) {
|
|
return _expandableItem(items[i], i, scale);
|
|
})
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// -----------------------------
|
|
// EXPANDABLE ITEM
|
|
// -----------------------------
|
|
Widget _expandableItem(Map item, int index, double scale) {
|
|
final id = "item_$index";
|
|
_expanded[id] = _expanded[id] ?? false;
|
|
|
|
final description = item['description'] ?? "Item";
|
|
final initials = _initials(description);
|
|
final imageUrl = item['image'] ?? "";
|
|
|
|
return AnimatedContainer(
|
|
duration: const Duration(milliseconds: 300),
|
|
margin: EdgeInsets.only(bottom: 16 * scale),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(14 * scale),
|
|
border: Border.all(color: Colors.black12),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
ListTile(
|
|
minVerticalPadding: 10 * scale,
|
|
leading: _avatar(imageUrl, initials, scale),
|
|
title: Text(description,
|
|
style:
|
|
TextStyle(fontWeight: FontWeight.bold, fontSize: 15 * scale)),
|
|
trailing: Transform.rotate(
|
|
angle: (_expanded[id]! ? 3.14 : 0),
|
|
child: Icon(Icons.keyboard_arrow_down, size: 24 * scale),
|
|
),
|
|
onTap: () {
|
|
setState(() {
|
|
_expanded[id] = !_expanded[id]!;
|
|
});
|
|
},
|
|
),
|
|
|
|
if (_expanded[id]!)
|
|
Padding(
|
|
padding: EdgeInsets.all(12 * scale),
|
|
child: Column(
|
|
children: [
|
|
_pill("Qty", Icons.list_alt, "${item['qty']}",
|
|
Colors.blue.shade100, scale),
|
|
_pill("Unit", Icons.category, "${item['unit']}",
|
|
Colors.orange.shade100, scale),
|
|
_pill("KG", Icons.scale, "${item['kg']}",
|
|
Colors.red.shade100, scale),
|
|
_pill("CBM", Icons.straighten, "${item['cbm']}",
|
|
Colors.purple.shade100, scale),
|
|
_pill("Shop", Icons.storefront, "${item['shop_no']}",
|
|
Colors.grey.shade300, scale),
|
|
_pill("Amount", Icons.currency_rupee,
|
|
"₹${item['ttl_amount']}",
|
|
Colors.green.shade100, scale),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// -----------------------------
|
|
// AVATAR (RESPONSIVE)
|
|
// -----------------------------
|
|
Widget _avatar(String url, String initials, double scale) {
|
|
return Container(
|
|
width: 48 * scale,
|
|
height: 48 * scale,
|
|
decoration: BoxDecoration(
|
|
color: Colors.blue.shade200,
|
|
borderRadius: BorderRadius.circular(10 * scale),
|
|
),
|
|
child: url.isNotEmpty
|
|
? ClipRRect(
|
|
borderRadius: BorderRadius.circular(10 * scale),
|
|
child: Image.network(url, fit: BoxFit.cover,
|
|
errorBuilder: (c, e, s) => Center(
|
|
child: Text(initials,
|
|
style: TextStyle(
|
|
fontSize: 18 * scale,
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.bold)),
|
|
)),
|
|
)
|
|
: Center(
|
|
child: Text(initials,
|
|
style: TextStyle(
|
|
fontSize: 18 * scale,
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.bold)),
|
|
),
|
|
);
|
|
}
|
|
|
|
// -----------------------------
|
|
// COLOR-CODED PILL (RESPONSIVE)
|
|
// -----------------------------
|
|
Widget _pill(
|
|
String title, IconData icon, String value, Color bgColor, double scale) {
|
|
return Container(
|
|
margin: EdgeInsets.only(bottom: 12 * scale),
|
|
padding:
|
|
EdgeInsets.symmetric(horizontal: 14 * scale, vertical: 12 * scale),
|
|
decoration: BoxDecoration(
|
|
color: bgColor,
|
|
borderRadius: BorderRadius.circular(12 * scale),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(icon, size: 20 * scale, color: Colors.black54),
|
|
SizedBox(width: 10 * scale),
|
|
Text("$title: ",
|
|
style:
|
|
TextStyle(fontWeight: FontWeight.bold, fontSize: 14 * scale)),
|
|
Expanded(
|
|
child: Text(value,
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.w700, fontSize: 15 * scale)),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// -----------------------------
|
|
// TOTAL SECTION
|
|
// -----------------------------
|
|
Widget _totalsSection(double scale) {
|
|
return Container(
|
|
padding: EdgeInsets.all(18 * scale),
|
|
decoration: _cardDecoration(scale),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text("Totalss",
|
|
style: TextStyle(fontSize: 18 * scale, fontWeight: FontWeight.bold)),
|
|
SizedBox(height: 12 * scale),
|
|
_totalRow("Total Qty", order['ttl_qty'], scale),
|
|
_totalRow("Total KG", order['ttl_kg'], scale),
|
|
SizedBox(height: 12 * scale),
|
|
|
|
Container(
|
|
width: double.infinity,
|
|
padding: EdgeInsets.symmetric(vertical: 20 * scale),
|
|
decoration: BoxDecoration(
|
|
color: Colors.green.shade50,
|
|
borderRadius: BorderRadius.circular(12 * scale),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Text("₹${order['ttl_amount'] ?? 0}",
|
|
style: TextStyle(
|
|
color: Colors.green,
|
|
fontSize: 28 * scale,
|
|
fontWeight: FontWeight.bold,
|
|
)),
|
|
SizedBox(height: 4 * scale),
|
|
Text("Total Amount",
|
|
style: TextStyle(
|
|
color: Colors.black54, fontSize: 14 * scale)),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _totalRow(String title, dynamic value, double scale) {
|
|
return Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(title,
|
|
style: TextStyle(color: Colors.grey, fontSize: 14 * scale)),
|
|
Text(value?.toString() ?? "0",
|
|
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15 * scale)),
|
|
],
|
|
);
|
|
}
|
|
|
|
// -----------------------------
|
|
// CARD DECORATION
|
|
// -----------------------------
|
|
BoxDecoration _cardDecoration(double scale) {
|
|
return BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16 * scale),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black12,
|
|
blurRadius: 8 * scale,
|
|
offset: Offset(0, 3 * scale)),
|
|
],
|
|
);
|
|
}
|
|
}
|