447 lines
15 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useEffect, useRef } from 'react';
import {
View,
Text,
ScrollView,
TouchableOpacity,
Alert,
Switch,
} from 'react-native';
import { useAppTheme, borderRadius, spacing, shadow, typography } from '@theme';
import { PrimaryButton } from '@components';
import { BellIcon, PersonIcon, WalletIcon, ClipboardIcon } from '@icons';
import { useAppDispatch, useAppSelector } from '@store';
import { setOnlineStatus } from '@store/commonReducers/auth';
import { setNewJobOffer } from '@store/commonReducers/job';
import { mockJobOffer } from '@store/mockData/mockJobs';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AppStackParamList } from '@navigation/navigationTypes';
import { RouteNames, JobStatus } from '@utils/constants';
import { formatCurrency } from '@utils/formatters';
import { getStyles } from './dashboardScreen.styles';
import { toggleStatus } from './thunk';
export const DashboardScreen: React.FC = () => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
const dispatch = useAppDispatch();
const navigation =
useNavigation<NativeStackNavigationProp<AppStackParamList>>();
// Derive online status from API response stored in dashboard slice.
// Falls back to auth.isOnline during the initial load before any toggle API call.
const { availabiltyStatus, loading: toggleLoading } = useAppSelector(
state => state.dashboard,
);
const authIsOnline = useAppSelector(state => state.auth.isOnline);
const isOnline = availabiltyStatus
? availabiltyStatus.availabilityStatus === 'ONLINE'
: authIsOnline;
const { todayEarnings, completedCount, onlineMinutes, weeklyData } =
useAppSelector(state => state.earnings);
const { jobStatus, activeJob } = useAppSelector(state => state.job);
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
// Background simulation: when online and idle, trigger new order offer after 12 seconds
useEffect(() => {
if (isOnline && jobStatus === JobStatus.Idle) {
timerRef.current = setTimeout(() => {
dispatch(setNewJobOffer(mockJobOffer));
navigation.navigate(RouteNames.NewJobRequest);
}, 12000);
} else {
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = null;
}
}
return () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
};
}, [isOnline, jobStatus, dispatch, navigation]);
const handleToggleOnline = () => {
const newStatus = isOnline ? 'OFFLINE' : 'ONLINE';
// Optimistically update the auth flag so the timer and UI react immediately
dispatch(setOnlineStatus(!isOnline));
dispatch(toggleStatus({ availabilityStatus: newStatus }))
.unwrap()
.then(response => {
// Confirm state from server — setOnlineStatus stays in sync
const isNowOnline = response.availabilityStatus === 'ONLINE';
dispatch(setOnlineStatus(isNowOnline));
})
.catch(() => {
// Revert the optimistic update on API failure
dispatch(setOnlineStatus(isOnline));
Alert.alert(
'Status Update Failed',
'Could not update your availability. Please try again.',
);
});
};
const handleSimulateOffer = () => {
if (!isOnline) {
Alert.alert('Go Online', 'Please go online to receive order offers.');
return;
}
if (jobStatus !== JobStatus.Idle) {
Alert.alert(
'Active Delivery',
'You already have an active job offer or order.',
);
return;
}
dispatch(setNewJobOffer(mockJobOffer));
navigation.navigate(RouteNames.NewJobRequest);
};
const handleActiveJobBannerPress = () => {
// Navigate back to the screen corresponding to the current job step
switch (jobStatus) {
case JobStatus.NewRequest:
navigation.navigate(RouteNames.NewJobRequest);
break;
case JobStatus.Accepted:
navigation.navigate(RouteNames.OrderAccepted);
break;
case JobStatus.ArrivedAtStore:
navigation.navigate(RouteNames.ConfirmPickup);
break;
case JobStatus.PickedUp:
navigation.navigate(RouteNames.OrderPickedUp);
break;
case JobStatus.EnRoute:
navigation.navigate(RouteNames.LiveTracking);
break;
case JobStatus.ArrivedAtCustomer:
navigation.navigate(RouteNames.DeliverOrder);
break;
default:
Alert.alert('Status Error', 'Unknown active delivery state.');
}
};
const formatOnlineTime = (mins: number) => {
const hours = Math.floor(mins / 60);
const remainingMins = mins % 60;
return `${hours}h ${
remainingMins < 10 ? `0${remainingMins}` : remainingMins
}m`;
};
// Helper to render chart bar heights dynamically based on amounts
const maxWeeklyAmount = Math.max(...weeklyData.map(d => d.amount), 1);
return (
<View style={styles.container}>
<ScrollView
contentContainerStyle={styles.scrollContainer}
showsVerticalScrollIndicator={false}
>
{/* Header Section */}
<View style={styles.header}>
<View>
<Text style={styles.greetingText}>Good Morning </Text>
<Text style={styles.nameText}>Rahul Kumar</Text>
</View>
<View style={styles.toggleWrapper}>
<View
style={[
styles.statusDot,
{
backgroundColor: isOnline
? colors.statusOnline
: colors.statusOffline,
},
]}
/>
<Text
style={[
styles.statusText,
{
color: isOnline ? colors.statusOnline : colors.statusOffline,
},
]}
>
{isOnline ? 'Online' : 'Offline'}
</Text>
<Switch
value={isOnline}
onValueChange={handleToggleOnline}
trackColor={{ false: colors.border, true: colors.primaryLight }}
thumbColor={isOnline ? colors.primary : colors.placeholder}
disabled={toggleLoading}
style={{ marginLeft: 8, opacity: toggleLoading ? 0.5 : 1 }}
/>
</View>
</View>
{/* Offline Warning Banner */}
{!isOnline && (
<View style={styles.offlineBanner}>
<Text style={styles.offlineBannerText}>
You are offline. Go online to start receiving orders.
</Text>
</View>
)}
{/* Today's Summary Card */}
<View style={styles.summaryCard}>
<Text style={styles.summaryTitle}>Today's Summary</Text>
<View style={styles.statsRow}>
<View style={styles.statBox}>
<Text style={styles.statVal}>
{formatCurrency(isOnline ? todayEarnings : 0)}
</Text>
<Text style={styles.statLabel}>Earnings</Text>
</View>
<View style={styles.statDivider} />
<View style={styles.statBox}>
<Text style={styles.statVal}>
{isOnline ? completedCount : 0}
</Text>
<Text style={styles.statLabel}>Completed</Text>
</View>
<View style={styles.statDivider} />
<View style={styles.statBox}>
<Text style={styles.statVal}>
{formatOnlineTime(isOnline ? onlineMinutes : 0)}
</Text>
<Text style={styles.statLabel}>Online Time</Text>
</View>
</View>
</View>
{/* Conditional Layout for Online vs Offline */}
{isOnline ? (
<>
{/* Performance Stats */}
<View style={styles.performanceCard}>
<Text style={styles.perfTitle}>Your Performance</Text>
<View style={styles.perfRow}>
<View style={styles.perfBox}>
<Text style={[styles.perfVal, { color: colors.warning }]}>
4.8 ★
</Text>
<Text style={styles.perfLabel}>Rating</Text>
</View>
<View style={styles.perfBox}>
<Text style={styles.perfVal}>95%</Text>
<Text style={styles.perfLabel}>Acceptance</Text>
</View>
<View style={styles.perfBox}>
<Text style={styles.perfVal}>98%</Text>
<Text style={styles.perfLabel}>Completion</Text>
</View>
</View>
</View>
{/* Weekly Earnings Chart (Styled SVG/Flex columns) */}
<View style={styles.chartCard}>
<Text style={styles.chartTitle}>Weekly Overview</Text>
<View style={styles.chartRow}>
{weeklyData.map((data, index) => {
// Bar height percent
const heightPercent = `${
(data.amount / maxWeeklyAmount) * 85
}%`;
return (
<View key={index} style={styles.barContainer}>
<Text style={styles.barVal}>₹{data.amount}</Text>
<View
style={[
styles.bar,
{
height: heightPercent as any,
backgroundColor:
index === weeklyData.length - 1
? colors.primary
: colors.placeholder,
},
]}
/>
<Text style={styles.barLabel}>{data.day}</Text>
</View>
);
})}
</View>
</View>
{/* Developer Simulation trigger */}
<TouchableOpacity
style={styles.simulateBtn}
activeOpacity={0.8}
onPress={handleSimulateOffer}
>
<Text style={styles.simulateBtnText}>
⚡ Simulate Immediate Job Offer
</Text>
</TouchableOpacity>
<PrimaryButton
title="Go Offline"
onPress={handleToggleOnline}
style={[styles.offlineBtn, { backgroundColor: colors.error }]}
/>
</>
) : (
<View style={styles.promptCard}>
<Text style={styles.promptTitle}>
Go Online to start receiving delivery requests
</Text>
<Text style={styles.promptDesc}>
Make sure you are in a high-demand delivery zone to maximize your
earnings.
</Text>
<PrimaryButton
title="Go Online"
onPress={handleToggleOnline}
style={styles.onlineBtn}
/>
</View>
)}
{/* Help & Support Cards at bottom */}
<View style={styles.helpSection}>
<Text style={styles.helpTitle}>Help & Support</Text>
<View style={styles.helpRow}>
<TouchableOpacity
style={styles.helpItem}
onPress={() =>
Alert.alert(
'Emergency SOS',
'Calling nearest security/emergency dispatch...',
)
}
>
<BellIcon size={24} color={colors.error} />
<Text style={styles.helpLabel}>Emergency SOS</Text>
<Text style={styles.helpSub}>Immediate Assistance</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.helpItem}
onPress={() =>
navigation.navigate(RouteNames.BottomTabs, {
screen: RouteNames.Profile,
})
}
>
<ClipboardIcon size={24} color={colors.primary} />
<Text style={styles.helpLabel}>FAQ Center</Text>
<Text style={styles.helpSub}>Help & Guides</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.helpItem}
onPress={() =>
Alert.alert(
'Support Chat',
'Initiating live helpdesk agent chat...',
)
}
>
<PersonIcon size={24} color={colors.statusOnDelivery} />
<Text style={styles.helpLabel}>Live Support</Text>
<Text style={styles.helpSub}>24/7 Chat Help</Text>
</TouchableOpacity>
</View>
</View>
</ScrollView>
{/* Active Job Floating Bottom Banner */}
{jobStatus !== JobStatus.Idle &&
jobStatus !== JobStatus.NewRequest &&
activeJob && (
<TouchableOpacity
style={{
position: 'absolute',
bottom: spacing.xs,
left: spacing.md,
right: spacing.md,
backgroundColor: colors.primary,
borderRadius: borderRadius.md,
paddingVertical: spacing.md,
paddingHorizontal: spacing.lg,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
...shadow.lg,
}}
activeOpacity={0.9}
onPress={handleActiveJobBannerPress}
>
<View
style={{ flexDirection: 'row', alignItems: 'center', flex: 1 }}
>
<ClipboardIcon size={20} color={colors.white} />
<View style={{ marginLeft: spacing.sm, flex: 1 }}>
<Text
style={{
color: colors.white,
fontWeight: 'bold',
fontSize: typography.fontSize.sm,
}}
>
Order {activeJob.orderId}
</Text>
<Text
style={{
color: colors.primaryLight,
fontSize: typography.fontSize.xs,
}}
numberOfLines={1}
>
{jobStatus === JobStatus.Accepted &&
'Heading to pickup store...'}
{jobStatus === JobStatus.ArrivedAtStore &&
'At pickup store, check items...'}
{jobStatus === JobStatus.PickedUp &&
'Order picked up, routing to customer...'}
{jobStatus === JobStatus.EnRoute &&
'On the way to customer...'}
{jobStatus === JobStatus.ArrivedAtCustomer &&
'Arrived at customer location...'}
</Text>
</View>
</View>
<View
style={{
backgroundColor: colors.white,
paddingHorizontal: spacing.md,
paddingVertical: 6,
borderRadius: borderRadius.sm,
}}
>
<Text
style={{
color: colors.primary,
fontWeight: 'bold',
fontSize: typography.fontSize.xs,
}}
>
View
</Text>
</View>
</TouchableOpacity>
)}
</View>
);
};
// export default DashboardScreen;