454 lines
17 KiB
TypeScript

import React, { useEffect, useState, useCallback } from 'react';
import {
View,
Text,
ScrollView,
ActivityIndicator,
TouchableOpacity,
Modal,
TextInput,
Alert,
} from 'react-native';
import { useRoute, useNavigation } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import { RouteProp } from '@react-navigation/native';
import { Header, OrderStatusTimeline, ProductRatingModal } from '@components';
import { useAppTheme } from '@theme';
import { getStyles } from './orderDetailsScreen.styles';
import { RootState, useAppDispatch, useAppSelector } from '@store';
import { getOrderByIdThunk } from '../checkoutPaymentScreen';
import { formatDate, formatTime } from '@utils/helper';
import { submitReview } from './thunk';
import { giveRatingPayload, OrderItem, TicketCategory } from '@interfaces';
import { AppStackParamList } from '@navigation';
import { useCustomerSupport } from '../../../hooks/useCustomerSupport';
type OrderDetailsRouteProp = RouteProp<AppStackParamList, 'OrderDetailsScreen'>;
type OrderDetailsNavProp = StackNavigationProp<AppStackParamList>;
export const OrderDetailsScreen: React.FC = () => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
const route = useRoute<OrderDetailsRouteProp>();
const navigation = useNavigation<OrderDetailsNavProp>();
const dispatch = useAppDispatch();
const orderId = route.params?.orderId;
const { orderDetails, orderDetailsLoading, orderDetailsError } =
useAppSelector((state: RootState) => state.paymentMethods);
// ── Rating modal state ────────────────────────────────────────────────────
const [ratingModalItem, setRatingModalItem] = useState<OrderItem | null>(
null,
);
/** Tracks which productIds have already been rated this session */
const [ratedProductIds, setRatedProductIds] = useState<Set<string>>(
new Set(),
);
const isDelivered = orderDetails?.status === 'DELIVERED';
// ── Support / Need Help modal state ───────────────────────────────────────
const [helpModalVisible, setHelpModalVisible] = useState(false);
const [categorySelectVisible, setCategorySelectVisible] = useState(false);
const [helpCategory, setHelpCategory] = useState<TicketCategory>('ORDER');
const [helpDescription, setHelpDescription] = useState('');
const [submittingTicket, setSubmittingTicket] = useState(false);
const { createTicket } = useCustomerSupport();
const handleHelpSubmit = async () => {
if (!orderDetails) return;
if (!helpDescription.trim()) {
Alert.alert('Error', 'Please describe the issue in detail.');
return;
}
setSubmittingTicket(true);
try {
const orderShortId = orderDetails.orderNumber?.split('-').pop() || orderDetails.id;
const newTicket = await createTicket({
category: helpCategory,
priority: 'MEDIUM',
subject: `Issue with Order #${orderShortId}`,
description: helpDescription.trim(),
orderId: orderDetails.id,
});
setHelpModalVisible(false);
setHelpDescription('');
Alert.alert('Success', 'Support ticket created successfully!');
navigation.navigate('SupportChatScreen', { ticketId: newTicket.id });
} catch (error) {
Alert.alert('Error', 'Failed to create support ticket. Please try again.');
} finally {
setSubmittingTicket(false);
}
};
useEffect(() => {
if (orderId) {
dispatch(getOrderByIdThunk(orderId));
}
}, [dispatch, orderId]);
const handleRatingSubmit = useCallback(
(payload: giveRatingPayload) => {
if (!ratingModalItem) return;
// Dispatch the API call: POST /products/:productId/ratings
dispatch(
submitReview({
id: ratingModalItem.productId,
payload: {
rating: payload.rating,
comment: payload.comment,
images: payload.images,
orderId: orderDetails?.id ?? '',
},
}),
)
.unwrap()
.then(() => {
setRatedProductIds(prev =>
new Set(prev).add(ratingModalItem.productId),
);
});
},
[dispatch, ratingModalItem, orderDetails?.id],
);
const handleModalClose = useCallback(() => {
setRatingModalItem(null);
}, []);
// ── Loading ───────────────────────────────────────────────────────────────
if (orderDetailsLoading) {
return (
<View style={styles.container}>
<Header title="Order Details" onBack={() => navigation.goBack()} />
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color={colors.primary} />
<Text style={styles.loadingText}>Fetching order details...</Text>
</View>
</View>
);
}
if (orderDetailsError || !orderDetails) {
return (
<View style={styles.container}>
<Header title="Order Details" onBack={() => navigation.goBack()} />
<View style={styles.errorContainer}>
<Text style={styles.errorText}>
{orderDetailsError || 'Could not load order details.'}
</Text>
</View>
</View>
);
}
const orderDate = orderDetails.createdAt
? `${formatDate(orderDetails.createdAt)} at ${formatTime(
orderDetails.createdAt,
)}`
: '';
return (
<View style={styles.container}>
<Header title="Order Details" onBack={() => navigation.goBack()} />
<ScrollView contentContainerStyle={styles.content}>
{/* Top Info Section */}
<View style={styles.section}>
<View style={styles.headerRow}>
<Text style={styles.orderNumber}>
Order {orderDetails.orderNumber?.split('-').pop() || ''}
</Text>
<View style={styles.statusBadge}>
<Text style={styles.statusText}>{orderDetails.status}</Text>
</View>
</View>
<Text style={styles.dateText}>Placed on {orderDate}</Text>
<Text style={styles.dateText}>
Payment: {orderDetails.paymentMethod}
</Text>
{/* Merchant Info */}
<View style={styles.merchantRow}>
<View style={styles.merchantIcon}>
<Text>🏪</Text>
</View>
<Text style={styles.merchantName}>
{orderDetails.merchant?.name || 'Store'}
</Text>
</View>
{/* Need Help Button */}
<TouchableOpacity
style={styles.orderNeedHelpBtn}
onPress={() => setHelpModalVisible(true)}
activeOpacity={0.7}
>
<Text style={styles.orderNeedHelpText}> Need help with this order?</Text>
</TouchableOpacity>
</View>
{/* Order Status Timeline Section */}
<View style={styles.timelineSection}>
<Text style={styles.sectionTitle}>Order Status</Text>
<OrderStatusTimeline
tracking={orderDetails.tracking}
currentStatus={orderDetails.status}
deliveryPartner={orderDetails?.delivery?.deliveryPartner?.user}
/>
</View>
{/* Order Items Section */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Order Items</Text>
{/* Rate all hint — shown only when delivered */}
{isDelivered && (
<View style={styles.rateHintBanner}>
<Text style={styles.rateHintEmoji}></Text>
<Text style={styles.rateHintText}>
Tap any item below to rate it
</Text>
</View>
)}
{orderDetails.orderItems?.map((item, index) => {
const isRated = ratedProductIds.has(item.productId);
return (
<TouchableOpacity
key={item.id || index}
style={[styles.itemRow, isDelivered && styles.itemRowTappable]}
activeOpacity={isDelivered ? 0.65 : 1}
onPress={() => {
if (isDelivered) {
setRatingModalItem(item);
}
}}
disabled={!isDelivered}
>
<View style={styles.itemInfo}>
<View style={styles.itemQuantityBadge}>
<Text style={styles.itemQuantityText}>
{item.quantity}x
</Text>
</View>
<View style={{ flex: 1 }}>
<Text style={styles.itemName}>{item.name}</Text>
{/* Rating status pill — only shown when DELIVERED */}
{isDelivered && (
<View style={styles.ratingPillRow}>
{isRated ? (
<View style={styles.ratedPill}>
<Text style={styles.ratedPillText}> Rated</Text>
</View>
) : (
<View style={styles.ratePill}>
<Text style={styles.ratePillText}>
Rate this
</Text>
</View>
)}
</View>
)}
</View>
</View>
<Text style={styles.itemPrice}>{item.totalAmount}</Text>
</TouchableOpacity>
);
})}
</View>
{/* Bill Summary Section */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Bill Summary</Text>
<View style={styles.billRow}>
<Text style={styles.billLabel}>Item Total</Text>
<Text style={styles.billValue}>{orderDetails.subtotal}</Text>
</View>
<View style={styles.billRow}>
<Text style={styles.billLabel}>Delivery Fee</Text>
<Text style={styles.billValue}>{orderDetails.deliveryFee}</Text>
</View>
<View style={styles.billRow}>
<Text style={styles.billLabel}>Platform Fee</Text>
<Text style={styles.billValue}>{orderDetails.platformFee}</Text>
</View>
<View style={styles.billRow}>
<Text style={styles.billLabel}>Taxes</Text>
<Text style={styles.billValue}>{orderDetails.taxAmount}</Text>
</View>
<View style={styles.billDivider} />
<View style={styles.billRow}>
<Text style={styles.billTotalLabel}>Grand Total</Text>
<Text style={styles.billTotalValue}>
{orderDetails.totalAmount}
</Text>
</View>
</View>
{/* Delivery Address Section */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Delivery Details</Text>
<Text style={styles.addressType}>
{orderDetails.dropAddress?.label || 'Home'}
</Text>
<Text style={styles.addressText}>
{orderDetails.dropAddress?.houseNumber
? `${orderDetails.dropAddress.houseNumber}, `
: ''}
{orderDetails.dropAddress?.addressLine1}
</Text>
<Text style={styles.addressText}>
{orderDetails.dropAddress?.landmark
? `Landmark: ${orderDetails.dropAddress.landmark}`
: ''}
</Text>
<Text style={styles.addressText}>
Phone: {orderDetails.dropAddress?.phone}
</Text>
</View>
{/* Payment Info */}
{orderDetails.payments && orderDetails.payments.length > 0 && (
<View style={styles.section}>
<Text style={styles.sectionTitle}>Payment Details</Text>
<View style={styles.paymentRow}>
<Text style={styles.paymentIcon}>💳</Text>
<Text style={styles.paymentText}>
Paid via {orderDetails.payments[0].method} (
{orderDetails.payments[0].status})
</Text>
</View>
</View>
)}
</ScrollView>
{/* Product Rating Modal */}
<ProductRatingModal
visible={ratingModalItem !== null}
productId={ratingModalItem?.productId ?? ''}
productName={ratingModalItem?.name ?? ''}
orderId={orderDetails?.id}
quantity={ratingModalItem?.quantity}
onSubmit={handleRatingSubmit}
onClose={handleModalClose}
/>
{/* Need Help / Ticket Creation Modal */}
<Modal
visible={helpModalVisible}
transparent
animationType="slide"
onRequestClose={() => setHelpModalVisible(false)}
>
<View style={styles.modalOverlay}>
<View style={styles.modalContent}>
<Text style={styles.modalTitle}>Report Issue with Order</Text>
<ScrollView showsVerticalScrollIndicator={false}>
<Text style={styles.label}>Category</Text>
<TouchableOpacity
style={styles.categorySelect}
onPress={() => setCategorySelectVisible(true)}
>
<Text style={styles.categorySelectText}>
{helpCategory.replace('_', ' ')}
</Text>
<Text style={{ color: colors.textSecondary }}></Text>
</TouchableOpacity>
<Text style={styles.label}>Order ID</Text>
<TextInput
style={[styles.input, { opacity: 0.6 }]}
value={orderDetails.orderNumber || orderDetails.id}
editable={false}
/>
<Text style={styles.label}>Details</Text>
<TextInput
style={[styles.input, styles.textArea]}
value={helpDescription}
onChangeText={setHelpDescription}
placeholder="What seems to be the problem with this order?"
placeholderTextColor={colors.textSecondary}
multiline
numberOfLines={4}
/>
<View style={styles.modalActions}>
<TouchableOpacity
style={[styles.modalBtn, styles.cancelBtn]}
onPress={() => setHelpModalVisible(false)}
disabled={submittingTicket}
>
<Text style={styles.cancelBtnText}>Cancel</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.modalBtn, styles.submitBtn]}
onPress={handleHelpSubmit}
disabled={submittingTicket}
>
{submittingTicket ? (
<ActivityIndicator size="small" color="#FFFFFF" />
) : (
<Text style={styles.submitBtnText}>Submit</Text>
)}
</TouchableOpacity>
</View>
</ScrollView>
</View>
</View>
</Modal>
{/* Help Category Select Modal */}
<Modal
visible={categorySelectVisible}
transparent
animationType="fade"
onRequestClose={() => setCategorySelectVisible(false)}
>
<View style={{ flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'center', padding: 24 }}>
<View style={{ backgroundColor: colors.cardBg, borderRadius: 12, padding: 16 }}>
<Text style={{ fontSize: 18, fontWeight: 'bold', color: colors.text, marginBottom: 12, textAlign: 'center' }}>
Select Issue Category
</Text>
{[
{ value: 'ORDER', label: 'Order' },
{ value: 'PAYMENT', label: 'Payment' },
{ value: 'PAYOUT', label: 'Payout' },
{ value: 'TECHNICAL_ISSUE', label: 'Technical Issue' },
{ value: 'ACCOUNT', label: 'Account' },
{ value: 'OTHER', label: 'Other' },
].map((item) => (
<TouchableOpacity
key={item.value}
style={{ paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: colors.border }}
onPress={() => {
setHelpCategory(item.value as TicketCategory);
setCategorySelectVisible(false);
}}
>
<Text style={{ fontSize: 16, color: colors.text }}>{item.label}</Text>
</TouchableOpacity>
))}
<TouchableOpacity
style={{ marginTop: 16, paddingVertical: 12, alignItems: 'center' }}
onPress={() => setCategorySelectVisible(false)}
>
<Text style={{ color: colors.primary, fontWeight: 'bold' }}>Close</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
</View>
);
};
export default OrderDetailsScreen;