import React, { useState } from "react";
import {
View,
ScrollView,
TouchableOpacity,
Platform,
Alert,
} from "react-native";
import { useLocalSearchParams, useRouter } from "expo-router";
import { AppContainer, MyText, tw, BottomDialog, MyTextInput, theme } from "common-ui";
import { trpc } from "@/src/trpc-client";
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
import FontAwesome5 from "@expo/vector-icons/FontAwesome5";
import dayjs from "dayjs";
import CancelOrderDialog from "@/components/CancelOrderDialog";
function UserIncidentDialog({ userId, orderId, open, onClose, onSuccess }: { userId: number; orderId: number; open: boolean; onClose: () => void; onSuccess?: () => void }) {
const [adminComment, setAdminComment] = useState('');
const [negativityScore, setNegativityScore] = useState('');
const addIncidentMutation = trpc.admin.user.addUserIncident.useMutation({
onSuccess: () => {
Alert.alert('Success', 'Incident added successfully');
setAdminComment('');
setNegativityScore('');
onClose();
onSuccess?.();
},
onError: (error: any) => {
Alert.alert('Error', error.message || 'Failed to add incident');
},
});
const handleAddIncident = () => {
const score = negativityScore ? parseInt(negativityScore) : undefined;
if (!adminComment.trim() && !negativityScore) {
Alert.alert('Error', 'Please enter a comment or negativity score');
return;
}
addIncidentMutation.mutate({
userId,
orderId,
adminComment: adminComment || undefined,
negativityScore: score,
});
};
return (
Add User Incident
Record an incident for this user. This will be visible in their profile.
Higher negativity scores indicate more serious incidents (e.g., repeated cancellations, abusive behavior).
Cancel
{addIncidentMutation.isPending ? 'Adding...' : 'Add Incident'}
);
}
function UserIncidentsView({ userId, orderId }: { userId: number; orderId: number }) {
const [incidentDialogOpen, setIncidentDialogOpen] = useState(false);
const { data: incidentsData, refetch: refetchIncidents } = trpc.admin.user.getUserIncidents.useQuery(
{ userId },
{ enabled: !!userId }
);
return (
<>
User Incidents
setIncidentDialogOpen(true)}
style={tw`flex-row items-center bg-amber-200 px-3 py-1.5 rounded-lg`}
>
Add Incident
{incidentsData?.incidents && incidentsData.incidents.length > 0 ? (
{incidentsData.incidents.map((incident: any, index: number) => (
{dayjs(incident.dateAdded).format('MMM DD, YYYY • h:mm A')}
{incident.negativityScore && (
Score: {incident.negativityScore}
)}
{incident.adminComment && (
{incident.adminComment}
)}
Added by {incident.addedBy}
{incident.orderId && (
<>
Order #{incident.orderId}
>
)}
))}
) : (
No incidents recorded for this user
)}
setIncidentDialogOpen(false)}
onSuccess={refetchIncidents}
/>
>
);
}
export default function OrderDetails() {
const { id } = useLocalSearchParams<{ id: string }>();
const router = useRouter();
const [generateCouponDialogOpen, setGenerateCouponDialogOpen] =
useState(false);
const [initiateRefundDialogOpen, setInitiateRefundDialogOpen] =
useState(false);
const [cancelDialogOpen, setCancelDialogOpen] = useState(false);
const [refundType, setRefundType] = useState<"percent" | "amount">("percent");
const [refundValue, setRefundValue] = useState("100");
const [updatingItems, setUpdatingItems] = useState>(new Set());
const {
data: orderData,
isLoading,
error,
refetch,
} = trpc.admin.order.getOrderDetails.useQuery(
{ orderId: id ? parseInt(id) : 0 },
{ enabled: !!id }
);
const generateCouponMutation =
trpc.admin.coupon.generateCancellationCoupon.useMutation({
onSuccess: (coupon) => {
Alert.alert(
"Success",
`Refund coupon generated successfully!\n\nCode: ${coupon.couponCode
}\nValue: ₹${coupon.flatDiscount}\nExpires: ${coupon.validTill
? new Date(coupon.validTill).toLocaleDateString()
: "N/A"
}`
);
setGenerateCouponDialogOpen(false);
},
onError: (error: any) => {
Alert.alert(
"Error",
error.message || "Failed to generate refund coupon"
);
},
});
const initiateRefundMutation = trpc.admin.payments.initiateRefund.useMutation(
{
onSuccess: (result) => {
Alert.alert(
"Success",
`Refund initiated successfully!\n\nAmount: ₹${result.amount}\nStatus: ${result.status}`
);
setInitiateRefundDialogOpen(false);
},
onError: (error: any) => {
Alert.alert("Error", error.message || "Failed to initiate refund");
},
}
);
const updateItemPackagingMutation = trpc.admin.order.updateOrderItemPackaging.useMutation({
onSuccess: () => {
// Refetch order details to get updated packaging status
refetch();
},
onError: (error: any) => {
Alert.alert("Error", error.message || "Failed to update packaging status");
},
});
const removeDeliveryChargeMutation = trpc.admin.order.removeDeliveryCharge.useMutation({
onSuccess: () => {
Alert.alert("Success", "Delivery charge has been removed");
refetch();
},
onError: (error: any) => {
Alert.alert("Error", error.message || "Failed to remove delivery charge");
},
});
if (isLoading) {
return (
Loading order details...
);
}
if (error || !orderData) {
return (
Oops!
{error?.message || "Failed to load order details"}
router.back()}
style={tw`bg-gray-900 px-6 py-3 rounded-xl w-full items-center`}
>
Go Back
);
}
const order = orderData;
// Calculate subtotal and discount for order summary
const subtotal = order.items.reduce((sum, item) => sum + item.amount, 0);
const discountAmount = order.discountAmount || 0;
const handleGenerateCoupon = () => {
generateCouponMutation.mutate({ orderId: order.id });
};
const handleInitiateRefund = () => {
const value = parseFloat(refundValue);
if (isNaN(value) || value <= 0) {
Alert.alert("Error", "Please enter a valid refund value");
return;
}
const mutationData: any = {
orderId: order.id,
};
if (refundType === "percent") {
if (value > 100) {
Alert.alert("Error", "Refund percentage cannot exceed 100%");
return;
}
mutationData.refundPercent = value;
} else {
mutationData.refundAmount = value;
}
initiateRefundMutation.mutate(mutationData);
setInitiateRefundDialogOpen(false);
};
const handlePackagingToggle = (itemId: number, field: 'isPackaged' | 'isPackageVerified', value: boolean) => {
// Add item to updating set to disable UI
setUpdatingItems(prev => new Set(prev).add(itemId));
updateItemPackagingMutation.mutate(
{
orderItemId: itemId,
[field]: value,
},
{
onSettled: () => {
// Remove item from updating set
setUpdatingItems(prev => {
const newSet = new Set(prev);
newSet.delete(itemId);
return newSet;
});
},
}
);
};
const getStatusColor = (status: string) => {
switch (status) {
case "delivered":
return "text-green-600 bg-green-50 border-green-100";
case "cancelled":
return "text-red-600 bg-red-50 border-red-100";
default:
return "text-yellow-600 bg-yellow-50 border-yellow-100";
}
};
const statusStyle = getStatusColor(order.status);
const showRefundOptions = true;
const getRefundDotColor = (status: string) => {
if (status === 'success') return 'bg-green-500';
else if (status === 'pending') return 'bg-yellow-500';
else if (status === 'failed') return 'bg-red-500';
else return 'bg-gray-500';
};
const getRefundTextColor = (status: string) => {
if (status === 'success' || status === 'na') return 'text-green-700';
else if (status === 'pending') return 'text-yellow-700';
else if (status === 'failed') return 'text-red-700';
else return 'text-gray-700';
};
const getRefundStatusText = (status: string) => {
if (status === 'success' || status === 'na') return 'Completed';
else if (status === 'pending') return 'Pending';
else if (status === 'failed') return 'Failed';
else if (status === 'none') return 'Not Initiated';
else if (status === 'na') return 'Not Applicable';
else return 'Unknown';
};
return (
{/* Order ID & Status Card */}
Order ID
#{order.readableId}
{order.status}
{order.isFlashDelivery && (
⚡
)}
{order.isFlashDelivery ? "1 Hr Delivery:" : "Delivery at:"} {order.isFlashDelivery
? dayjs(order.createdAt).add(30, 'minutes').format("MMM DD, YYYY • h:mm A")
: order.slotInfo?.time ? dayjs(order.slotInfo.time).format("MMM DD, YYYY • h:mm A") : 'Not scheduled'}
{order.isFlashDelivery && (
⚡ 1 Hr Delivery • High Priority
)}
Placed on {dayjs(order.createdAt).format("MMM DD, YYYY • h:mm A")}
{/* Cancellation Reason */}
{order.status === "cancelled" && order.cancelReason && (
Cancellation Reason
{order.cancelReason}
)}
{/* Order Progress (Simplified Timeline) */}
{order.status !== "cancelled" && (
Order Status
{/* Placed */}
Placed
{/* Packaged */}
Packaged
{/* Delivered */}
{order.isFlashDelivery && order.isDelivered ? (
) : (
)}
{order.isFlashDelivery && order.isDelivered ? "Flash Delivered" : "Delivered"}
{order.isFlashDelivery && (
⚡ 30-Min
)}
)}
{/* Customer Details */}
order.userId && router.push(`/(drawer)/user-management/${order.userId}`)}
activeOpacity={0.7}
style={tw`bg-white p-5 rounded-2xl shadow-sm mb-4 border border-gray-100`}
>
Customer Details
{order.customerName || 'Unknown User'}
Customer
{order.customerMobile}
{order.customerEmail && (
{order.customerEmail}
)}
{order.address.name} {' '}
{order.address.line1}
{order.address.line2 ? `, ${order.address.line2}` : ""}
{`\n${order.address.city}, ${order.address.state} - ${order.address.pincode}`}
{/* Order Items */}
Items Ordered
{order.items.map((item, index) => (
{item.name}
{Number(item.quantity) * item.productSize} {item.unit} × ₹{item.price}
handlePackagingToggle(item.id, 'isPackaged', !item.isPackaged)}
disabled={updatingItems.has(item.id)}
>
Packaged
handlePackagingToggle(item.id, 'isPackageVerified', !item.isPackageVerified)}
disabled={updatingItems.has(item.id)}
>
Pkg Verified
₹{item.amount}
))}
Subtotal ({order.items.length} items)
₹{subtotal}
{discountAmount > 0 && (
Discount
-₹{discountAmount}
)}
{order.deliveryCharge > 0 && (
Delivery Charge
{
Alert.alert(
'Remove Delivery Cost',
'Are you sure you want to remove the delivery cost from this order?',
[
{ text: 'Cancel', style: 'cancel' },
{
text: 'Remove',
style: 'destructive',
onPress: () => removeDeliveryChargeMutation.mutate({ orderId: order.id }),
},
]
);
}}
disabled={removeDeliveryChargeMutation.isPending}
style={tw`ml-2 px-2 py-1 bg-red-100 rounded-md`}
>
{removeDeliveryChargeMutation.isPending ? 'Removing...' : 'Remove'}
₹{order.deliveryCharge}
)}
Total Amount
{order.isFlashDelivery && (
⚡ FLASH
)}
₹{order.totalAmount}
{/* 1 Hr Delivery Priority Notice */}
{order.isFlashDelivery && (
1 Hr Delivery Order
⚡ This is a high-priority flash delivery order that must be delivered within 1 hr of placement.
Expected Delivery: {dayjs(order.createdAt).add(30, 'minutes').format("MMM DD, YYYY • h:mm A")}
)}
{/* Admin Notes */}
{order.adminNotes && (
Admin Notes
{order.adminNotes}
)}
{/* User Incidents Section */}
{order.userId && (
)}
{/* Coupon Applied Section */}
{order.couponCode && (
Coupon Applied
{order.couponCode}
{order.couponDescription}
Discount Applied:
-₹{order.discountAmount}
)}
{/* Refund Coupon Section */}
{order.orderStatus?.refundCouponId && (
Refund Coupon
{order.couponCode}
Generated refund coupon for order cancellation
Value:
₹{order.couponData?.discountAmount}
{/*
Expires:
{order.couponData?.
? dayjs(order.orderStatus.refundCoupon.validTill).format("DD MMM YYYY")
: "N/A"}
*/}
)}
{/* TEMPORARILY HIDDEN: Refund Details Section */}
{/* WARNING: This section contains functional refund and cancellation management features */}
{/* DO NOT REMOVE - This is temporarily commented out for UI simplification */}
{/* When ready to re-enable, simply uncomment the entire block below */}
{/*
{order.status === "cancelled" ? "Cancellation Details" : "Refund Details"}
{order.status === "cancelled" && order.cancelReason && (
Cancellation Reason
{order.cancelReason}
)}
Refund Status
{getRefundStatusText(order.refundStatus)}
{order.refundRecord && (
Refund Amount
₹{order.refundRecord.refundAmount}
)}
{(!Boolean(order.refundRecord)) &&
{!order.isCod && ( setInitiateRefundDialogOpen(true)}
>
Initiate Refund
)}
setGenerateCouponDialogOpen(true)}
>
Generate Refund Coupon
}
*/}
{/* Bottom Action Bar */}
{order.status !== "cancelled" && (
setCancelDialogOpen(true)}
style={tw`bg-red-500 rounded-xl py-4 items-center shadow-lg mb-3`}
>
Cancel Order
)}
router.push("/(drawer)/manage-orders")}
style={tw`bg-gray-900 rounded-xl py-4 items-center shadow-lg`}
>
Manage Orders
{/* Generate Coupon Dialog */}
setGenerateCouponDialogOpen(false)}
>
Generate Refund Coupon
Create a one-time use coupon for the customer equal to the order amount. Valid for 30 days.
This only works for online payment orders. COD orders cannot generate refund coupons.
setGenerateCouponDialogOpen(false)}
>
Cancel
{generateCouponMutation.isPending
? "Generating..."
: "Generate Coupon"}
{/* Initiate Refund Dialog */}
setInitiateRefundDialogOpen(false)}
>
Initiate Refund
Process a refund directly to the customer's source account via Razorpay.
{/* Refund Type Selection */}
Refund Type
setRefundType("percent")}
>
Percentage
setRefundType("amount")}
>
Fixed Amount
{/* Refund Value Input */}
For COD orders, refunds are processed immediately upon delivery confirmation.
setInitiateRefundDialogOpen(false)}
>
Cancel
{initiateRefundMutation.isPending
? "Processing..."
: "Confirm Refund"}
{/* Cancel Order Dialog */}
setCancelDialogOpen(false)}
onSuccess={refetch}
/>
);
}