378 lines
12 KiB
TypeScript

import React, { useState } from 'react';
import {
View,
Text,
TouchableOpacity,
Linking,
Platform,
LayoutAnimation,
UIManager,
} from 'react-native';
import { OrderStatus, TrackingEntry } from '@interfaces';
import { formatDate, formatTime } from '@utils/helper';
import { OrderStatusTimelineProps } from './OrderStatusTimeline.props';
import { useAppTheme } from '@theme';
import { getStyles } from './OrderStatusTimeline.styles';
if (
Platform.OS === 'android' &&
UIManager.setLayoutAnimationEnabledExperimental
) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
export const STATUS_FLOW: OrderStatus[] = [
'PENDING',
'CONFIRMED',
'PREPARING',
'READY_FOR_PICKUP',
'OUT_FOR_DELIVERY',
'DELIVERED',
];
export const STATUS_LABEL: Record<OrderStatus, string> = {
PENDING: 'Order Placed',
CONFIRMED: 'Confirmed',
PREPARING: 'Preparing',
READY_FOR_PICKUP: 'Ready for Pickup',
OUT_FOR_DELIVERY: 'Out for Delivery',
DELIVERED: 'Delivered',
CANCELLED: 'Cancelled',
};
const STATUS_DETAILS: Record<
OrderStatus,
{ title: string; desc: string; icon: string }
> = {
PENDING: {
title: 'Waiting for Confirmation',
desc: 'The merchant is reviewing your order. We will start preparing it soon.',
icon: '⏳',
},
CONFIRMED: {
title: 'Order Confirmed',
desc: 'Your order has been accepted. The chef will start preparing it shortly.',
icon: '✅',
},
PREPARING: {
title: 'Preparing Your Food',
desc: 'The kitchen is preparing your delicious order with fresh ingredients.',
icon: '🍳',
},
READY_FOR_PICKUP: {
title: 'Ready for Pickup',
desc: 'Your order is ready. The delivery partner is picking it up now.',
icon: '📦',
},
OUT_FOR_DELIVERY: {
title: 'Out for Delivery',
desc: 'Our delivery partner is on the way with your food. Keep your phone handy!',
icon: '🛵',
},
DELIVERED: {
title: 'Order Delivered',
desc: 'Your order has been successfully delivered. Enjoy your meal!',
icon: '🎉',
},
CANCELLED: {
title: 'Order Cancelled',
desc: 'This order was cancelled. Any refund will be processed shortly.',
icon: '❌',
},
};
const getStepIndex = (status: OrderStatus): number => {
switch (status) {
case 'PENDING':
case 'CONFIRMED':
return 0;
case 'PREPARING':
return 1;
case 'READY_FOR_PICKUP':
case 'OUT_FOR_DELIVERY':
return 2;
case 'DELIVERED':
return 3;
default:
return 0;
}
};
export const OrderStatusTimeline: React.FC<OrderStatusTimelineProps> = ({
tracking = [],
currentStatus,
styles: propStyles,
}) => {
const { colors } = useAppTheme();
const localStyles = getStyles(colors);
// If the caller passes the screen styles, they won't have timelineRow etc, so we fallback to localStyles
const styles =
propStyles && propStyles.timelineRow ? propStyles : localStyles;
const [expanded, setExpanded] = useState(false);
const toggleExpand = () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setExpanded(!expanded);
};
const handleCall = () => {
Linking.openURL('tel:+919876543210');
};
const handleMessage = () => {
Linking.openURL('sms:+919876543210');
};
const trackingMap = tracking.reduce<Record<string, string>>((acc, t) => {
acc[t.status] = t.time;
return acc;
}, {});
const details = STATUS_DETAILS[currentStatus] || STATUS_DETAILS.PENDING;
if (currentStatus === 'CANCELLED') {
return (
<View style={localStyles.cardContainer}>
{/* Cancelled Status Banner */}
<View style={localStyles.headerSection}>
<View
style={[
localStyles.headerIconContainer,
{ backgroundColor: '#FFEBEE' },
]}
>
<Text style={localStyles.headerIconText}></Text>
</View>
<View style={localStyles.headerTextContainer}>
<Text
style={[localStyles.statusHeaderTitle, { color: colors.error }]}
>
{details.title}
</Text>
<Text style={localStyles.statusHeaderDesc}>{details.desc}</Text>
</View>
</View>
{/* Collapsible detailed view */}
<View style={localStyles.toggleSection}>
<TouchableOpacity
onPress={toggleExpand}
style={localStyles.toggleButton}
activeOpacity={0.7}
>
<Text style={localStyles.toggleButtonText}>
{expanded ? 'Hide Details' : 'View Detailed History'}
</Text>
<Text style={localStyles.toggleChevron}>
{expanded ? '▲' : '▼'}
</Text>
</TouchableOpacity>
</View>
{expanded && (
<View style={localStyles.detailedTimelineWrapper}>
<View style={styles.timelineRow}>
<View style={styles.timelineDotColumn}>
<View
style={[styles.timelineDot, styles.timelineDotCancelled]}
/>
</View>
<View style={styles.timelineContent}>
<Text style={styles.timelineLabelCancelled}>
Order Cancelled
</Text>
{trackingMap['CANCELLED'] && (
<Text style={styles.timelineTime}>
{formatDate(trackingMap['CANCELLED'])} at{' '}
{formatTime(trackingMap['CANCELLED'])}
</Text>
)}
</View>
</View>
</View>
)}
</View>
);
}
const activeStep = getStepIndex(currentStatus);
const steps = [
{ label: 'Placed', status: 'PENDING' },
{ label: 'Preparing', status: 'PREPARING' },
{ label: 'On the Way', status: 'OUT_FOR_DELIVERY' },
{ label: 'Delivered', status: 'DELIVERED' },
];
const showDriver = [
'PREPARING',
'READY_FOR_PICKUP',
'OUT_FOR_DELIVERY',
'DELIVERED',
].includes(currentStatus);
const currentIndex = STATUS_FLOW.indexOf(currentStatus);
return (
<View style={localStyles.cardContainer}>
{/* Current Status Header Banner */}
<View style={localStyles.headerSection}>
<View style={localStyles.headerIconContainer}>
<Text style={localStyles.headerIconText}>{details.icon}</Text>
</View>
<View style={localStyles.headerTextContainer}>
<Text style={localStyles.statusHeaderTitle}>{details.title}</Text>
<Text style={localStyles.statusHeaderDesc}>{details.desc}</Text>
</View>
</View>
{/* Horizontal Progress Stepper */}
<View style={localStyles.stepperWrapper}>
<View style={localStyles.stepperRow}>
<View style={localStyles.stepLineBackground} />
<View
style={[
localStyles.stepLineActive,
{ width: `${(activeStep / (steps.length - 1)) * 76}%` },
]}
/>
{steps.map((step, idx) => {
const isCompleted = idx < activeStep;
const isActive = idx === activeStep;
return (
<View key={step.label} style={localStyles.stepItem}>
<View
style={[
localStyles.stepDot,
isCompleted && localStyles.stepDotCompleted,
isActive && localStyles.stepDotActive,
]}
>
{isCompleted ? (
<Text style={localStyles.stepCheckmark}></Text>
) : isActive ? (
<View style={localStyles.stepInnerDotActive} />
) : null}
</View>
<Text
style={[
localStyles.stepLabel,
(isCompleted || isActive) && localStyles.stepLabelActive,
]}
>
{step.label}
</Text>
</View>
);
})}
</View>
</View>
{/* Delivery Partner Details (if driver assigned) */}
{showDriver && (
<View style={localStyles.driverSection}>
<View style={localStyles.driverInfoRow}>
<View style={localStyles.driverAvatarContainer}>
<Text style={localStyles.driverAvatarText}>RS</Text>
</View>
<View style={localStyles.driverMeta}>
<Text style={localStyles.driverRoleText}>Your Delivery Hero</Text>
<Text style={localStyles.driverNameText}>Rahul Sharma</Text>
<Text style={localStyles.driverVehicleText}>
KA-01-AB-1234 Honda Activa
</Text>
</View>
<View style={localStyles.ratingBadge}>
<Text style={localStyles.ratingText}> 4.8</Text>
</View>
</View>
<View style={localStyles.driverActionsRow}>
<TouchableOpacity
style={[localStyles.actionButton, localStyles.callButton]}
onPress={handleCall}
activeOpacity={0.8}
>
<Text style={localStyles.actionIcon}>📞</Text>
<Text style={localStyles.callButtonText}>Call Partner</Text>
</TouchableOpacity>
<TouchableOpacity
style={[localStyles.actionButton, localStyles.messageButton]}
onPress={handleMessage}
activeOpacity={0.8}
>
<Text style={localStyles.actionIcon}>💬</Text>
<Text style={localStyles.messageButtonText}>Chat</Text>
</TouchableOpacity>
</View>
</View>
)}
{/* View Detailed History Toggle */}
<View style={localStyles.toggleSection}>
<TouchableOpacity
onPress={toggleExpand}
style={localStyles.toggleButton}
activeOpacity={0.7}
>
<Text style={localStyles.toggleButtonText}>
{expanded ? 'Hide Details' : 'View Detailed History'}
</Text>
<Text style={localStyles.toggleChevron}>{expanded ? '▲' : '▼'}</Text>
</TouchableOpacity>
</View>
{/* Detailed Vertical Logs */}
{expanded && (
<View style={localStyles.detailedTimelineWrapper}>
{STATUS_FLOW.map((status, index) => {
const isCompleted = index <= currentIndex;
const isLast = index === STATUS_FLOW.length - 1;
const time = trackingMap[status];
return (
<View key={status} style={styles.timelineRow}>
<View style={styles.timelineDotColumn}>
<View
style={[
styles.timelineDot,
isCompleted && styles.timelineDotCompleted,
]}
>
{isCompleted && <Text style={styles.timelineCheck}></Text>}
</View>
{!isLast && (
<View
style={[
styles.timelineLine,
index < currentIndex && styles.timelineLineCompleted,
]}
/>
)}
</View>
<View style={styles.timelineContent}>
<Text
style={
isCompleted
? styles.timelineLabelCompleted
: styles.timelineLabelPending
}
>
{STATUS_LABEL[status]}
</Text>
{time && (
<Text style={styles.timelineTime}>
{formatDate(time)} at {formatTime(time)}
</Text>
)}
</View>
</View>
);
})}
</View>
)}
</View>
);
};