Files
CBS-Mobile-App/src/modules/cards/components/CardDeliveryStatus.tsx
T

284 lines
7.9 KiB
TypeScript

import React, { memo, useMemo } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Ionicons from '@react-native-vector-icons/ionicons';
import { useTheme } from '../../../theme';
import type { CardPhysicalDelivery } from '../../../types/app';
import { formatDate } from '../../../utils/formatters';
import {
getCardDeliveryProgressIndex,
getCardDeliveryStatusLabel,
} from '../utils/cardCopy';
interface CardDeliveryStatusProps {
delivery: CardPhysicalDelivery;
compact?: boolean;
}
type DeliveryStep = {
id: string;
label: string;
};
const CardDeliveryStatusComponent = ({
delivery,
compact = false,
}: CardDeliveryStatusProps) => {
const theme = useTheme();
const statusLabel = getCardDeliveryStatusLabel(delivery);
const progressIndex = getCardDeliveryProgressIndex(delivery);
const steps = useMemo<DeliveryStep[]>(
() =>
delivery.mode === 'home'
? [
{ id: 'processing', label: 'Requested' },
{ id: 'personalizing', label: 'Prepared' },
{ id: 'shipped', label: 'In transit' },
{ id: 'delivered', label: 'Delivered' },
]
: [
{ id: 'processing', label: 'Requested' },
{ id: 'personalizing', label: 'Prepared' },
{ id: 'ready-for-pickup', label: 'At branch' },
{ id: 'delivered', label: 'Collected' },
],
[delivery.mode],
);
const accentColor =
delivery.status === 'delivered'
? theme.colors.accentStrong
: delivery.status === 'ready-for-pickup'
? theme.colors.warning
: theme.colors.primary;
const accentBackground =
delivery.status === 'delivered'
? `${theme.colors.accent}12`
: delivery.status === 'ready-for-pickup'
? `${theme.colors.warning}14`
: `${theme.colors.primary}12`;
const modeLabel = delivery.mode === 'home' ? 'Home delivery' : 'Branch pickup';
return (
<View style={styles.container}>
<View style={styles.header}>
<View style={styles.headerCopy}>
<Text style={[styles.eyebrow, { color: theme.colors.textSecondary }]}>
Physical card delivery
</Text>
<Text style={[styles.title, { color: theme.colors.textPrimary }]}>{statusLabel}</Text>
<Text style={[styles.subtitle, { color: theme.colors.textSecondary }]}>
{delivery.status === 'delivered'
? `Completed on ${formatDate(delivery.lastUpdatedAt)}`
: `Expected by ${formatDate(delivery.estimatedDate)}`}
</Text>
</View>
<View style={[styles.badge, { backgroundColor: accentBackground }]}>
<Ionicons name="cube-outline" size={14} color={accentColor} />
<Text style={[styles.badgeText, { color: accentColor }]}>{modeLabel}</Text>
</View>
</View>
<View style={styles.metricsRow}>
<View style={[styles.metricTile, { backgroundColor: theme.colors.surfaceMuted }]}>
<Text style={[styles.metricLabel, { color: theme.colors.textSecondary }]}>
Tracking ID
</Text>
<Text style={[styles.metricValue, { color: theme.colors.textPrimary }]}>
{delivery.trackingId}
</Text>
</View>
<View style={[styles.metricTile, { backgroundColor: theme.colors.surfaceMuted }]}>
<Text style={[styles.metricLabel, { color: theme.colors.textSecondary }]}>
Destination
</Text>
<Text
numberOfLines={compact ? 1 : 2}
style={[styles.metricValue, { color: theme.colors.textPrimary }]}
>
{delivery.destinationLabel}
</Text>
</View>
</View>
<View style={styles.timelineRow}>
{steps.map((step, index) => {
const isCompleted = index < progressIndex;
const isCurrent = index === progressIndex;
const activeColor = isCompleted || isCurrent ? accentColor : theme.colors.cardBorder;
const timelineDotStyle = {
backgroundColor: isCompleted || isCurrent ? activeColor : 'transparent',
borderColor: activeColor,
};
const timelineLineStyle = {
backgroundColor: index < progressIndex ? accentColor : theme.colors.cardBorder,
};
return (
<View key={step.id} style={styles.timelineItem}>
<View style={styles.timelineTopRow}>
<View style={[styles.timelineDot, timelineDotStyle]}>
{isCompleted ? (
<Ionicons name="checkmark" size={10} color={theme.colors.white} />
) : null}
</View>
{index !== steps.length - 1 ? (
<View style={[styles.timelineLine, timelineLineStyle]} />
) : null}
</View>
<Text
style={[
styles.timelineLabel,
{
color:
isCompleted || isCurrent
? theme.colors.textPrimary
: theme.colors.textMuted,
},
]}
>
{step.label}
</Text>
</View>
);
})}
</View>
{!compact ? (
<View style={[styles.footerCard, { backgroundColor: theme.colors.surfaceMuted }]}>
<View style={styles.footerRow}>
<Text style={[styles.footerLabel, { color: theme.colors.textSecondary }]}>
Requested
</Text>
<Text style={[styles.footerValue, { color: theme.colors.textPrimary }]}>
{formatDate(delivery.requestedAt)}
</Text>
</View>
<View style={styles.footerRow}>
<Text style={[styles.footerLabel, { color: theme.colors.textSecondary }]}>
Last updated
</Text>
<Text style={[styles.footerValue, { color: theme.colors.textPrimary }]}>
{formatDate(delivery.lastUpdatedAt)}
</Text>
</View>
</View>
) : null}
</View>
);
};
const styles = StyleSheet.create({
container: {
gap: 14,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
gap: 12,
},
headerCopy: {
flex: 1,
gap: 4,
},
eyebrow: {
fontSize: 11,
fontWeight: '700',
textTransform: 'uppercase',
letterSpacing: 0.8,
},
title: {
fontSize: 17,
fontWeight: '800',
letterSpacing: -0.2,
},
subtitle: {
fontSize: 13,
lineHeight: 18,
},
badge: {
flexDirection: 'row',
alignItems: 'center',
gap: 6,
borderRadius: 999,
paddingHorizontal: 10,
paddingVertical: 8,
},
badgeText: {
fontSize: 12,
fontWeight: '700',
},
metricsRow: {
flexDirection: 'row',
gap: 12,
},
metricTile: {
flex: 1,
borderRadius: 18,
padding: 14,
gap: 6,
},
metricLabel: {
fontSize: 11,
fontWeight: '700',
textTransform: 'uppercase',
letterSpacing: 0.7,
},
metricValue: {
fontSize: 14,
fontWeight: '800',
},
timelineRow: {
flexDirection: 'row',
alignItems: 'flex-start',
},
timelineItem: {
flex: 1,
gap: 8,
},
timelineTopRow: {
flexDirection: 'row',
alignItems: 'center',
},
timelineDot: {
width: 20,
height: 20,
borderRadius: 10,
borderWidth: 2,
alignItems: 'center',
justifyContent: 'center',
},
timelineLine: {
flex: 1,
height: 2,
marginHorizontal: 6,
},
timelineLabel: {
fontSize: 11,
fontWeight: '700',
lineHeight: 15,
},
footerCard: {
borderRadius: 18,
padding: 14,
gap: 10,
},
footerRow: {
flexDirection: 'row',
justifyContent: 'space-between',
gap: 12,
},
footerLabel: {
fontSize: 12,
fontWeight: '600',
},
footerValue: {
fontSize: 12,
fontWeight: '800',
},
});
export const CardDeliveryStatus = memo(CardDeliveryStatusComponent);