412 lines
17 KiB
Dart
412 lines
17 KiB
Dart
part of 'deliveries.dart';
|
|
|
|
// -------------------------------------------------------------------------
|
|
// SKIP REASONS SHEET
|
|
// -------------------------------------------------------------------------
|
|
Future<void> _showMyOptionsSheet(
|
|
BuildContext context,
|
|
Map<String, dynamic> delivery,
|
|
_MyDeliveriesState? parentState,
|
|
) async {
|
|
int selected = -1;
|
|
bool isLoading = false;
|
|
|
|
final List<String> skipReasons = [
|
|
'Customer unreachable',
|
|
'Customer not at the location',
|
|
];
|
|
|
|
await showModalBottomSheet(
|
|
context: context,
|
|
isScrollControlled: true,
|
|
backgroundColor: Colors.white,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
|
),
|
|
builder: (context) {
|
|
return SafeArea(
|
|
top: false,
|
|
left: false,
|
|
right: false,
|
|
bottom: true,
|
|
child: StatefulBuilder(
|
|
builder: (context, setState) {
|
|
Widget optionBox(String title, int index) {
|
|
final bool isSelected = selected == index;
|
|
|
|
return GestureDetector(
|
|
onTap: () {
|
|
if (!isLoading) {
|
|
setState(() => selected = index);
|
|
}
|
|
},
|
|
child: Container(
|
|
padding: const EdgeInsets.all(16),
|
|
margin: const EdgeInsets.only(bottom: 12),
|
|
decoration: BoxDecoration(
|
|
color: isSelected
|
|
? ColorConstants.primaryColor.withOpacity(0.1)
|
|
: Colors.grey.shade100,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: isSelected
|
|
? ColorConstants.primaryColor
|
|
: Colors.grey.shade300,
|
|
width: isSelected ? 2 : 1,
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
isSelected ? Icons.check_circle : Icons.circle_outlined,
|
|
color: isSelected
|
|
? ColorConstants.primaryColor
|
|
: Colors.grey,
|
|
size: 28,
|
|
),
|
|
const SizedBox(width: 12),
|
|
Text(
|
|
title,
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.black87,
|
|
fontFamily: FontConstants.fontFamily,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> handleConfirm() async {
|
|
if (selected == -1 || isLoading) return;
|
|
|
|
// Check skip limits before proceeding
|
|
final dc = Get.put(DeliveriesController(), permanent: true);
|
|
final skipStatus = await dc.checkSkipStatus();
|
|
final int skipCount = skipStatus['count'] ?? 0;
|
|
bool applyPenalty = false;
|
|
|
|
if (skipCount >= 2) {
|
|
// Show Styled Alert Dialog
|
|
final bool? confirm = await showDialog<bool>(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (BuildContext context) {
|
|
return Dialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
elevation: 5,
|
|
backgroundColor: Colors.white,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.red.shade50,
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(
|
|
Icons.warning_amber_rounded,
|
|
color: Colors.red.shade600,
|
|
size: 40,
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
Text(
|
|
'Skip Limit Reached',
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(
|
|
fontSize: 22,
|
|
fontWeight: FontWeight.bold,
|
|
fontFamily: FontConstants.fontFamily,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
'You have exceeded the limit of 2 skips within 3 hours.',
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontFamily: FontConstants.fontFamily,
|
|
color: Colors.black54,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: Colors.orange.shade50,
|
|
borderRadius: BorderRadius.circular(10),
|
|
border: Border.all(
|
|
color: Colors.orange.shade200,
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.info_outline, color: Colors.orange.shade800, size: 20),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: Text(
|
|
"Proceeding will forfeit your bonus points for this session.",
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.orange.shade900,
|
|
fontFamily: FontConstants.fontFamily,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: OutlinedButton(
|
|
onPressed: () => Navigator.pop(context, false),
|
|
style: OutlinedButton.styleFrom(
|
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
|
side: BorderSide(color: Colors.grey.shade300),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
child: Text(
|
|
"Cancel",
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.black54,
|
|
fontFamily: FontConstants.fontFamily,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: ElevatedButton(
|
|
onPressed: () => Navigator.pop(context, true),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.red.shade600,
|
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
child: Text(
|
|
"Confirm Skip",
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
fontFamily: FontConstants.fontFamily,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
|
|
if (confirm != true) return; // User cancelled or dismissed
|
|
applyPenalty = true;
|
|
}
|
|
|
|
setState(() => isLoading = true);
|
|
|
|
try {
|
|
final dc = Get.put(DeliveriesController(), permanent: true);
|
|
final d = delivery;
|
|
final int deliveryId =
|
|
int.tryParse('${d['deliveryid'] ?? 0}') ?? 0;
|
|
final int orderHeaderId =
|
|
int.tryParse('${d['orderheaderid'] ?? 0}') ?? 0;
|
|
final String reason = selected >= 0 && selected < skipReasons.length
|
|
? skipReasons[selected]
|
|
: '';
|
|
|
|
if (deliveryId > 0 && orderHeaderId > 0) {
|
|
String riderLatStr = '0';
|
|
String riderLngStr = '0';
|
|
try {
|
|
final position = await Geolocator.getCurrentPosition(
|
|
desiredAccuracy: LocationAccuracy.high,
|
|
timeLimit: const Duration(seconds: 5),
|
|
);
|
|
riderLatStr = position.latitude.toStringAsFixed(6);
|
|
riderLngStr = position.longitude.toStringAsFixed(6);
|
|
} catch (e) {
|
|
debugPrint('[SKIP] Error getting rider location: $e');
|
|
try {
|
|
final lastPos = await Geolocator.getLastKnownPosition();
|
|
if (lastPos != null) {
|
|
riderLatStr = lastPos.latitude.toStringAsFixed(6);
|
|
riderLngStr = lastPos.longitude.toStringAsFixed(6);
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
|
|
debugPrint(
|
|
'[SKIP] Updating status for deliveryId=$deliveryId orderHeaderId=$orderHeaderId reason="$reason"',
|
|
);
|
|
|
|
final ok = await dc.updateSkippedStatus(
|
|
deliveryId: deliveryId,
|
|
orderHeaderId: orderHeaderId,
|
|
ridersLat: riderLatStr,
|
|
ridersLng: riderLngStr,
|
|
notes: reason,
|
|
);
|
|
|
|
debugPrint('[SKIP] Status update result: $ok');
|
|
|
|
if (ok && context.mounted) {
|
|
// Register skip locally only on success
|
|
await dc.registerSkip(applyPenalty: applyPenalty);
|
|
|
|
if (context.mounted) {
|
|
Navigator.pop(context); // Close the sheet
|
|
|
|
if (parentState != null) {
|
|
// Only update list if called from the list view
|
|
parentState.markOrderAsSkipped(d, reason);
|
|
} else {
|
|
// If called from Navigation/Map (where parentState is null),
|
|
// we just close the sheet and let the caller handle the UI update
|
|
// typically by popping the route or showing a snackbar.
|
|
// We DO NOT force navigation to "MyDeliveries".
|
|
debugPrint(
|
|
'[SKIP] Skipped from Nav/Map screen, sheet closed.',
|
|
);
|
|
}
|
|
}
|
|
} else if (context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text(
|
|
'Failed to skip delivery. Please try again.',
|
|
),
|
|
backgroundColor: Colors.red,
|
|
),
|
|
);
|
|
}
|
|
} else {
|
|
debugPrint(
|
|
'[SKIP] Invalid deliveryId: $deliveryId or orderHeaderId: $orderHeaderId',
|
|
);
|
|
if (context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('Invalid delivery information.'),
|
|
backgroundColor: Colors.red,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
debugPrint('[SKIP] Error updating skip status: $e');
|
|
if (context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('An error occurred. Please try again.'),
|
|
backgroundColor: Colors.red,
|
|
),
|
|
);
|
|
}
|
|
} finally {
|
|
if (context.mounted) {
|
|
setState(() => isLoading = false);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.fromLTRB(16, 20, 16, 30),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'Select Reason',
|
|
style: TextStyle(
|
|
fontSize: 22,
|
|
fontWeight: FontWeight.bold,
|
|
fontFamily: FontConstants.fontFamily,
|
|
),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.cancel,
|
|
color: Colors.red, size: 32),
|
|
onPressed: isLoading ? null : () => Navigator.pop(context),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 20),
|
|
optionBox('Customer unreachable', 0),
|
|
optionBox('Customer not at the location', 1),
|
|
const SizedBox(height: 20),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
height: 55,
|
|
child: ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: (selected == -1 || isLoading)
|
|
? Colors.grey.shade300
|
|
: ColorConstants.primaryColor,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
onPressed:
|
|
(selected == -1 || isLoading) ? null : handleConfirm,
|
|
child: isLoading
|
|
? const SizedBox(
|
|
width: 24,
|
|
height: 24,
|
|
child: CircularProgressIndicator(
|
|
strokeWidth: 3,
|
|
valueColor:
|
|
AlwaysStoppedAnimation<Color>(Colors.white),
|
|
),
|
|
)
|
|
: Text(
|
|
'Confirm',
|
|
style: TextStyle(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.bold,
|
|
fontFamily: FontConstants.fontFamily,
|
|
color: (selected == -1 || isLoading)
|
|
? Colors.black45
|
|
: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|