diff --git a/src/app/(delivery-tabs)/_layout.tsx b/src/app/(delivery-tabs)/_layout.tsx
index 9e1243f..14b1f43 100644
--- a/src/app/(delivery-tabs)/_layout.tsx
+++ b/src/app/(delivery-tabs)/_layout.tsx
@@ -3,25 +3,17 @@ import { Tabs } from 'expo-router';
import { View, StyleSheet } from 'react-native';
import { Colors } from '../../styles/theme';
import PoppinsText from '../../components/PoppinsText';
-import {
- HomeIcon,
- ClockIcon,
- LifebuoyIcon,
-} from 'react-native-heroicons/outline';
+import { HomeIcon, ClockIcon } from 'react-native-heroicons/outline';
import TopBarDelivery from '../../components/TopBarDelivery';
import { AlertProvider } from '../../components/AlertProvider'; // Importar el AlertProvider
+import { TabBar } from '../../components/TabBar';
export default function DeliveryTabLayout() {
return (
-
+ }>
- (
-
- Soporte
-
- ),
- headerShown: false,
- title: 'Soporte',
- tabBarIcon: () => (
-
- ),
- }}
- />
diff --git a/src/app/(delivery-tabs)/deliverySupport.tsx b/src/app/(delivery-tabs)/deliverySupport.tsx
deleted file mode 100644
index fae268e..0000000
--- a/src/app/(delivery-tabs)/deliverySupport.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import React from 'react';
-import DeliverySupportScreen from '../../screens/delivery/DeliverySupportScreen';
-
-export default function DeliverySupport() {
- return ;
-}
diff --git a/src/app/(tabs)/_layout.tsx b/src/app/(tabs)/_layout.tsx
index ae1c79b..27b9f64 100644
--- a/src/app/(tabs)/_layout.tsx
+++ b/src/app/(tabs)/_layout.tsx
@@ -3,22 +3,21 @@ import {
ListBulletIcon,
MapPinIcon,
TagIcon,
- LifebuoyIcon,
} from 'react-native-heroicons/outline';
import { Tabs } from 'expo-router';
import { Colors } from '../../styles/theme';
import PoppinsText from '../../components/PoppinsText';
import { View, StyleSheet } from 'react-native';
import TopBar from '../../components/TopBar';
+import { TabBar } from '../../components/TabBar';
export default function TabLayout() {
return (
} // Siempre renderiza la TabBar
screenOptions={{
- tabBarActiveTintColor: Colors.primary,
- tabBarStyle: { backgroundColor: Colors.secondaryWhite },
tabBarHideOnKeyboard: true,
tabBarVisibilityAnimationConfig: {
show: {
@@ -93,21 +92,6 @@ export default function TabLayout() {
),
}}
/>
- (
-
- Soporte
-
- ),
- title: 'Soporte',
- headerShown: false,
- tabBarIcon: () => (
-
- ),
- }}
- />
);
diff --git a/src/app/(tabs)/support.tsx b/src/app/(tabs)/support.tsx
deleted file mode 100644
index 600e4de..0000000
--- a/src/app/(tabs)/support.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-// app/(tabs)/support.tsx
-import React from 'react';
-import SupportScreen from '../../screens/tab/SupportScreen';
-
-export default function TabsHome() {
- return ;
-}
diff --git a/src/app/_layout.tsx b/src/app/_layout.tsx
index 7198700..0440b97 100644
--- a/src/app/_layout.tsx
+++ b/src/app/_layout.tsx
@@ -55,6 +55,10 @@ export default function RootLayout() {
({
headerBackVisible: false,
+ headerStyle: {
+ backgroundColor: Colors.bgColor, // Color específico para esta pantalla
+ },
+ headerShadowVisible: false,
headerLeft: () =>
navigation.canGoBack() ? (
-
+
-
+
-
-
-
-
+
+
+
+
({
headerTitle: '',
- headerTransparent: true,
headerLeft: undefined,
headerRight: () => (
navigation.goBack()}>
@@ -149,10 +137,7 @@ export default function RootLayout() {
),
})}
/>
-
+
diff --git a/src/components/CardButton.tsx b/src/components/CardButton.tsx
index ea62541..1ab08d0 100644
--- a/src/components/CardButton.tsx
+++ b/src/components/CardButton.tsx
@@ -9,6 +9,8 @@ interface CardButtonProps {
initialValue: number;
size?: number;
syncQuantity?: (count: number) => void;
+ disabled?: boolean;
+ showNoStockAlert?: () => void; // <-- nuevo prop opcional
}
const CardButton: React.FC = ({
@@ -16,6 +18,8 @@ const CardButton: React.FC = ({
initialValue = 0,
size = 4,
syncQuantity,
+ disabled = false, // <-- Valor por defecto
+ showNoStockAlert, // <-- Add this line
}) => {
const [count, setCount] = useState(initialValue);
const [showCounter, setShowCounter] = useState(false);
@@ -38,11 +42,19 @@ const CardButton: React.FC = ({
}, [initialValue]);
const incrementCount = () => {
+ if (disabled) {
+ if (showNoStockAlert) showNoStockAlert();
+ return;
+ }
setHasInteracted(true);
setCount((prev) => prev + 1);
};
const decrementCount = () => {
+ if (disabled) {
+ if (showNoStockAlert) showNoStockAlert();
+ return;
+ }
if (count > 0) {
setHasInteracted(true);
setCount((prev) => prev - 1);
@@ -50,6 +62,7 @@ const CardButton: React.FC = ({
};
const showCounterIncrement = () => {
+ if (disabled) return; // <-- No permitir si está deshabilitado
setShowCounter(true);
incrementCount();
};
@@ -58,18 +71,34 @@ const CardButton: React.FC = ({
{!showCounter ? (
{
+ if (disabled && showNoStockAlert) showNoStockAlert();
+ else showCounterIncrement();
+ }}
>
) : (
-
+
{
+ if (disabled && showNoStockAlert) showNoStockAlert();
+ else decrementCount();
+ }}
>
@@ -80,7 +109,10 @@ const CardButton: React.FC = ({
{
+ if (disabled && showNoStockAlert) showNoStockAlert();
+ else incrementCount();
+ }}
>
diff --git a/src/components/DeliveryMap.tsx b/src/components/DeliveryMap.tsx
index c987c23..5ba7b4d 100644
--- a/src/components/DeliveryMap.tsx
+++ b/src/components/DeliveryMap.tsx
@@ -1,73 +1,31 @@
import React, { useEffect, useState } from 'react';
import MapView, { Marker, Polyline } from 'react-native-maps';
-import { StyleSheet, View, Alert } from 'react-native';
-import * as Location from 'expo-location';
+import { StyleSheet, View, ActivityIndicator, ViewStyle } from 'react-native';
import { Config } from '../config';
+import { Colors } from '../styles/theme';
interface DeliveryMapProps {
deliveryState: number;
branchLocation: { latitude: number; longitude: number };
customerLocation: { latitude: number; longitude: number };
+ deliveryLocation: { latitude: number; longitude: number } | null; // NUEVO
+ style?: ViewStyle;
}
const DeliveryMap: React.FC = ({
deliveryState,
branchLocation,
customerLocation,
+ deliveryLocation,
+ style,
}) => {
- const [deliveryLocation, setDeliveryLocation] = useState<{
- latitude: number;
- longitude: number;
- } | null>(null);
const [routeCoordinates, setRouteCoordinates] = useState<
{ latitude: number; longitude: number }[]
>([]);
const [customerRouteCoordinates, setCustomerRouteCoordinates] = useState<
{ latitude: number; longitude: number }[]
>([]);
-
- // Solicitar permisos y obtener la ubicación del delivery
- useEffect(() => {
- const getDeliveryLocation = async () => {
- try {
- const { status } = await Location.getForegroundPermissionsAsync();
- if (status !== 'granted') {
- const { status: newStatus } =
- await Location.requestForegroundPermissionsAsync();
- if (newStatus !== 'granted') {
- Alert.alert(
- 'Permiso denegado',
- 'No se pudo obtener la ubicación del delivery.',
- );
- return;
- }
- }
-
- const location = await Location.getCurrentPositionAsync({});
- setDeliveryLocation({
- latitude: location.coords.latitude,
- longitude: location.coords.longitude,
- });
-
- // Obtener la ruta desde la ubicación del delivery hasta la sucursal
- fetchRoute(
- location.coords.latitude,
- location.coords.longitude,
- branchLocation.latitude,
- branchLocation.longitude,
- setRouteCoordinates,
- );
- } catch (error) {
- console.error('Error al obtener la ubicación del delivery:', error);
- Alert.alert(
- 'Error',
- 'Hubo un problema al obtener la ubicación del delivery.',
- );
- }
- };
-
- getDeliveryLocation();
- }, []);
+ const [isLoading, setIsLoading] = useState(true); // Estado de carga
// Obtener la ruta desde Google Maps Directions API
const fetchRoute = async (
@@ -96,11 +54,30 @@ const DeliveryMap: React.FC = ({
}
};
+ // Obtener la ruta hacia la sucursal
+ useEffect(() => {
+ if (
+ deliveryLocation &&
+ branchLocation.latitude !== 0 &&
+ branchLocation.longitude !== 0
+ ) {
+ fetchRoute(
+ deliveryLocation.latitude,
+ deliveryLocation.longitude,
+ branchLocation.latitude,
+ branchLocation.longitude,
+ setRouteCoordinates,
+ );
+ }
+ }, [deliveryLocation, branchLocation]);
+
// Obtener la ruta hacia el cliente cuando el estado sea el correspondiente
useEffect(() => {
if (
- deliveryState >= 3 && // Estado "Ir a destino de entrega" o posterior
- deliveryLocation
+ deliveryState >= 3 &&
+ deliveryLocation &&
+ customerLocation.latitude !== 0 &&
+ customerLocation.longitude !== 0
) {
fetchRoute(
deliveryLocation.latitude,
@@ -113,7 +90,20 @@ const DeliveryMap: React.FC = ({
// Limpiar la ruta hacia la sucursal
setRouteCoordinates([]);
}
- }, [deliveryState, deliveryLocation]);
+ }, [deliveryState, deliveryLocation, customerLocation]);
+
+ // Verificar si todos los datos necesarios están disponibles
+ useEffect(() => {
+ if (
+ deliveryLocation &&
+ branchLocation.latitude !== 0 &&
+ branchLocation.longitude !== 0 &&
+ customerLocation.latitude !== 0 &&
+ customerLocation.longitude !== 0
+ ) {
+ setIsLoading(false); // Finalizar la carga cuando todos los datos estén disponibles
+ }
+ }, [deliveryLocation, branchLocation, customerLocation]);
// Decodificar la polyline de Google Maps
const decodePolyline = (encoded: string) => {
@@ -154,13 +144,22 @@ const DeliveryMap: React.FC = ({
return points;
};
+ if (isLoading) {
+ // Mostrar un indicador de carga mientras se obtienen los datos
+ return (
+
+
+
+ );
+ }
+
return (
-
+
= ({
)}
{/* Mostrar la ubicación de la sucursal */}
-
+ {branchLocation.latitude !== 0 && branchLocation.longitude !== 0 && (
+
+ )}
{/* Mostrar la ubicación del cliente */}
-
+ {customerLocation.latitude !== 0 &&
+ customerLocation.longitude !== 0 && (
+
+ )}
{/* Mostrar la ruta hacia la sucursal */}
{routeCoordinates.length > 0 && (
@@ -197,7 +201,7 @@ const DeliveryMap: React.FC = ({
/>
)}
- {/* Mostrar la ruta hacia el cliente solo si el estado es el correspondiente */}
+ {/* Mostrar la ruta hacia el cliente */}
{deliveryState >= 3 && customerRouteCoordinates.length > 0 && (
= ({
const styles = StyleSheet.create({
container: {
- height: 300,
- borderRadius: 16,
- overflow: 'hidden',
- marginBottom: 16,
+ flex: 1, // Permitir que el mapa ocupe todo el espacio disponible
},
- map: {
- flex: 1,
+ loadingContainer: {
+ flex: 1, // Asegura que el indicador de carga ocupe todo el espacio disponible
+ justifyContent: 'center',
+ alignItems: 'center',
},
});
diff --git a/src/components/HistoryMap.tsx b/src/components/HistoryMap.tsx
index 7a0b0d7..ba62402 100644
--- a/src/components/HistoryMap.tsx
+++ b/src/components/HistoryMap.tsx
@@ -1,18 +1,20 @@
import React, { useEffect, useState } from 'react';
import MapView, { Marker, Polyline } from 'react-native-maps';
-import { StyleSheet, View, Alert } from 'react-native';
+import { StyleSheet, View, Alert, ViewStyle } from 'react-native';
import { Config } from '../config';
interface HistoryMapProps {
deliveryLocation: { latitude: number; longitude: number };
branchLocation: { latitude: number; longitude: number };
customerLocation: { latitude: number; longitude: number };
+ style?: ViewStyle; // Permitir estilos personalizados
}
const HistoryMap: React.FC = ({
deliveryLocation,
branchLocation,
customerLocation,
+ style, // Recibir el estilo como prop
}) => {
const [routeToBranch, setRouteToBranch] = useState<
{ latitude: number; longitude: number }[]
@@ -118,9 +120,9 @@ const HistoryMap: React.FC = ({
};
return (
-
+
= ({
const styles = StyleSheet.create({
container: {
- height: 300,
- borderRadius: 16,
- overflow: 'hidden',
- marginBottom: 16,
- },
- map: {
- flex: 1,
+ flex: 1, // Permitir que el mapa ocupe todo el espacio disponible
},
});
diff --git a/src/components/HistoryOrderCard.tsx b/src/components/HistoryOrderCard.tsx
index e52e37e..e70d09c 100644
--- a/src/components/HistoryOrderCard.tsx
+++ b/src/components/HistoryOrderCard.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import { View, StyleSheet } from 'react-native';
import {
MapPinIcon,
- ClockIcon,
+ CalendarDaysIcon,
BuildingStorefrontIcon,
} from 'react-native-heroicons/outline';
import { CheckCircleIcon } from 'react-native-heroicons/solid';
@@ -84,9 +84,9 @@ const HistoryOrderCard: React.FC = ({
-
+
- Tiempo de entrega: {estimatedTime}
+ Fecha de entrega: {estimatedTime}
@@ -151,6 +151,9 @@ const styles = StyleSheet.create({
fontSize: FontSizes.b3.size,
color: Colors.textMain,
marginLeft: 8,
+ flexWrap: 'wrap', // Permite que el texto se ajuste en varias líneas
+ maxWidth: '90%', // Limita el ancho del texto al 90% del contenedor
+ textAlign: 'left',
},
button: {
marginBottom: 8,
diff --git a/src/components/OrderCard.tsx b/src/components/OrderCard.tsx
index ca4451b..67d196c 100644
--- a/src/components/OrderCard.tsx
+++ b/src/components/OrderCard.tsx
@@ -59,7 +59,7 @@ const OrderCard: React.FC = ({
{/* Details */}
-
+
{address}
@@ -73,7 +73,7 @@ const OrderCard: React.FC = ({
- Hora de entrega: {estimatedTime}
+ Tiempo estimado: {estimatedTime}
@@ -135,18 +135,18 @@ const styles = StyleSheet.create({
color: Colors.primary,
marginBottom: 8,
},
- details: {
- marginBottom: 4,
- },
detailRow: {
flexDirection: 'row',
alignItems: 'center',
- marginBottom: 8,
+ marginBottom: 10,
},
detailText: {
fontSize: FontSizes.b3.size,
color: Colors.textMain,
marginLeft: 8,
+ flexWrap: 'wrap',
+ maxWidth: '90%',
+ textAlign: 'left',
},
button: {
marginBottom: 8,
diff --git a/src/components/TabBar.tsx b/src/components/TabBar.tsx
new file mode 100644
index 0000000..c34528f
--- /dev/null
+++ b/src/components/TabBar.tsx
@@ -0,0 +1,140 @@
+import { View, StyleSheet, LayoutChangeEvent } from 'react-native';
+import { BottomTabBarProps } from '@react-navigation/bottom-tabs';
+import { Colors } from '../styles/theme';
+import { useState } from 'react';
+import TabBarButton from './TabBarButton';
+import Animated, {
+ useAnimatedStyle,
+ useSharedValue,
+ withTiming,
+} from 'react-native-reanimated';
+
+export function TabBar({ state, descriptors, navigation }: BottomTabBarProps) {
+ const currentRoute = state.routes[state.index].name;
+
+ // Oculta la TabBar en pantallas específicas
+ const hiddenRoutes = ['menu', 'cart']; // Agrega aquí las rutas donde no quieres mostrar la TabBar
+ if (hiddenRoutes.includes(currentRoute)) {
+ return null; // No renderiza la TabBar
+ }
+
+ const [dimensions, setDimensions] = useState({ width: 10, height: 20 });
+
+ const buttonWidth = dimensions.width / state.routes.length;
+
+ const onTabBarLayout = (e: LayoutChangeEvent) => {
+ setDimensions({
+ width: e.nativeEvent.layout.width,
+ height: e.nativeEvent.layout.height,
+ });
+ };
+
+ const tabPositionX = useSharedValue(0);
+
+ const animatedStyle = useAnimatedStyle(() => ({
+ transform: [
+ {
+ translateX: tabPositionX.value + 16,
+ },
+ ],
+ }));
+
+ return (
+
+
+ {state.routes.map((route, index) => {
+ const { options } = descriptors[route.key];
+ const label =
+ options.tabBarLabel !== undefined
+ ? options.tabBarLabel
+ : options.title !== undefined
+ ? options.title
+ : route.name;
+
+ const isFocused = state.index === index;
+
+ const onPress = () => {
+ tabPositionX.value = withTiming(buttonWidth * index, {
+ duration: 350,
+ });
+
+ const event = navigation.emit({
+ type: 'tabPress',
+ target: route.key,
+ canPreventDefault: true,
+ });
+
+ if (!isFocused && !event.defaultPrevented) {
+ navigation.navigate(route.name, route.params);
+ }
+ };
+
+ const onLongPress = () => {
+ navigation.emit({
+ type: 'tabLongPress',
+ target: route.key,
+ });
+ };
+
+ return (
+
+ label({
+ ...props,
+ position: props.position as 'beside-icon' | 'below-icon',
+ })
+ : label
+ }
+ />
+ );
+ })}
+
+ );
+}
+
+const styles = StyleSheet.create({
+ tabBar: {
+ position: 'absolute',
+ flexDirection: 'row',
+ bottom: 0,
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ borderColor: Colors.gray_100,
+ borderTopWidth: 1,
+ backgroundColor: Colors.menuWhite,
+ paddingVertical: 6,
+ shadowColor: '#000',
+ shadowOffset: {
+ width: 0,
+ height: 10,
+ },
+ shadowOpacity: 0.1,
+ shadowRadius: 10,
+ },
+ // tabBarItem: {
+ // flex: 1,
+ // justifyContent: 'center',
+ // alignItems: 'center',
+ // },
+});
diff --git a/src/components/TabBarButton.tsx b/src/components/TabBarButton.tsx
new file mode 100644
index 0000000..d11c631
--- /dev/null
+++ b/src/components/TabBarButton.tsx
@@ -0,0 +1,94 @@
+import { Pressable, StyleSheet, GestureResponderEvent } from 'react-native';
+import Animated from 'react-native-reanimated';
+import React, { useEffect } from 'react';
+import { icon } from '../constants/icon';
+import {
+ interpolate,
+ useAnimatedStyle,
+ useSharedValue,
+ withSpring,
+} from 'react-native-reanimated';
+import { Colors } from '../styles/theme';
+
+const TabBarButton = ({
+ onPress,
+ onLongPress,
+ isFocused,
+ routeName,
+ label,
+}: {
+ onPress: (event: GestureResponderEvent) => void;
+ onLongPress: (event: GestureResponderEvent) => void;
+ isFocused: boolean;
+ routeName: keyof typeof icon;
+ color: string;
+ label:
+ | string
+ | ((args: {
+ focused: boolean;
+ color: string;
+ position: string;
+ children: string;
+ }) => React.ReactNode);
+}) => {
+ const scale = useSharedValue(0);
+
+ useEffect(() => {
+ scale.value = withSpring(
+ typeof isFocused === 'boolean' ? (isFocused ? 1 : 0) : isFocused,
+ { duration: 350 },
+ );
+ }, [scale, isFocused]);
+
+ const animatedIconStyle = useAnimatedStyle(() => {
+ const scaleValue = interpolate(scale.value, [0, 1], [1, 1.2]);
+
+ const top = interpolate(scale.value, [0, 1], [0, 9]);
+ return {
+ transform: [{ scale: scaleValue }],
+ top,
+ };
+ });
+
+ const animatedTextStyle = useAnimatedStyle(() => {
+ const opacity = interpolate(scale.value, [0, 1], [1, 0]);
+ return {
+ opacity,
+ };
+ });
+ return (
+
+
+ {icon[routeName]({
+ color: isFocused ? Colors.menuWhite : Colors.primary,
+ })}
+
+
+
+ {typeof label === 'function'
+ ? label({
+ focused: isFocused,
+ color: isFocused ? Colors.primary : Colors.primary,
+ position: 'beside-icon',
+ children: '',
+ })
+ : label}
+
+
+ );
+};
+
+export default TabBarButton;
+
+const styles = StyleSheet.create({
+ tabBarItem: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ paddingVertical: 6,
+ },
+});
diff --git a/src/constants/icon.tsx b/src/constants/icon.tsx
new file mode 100644
index 0000000..45386e5
--- /dev/null
+++ b/src/constants/icon.tsx
@@ -0,0 +1,22 @@
+import { Feather } from '@expo/vector-icons';
+import type { ComponentProps } from 'react';
+
+type FeatherIconProps = Omit, 'name'>;
+
+export const icon = {
+ index: (props: FeatherIconProps) => (
+
+ ),
+ categories: (props: FeatherIconProps) => (
+
+ ),
+ branches: (props: FeatherIconProps) => (
+
+ ),
+ offers: (props: FeatherIconProps) => (
+
+ ),
+ deliveryHistory: (props: FeatherIconProps) => (
+
+ ),
+};
diff --git a/src/hooks/useNotifications.ts b/src/hooks/useNotifications.ts
index 127bb31..d3a0832 100644
--- a/src/hooks/useNotifications.ts
+++ b/src/hooks/useNotifications.ts
@@ -67,7 +67,7 @@ export function NotificationsProvider(props: { children: ReactNode }) {
useEffect(() => {
const interval = setInterval(() => {
void refreshNotifications();
- }, 30_000); // ajusta intervalo según necesidad
+ }, 1200_000); // ajusta intervalo según necesidad
return () => clearInterval(interval);
}, [refreshNotifications]);
diff --git a/src/redux/slices/deliverySlice.ts b/src/redux/slices/deliverySlice.ts
index e2f7d42..4de3264 100644
--- a/src/redux/slices/deliverySlice.ts
+++ b/src/redux/slices/deliverySlice.ts
@@ -1,5 +1,8 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
-import { OrderDeliveryDetailedResponse } from '@pharmatech/sdk';
+import {
+ OrderDeliveryDetailedResponse,
+ OrderDeliveryStatus,
+} from '@pharmatech/sdk';
interface DeliveryState {
orders: Record; // Almacenar múltiples órdenes por ID
@@ -30,6 +33,15 @@ const deliverySlice = createSlice({
) {
state.deliveryState[action.payload.id] = action.payload.state;
},
+ updateDeliveryStatus(
+ state,
+ action: PayloadAction<{ id: string; status: OrderDeliveryStatus }>,
+ ) {
+ if (state.orders[action.payload.id]) {
+ state.orders[action.payload.id].deliveryStatus = action.payload
+ .status as OrderDeliveryStatus;
+ }
+ },
resetOrderState(state, action: PayloadAction) {
delete state.orders[action.payload];
delete state.deliveryState[action.payload];
@@ -37,6 +49,10 @@ const deliverySlice = createSlice({
},
});
-export const { setOrderDetails, setDeliveryState, resetOrderState } =
- deliverySlice.actions;
+export const {
+ setOrderDetails,
+ setDeliveryState,
+ updateDeliveryStatus,
+ resetOrderState,
+} = deliverySlice.actions;
export default deliverySlice.reducer;
diff --git a/src/screens/ActiveOrdersScreen.tsx b/src/screens/ActiveOrdersScreen.tsx
index 51bd3eb..a5fc103 100644
--- a/src/screens/ActiveOrdersScreen.tsx
+++ b/src/screens/ActiveOrdersScreen.tsx
@@ -171,6 +171,7 @@ const styles = StyleSheet.create({
flexGrow: 1,
backgroundColor: Colors.bgColor,
padding: 20,
+ paddingTop: -20,
},
alertContainer: {
position: 'absolute',
@@ -183,7 +184,6 @@ const styles = StyleSheet.create({
},
orderHeader: {
alignItems: 'center',
- marginTop: 30,
marginBottom: 15,
},
orderInfo: {
diff --git a/src/screens/CartListScreen.tsx b/src/screens/CartListScreen.tsx
index 89beb9f..35762bc 100644
--- a/src/screens/CartListScreen.tsx
+++ b/src/screens/CartListScreen.tsx
@@ -161,6 +161,7 @@ const CartListScreen = () => {
>
)}
+
);
};
@@ -170,6 +171,7 @@ const styles = StyleSheet.create({
flex: 1,
backgroundColor: Colors.bgColor,
padding: 16,
+ paddingTop: -16,
},
header: {
fontSize: FontSizes.h5.size,
@@ -280,6 +282,9 @@ const styles = StyleSheet.create({
color: Colors.textMain,
marginBottom: 20,
},
+ height: {
+ height: 64,
+ },
row: {
flexDirection: 'row',
justifyContent: 'space-between',
diff --git a/src/screens/DirectionChangeScreen.tsx b/src/screens/DirectionChangeScreen.tsx
index 261a9a6..500ef20 100644
--- a/src/screens/DirectionChangeScreen.tsx
+++ b/src/screens/DirectionChangeScreen.tsx
@@ -251,6 +251,7 @@ const styles = StyleSheet.create({
flexGrow: 1,
backgroundColor: Colors.bgColor,
padding: 20,
+ paddingTop: -20,
},
alertContainer: {
position: 'absolute',
@@ -263,7 +264,6 @@ const styles = StyleSheet.create({
},
addressHeader: {
alignItems: 'center',
- marginTop: 30,
marginBottom: 15,
},
addressImage: {
diff --git a/src/screens/DirectionCreateScreen.tsx b/src/screens/DirectionCreateScreen.tsx
index 62485cd..3e6f478 100644
--- a/src/screens/DirectionCreateScreen.tsx
+++ b/src/screens/DirectionCreateScreen.tsx
@@ -207,6 +207,7 @@ const styles = StyleSheet.create({
flexGrow: 1,
backgroundColor: Colors.bgColor,
padding: 20,
+ paddingTop: -20,
},
alertContainer: {
position: 'absolute',
@@ -219,7 +220,6 @@ const styles = StyleSheet.create({
},
addressHeader: {
alignItems: 'center',
- marginTop: 30,
marginBottom: 15,
},
addressInfo: {
diff --git a/src/screens/DirectionScreen.tsx b/src/screens/DirectionScreen.tsx
index 983b8d2..8649dc8 100644
--- a/src/screens/DirectionScreen.tsx
+++ b/src/screens/DirectionScreen.tsx
@@ -198,6 +198,7 @@ const styles = StyleSheet.create({
flexGrow: 1,
backgroundColor: Colors.bgColor,
padding: 20,
+ paddingTop: -20,
},
alertContainer: {
position: 'absolute',
@@ -210,7 +211,6 @@ const styles = StyleSheet.create({
},
directionHeader: {
alignItems: 'center',
- marginTop: 30,
marginBottom: 15,
},
directionInfo: {
diff --git a/src/screens/LoggedInPasswordRecoveryScreen.tsx b/src/screens/LoggedInPasswordRecoveryScreen.tsx
index e2b2096..880e6f5 100644
--- a/src/screens/LoggedInPasswordRecoveryScreen.tsx
+++ b/src/screens/LoggedInPasswordRecoveryScreen.tsx
@@ -296,7 +296,6 @@ const styles = StyleSheet.create({
flexGrow: 1,
backgroundColor: Colors.bgColor,
justifyContent: 'flex-start',
- paddingTop: 56,
paddingHorizontal: 20,
},
stepsWrapper: {
diff --git a/src/screens/MenuScreen.tsx b/src/screens/MenuScreen.tsx
index 8b18a07..78ba921 100644
--- a/src/screens/MenuScreen.tsx
+++ b/src/screens/MenuScreen.tsx
@@ -214,7 +214,6 @@ const styles = StyleSheet.create({
},
menuContent: {
flex: 1,
- marginTop: 40,
paddingHorizontal: 10,
justifyContent: 'space-between',
},
diff --git a/src/screens/OrderDetailScreen.tsx b/src/screens/OrderDetailScreen.tsx
index 2a4d39b..6dddebc 100644
--- a/src/screens/OrderDetailScreen.tsx
+++ b/src/screens/OrderDetailScreen.tsx
@@ -234,6 +234,7 @@ const styles = StyleSheet.create({
flexGrow: 1,
backgroundColor: Colors.bgColor,
padding: 20,
+ paddingTop: -20,
paddingBottom: 0,
},
alertContainer: {
@@ -253,7 +254,6 @@ const styles = StyleSheet.create({
},
orderHeader: {
alignItems: 'center',
- marginTop: 30,
marginBottom: 15,
},
orderImage: {
diff --git a/src/screens/OrdersScreen.tsx b/src/screens/OrdersScreen.tsx
index aa48501..5433cbf 100644
--- a/src/screens/OrdersScreen.tsx
+++ b/src/screens/OrdersScreen.tsx
@@ -198,6 +198,7 @@ const styles = StyleSheet.create({
flexGrow: 1,
backgroundColor: Colors.bgColor,
padding: 20,
+ paddingTop: -20,
},
alertContainer: {
position: 'absolute',
@@ -210,7 +211,6 @@ const styles = StyleSheet.create({
},
orderHeader: {
alignItems: 'center',
- marginTop: 30,
marginBottom: 15,
},
orderInfo: {
diff --git a/src/screens/PasswordChangeScreen.tsx b/src/screens/PasswordChangeScreen.tsx
index 595e918..9e35277 100644
--- a/src/screens/PasswordChangeScreen.tsx
+++ b/src/screens/PasswordChangeScreen.tsx
@@ -207,7 +207,7 @@ const styles = StyleSheet.create({
backgroundColor: Colors.bgColor,
justifyContent: 'flex-start',
alignItems: 'center',
- paddingTop: 100,
+ paddingTop: 32,
paddingHorizontal: 20,
},
alertContainer: {
diff --git a/src/screens/ProductDetailScreen.tsx b/src/screens/ProductDetailScreen.tsx
index d0728a4..816fe4b 100644
--- a/src/screens/ProductDetailScreen.tsx
+++ b/src/screens/ProductDetailScreen.tsx
@@ -41,6 +41,7 @@ import {
import Button from '../components/Button';
import { BranchService } from '../services/branches';
import { formatPrice } from '../utils/formatPrice';
+import Alert from '../components/Alerts';
const ProductDetailScreen: React.FC = () => {
const { id, productId } = useLocalSearchParams<{
@@ -62,6 +63,7 @@ const ProductDetailScreen: React.FC = () => {
const [hoverRating, setHoverRating] = useState(0);
const [currentImageIndex, setCurrentImageIndex] = useState(0);
const [isLoadingInventory, setIsLoadingInventory] = useState(false);
+ const [showNoStockAlert, setShowNoStockAlert] = useState(false);
const imagesScrollRef = useRef(null);
//const discount = 10;
const router = useRouter();
@@ -308,6 +310,8 @@ const ProductDetailScreen: React.FC = () => {
return discount > 0 ? (original * (100 - discount)) / 100 : original;
};
+ console.log('Stock actual:', product?.stock);
+
return (
@@ -339,6 +343,18 @@ const ProductDetailScreen: React.FC = () => {
+ {/* ALERTA EN LA PARTE SUPERIOR */}
+ {showNoStockAlert && (
+
+ setShowNoStockAlert(false)}
+ />
+
+ )}
+
{/* Carrusel de imágenes */}
{
setShowNoStockAlert(true)}
getValue={(quantity) => {
+ const stock = product?.stock ?? 0;
+ // Si no hay stock o la cantidad supera el stock, muestra alerta y no actualices
+ if (stock === 0 || quantity > stock) {
+ setShowNoStockAlert(true);
+ return;
+ }
+ // Si la cantidad es 0, elimina del carrito
+ if (product?.id && quantity === 0) {
+ updateCartQuantity(product.id, 0);
+ return;
+ }
+ // Si hay stock y cantidad válida, agrega o actualiza en el carrito
if (product?.id && quantity > 0) {
const promo = product.promo;
const discount =
typeof promo?.discount === 'number' ? promo.discount : 0;
+ updateCartQuantity(product.id, quantity);
addToCart({
id: product.id,
name:
@@ -633,7 +665,6 @@ const ProductDetailScreen: React.FC = () => {
});
}
}}
- initialValue={getQuantity()}
/>
@@ -653,6 +684,14 @@ const styles = StyleSheet.create({
width: width,
height: 200,
},
+ alertContainer: {
+ position: 'absolute',
+ width: 326,
+ left: '50%',
+ marginLeft: -163,
+ top: 20,
+ zIndex: 1000,
+ },
imageIndicators: {
flexDirection: 'row',
justifyContent: 'center',
diff --git a/src/screens/ProfileScreen.tsx b/src/screens/ProfileScreen.tsx
index ee10956..1e60176 100644
--- a/src/screens/ProfileScreen.tsx
+++ b/src/screens/ProfileScreen.tsx
@@ -311,6 +311,7 @@ const styles = StyleSheet.create({
flexGrow: 1,
backgroundColor: Colors.bgColor,
padding: 20,
+ paddingTop: -20,
},
alertContainer: {
position: 'absolute',
@@ -323,7 +324,6 @@ const styles = StyleSheet.create({
},
profileHeader: {
alignItems: 'center',
- marginTop: 30,
marginBottom: 15,
},
profileImage: {
diff --git a/src/screens/SelectLocationScreen.tsx b/src/screens/SelectLocationScreen.tsx
index 9930ef0..637f2ac 100644
--- a/src/screens/SelectLocationScreen.tsx
+++ b/src/screens/SelectLocationScreen.tsx
@@ -1,5 +1,11 @@
import React, { useState, useRef, useEffect } from 'react';
-import { StyleSheet, View, TextInput, Alert } from 'react-native';
+import {
+ StyleSheet,
+ View,
+ TextInput,
+ Alert,
+ TouchableOpacity,
+} from 'react-native';
import MapView, { PROVIDER_GOOGLE, Region } from 'react-native-maps'; // Importa el tipo Region
import * as Location from 'expo-location';
import { useLocalSearchParams, useRouter } from 'expo-router';
@@ -8,11 +14,14 @@ import { MapPinIcon } from 'react-native-heroicons/solid';
import { Colors, FontSizes } from '../styles/theme';
import PoppinsText from '../components/PoppinsText';
import { Config } from '../config';
+import { ChevronLeftIcon } from 'react-native-heroicons/outline';
+import { useNavigation } from '@react-navigation/native';
const SelectLocationScreen = () => {
console.log('Clave de API utilizada:', Config.googleMapsApiKey);
const { fromCheckout } = useLocalSearchParams();
+ const navigation = useNavigation();
const [selectedLocation, setSelectedLocation] = useState<{
latitude: number;
@@ -131,6 +140,15 @@ const SelectLocationScreen = () => {
{/* Header con input para mostrar la dirección */}
+
+
+
{
const { id } = useLocalSearchParams();
@@ -41,48 +51,29 @@ const DeliveryDetailScreen: React.FC = () => {
const [order, setOrder] = useState(
undefined,
);
- useEffect(() => {
- const fetchOrder = async () => {
- try {
- if (!orderDetails) {
- console.error(
- 'No se encontraron detalles de la orden de tipo delivery.',
- );
- return;
- }
-
- const orderId = orderDetails.orderId; // Extraer el ID de la orden
- console.log('ID de la orden:', orderId);
-
- const order = await UserService.getOrder(orderId); // Usar el ID de la orden
+ const [isOrderDetailsLoading, setIsOrderDetailsLoading] = useState(true);
+ const [isFetchingOrder, setIsFetchingOrder] = useState(false); // Nuevo estado de carga para fetchOrder
+ const [branchNames, setBranchNames] = useState<
+ Record
+ >({});
+ const [deliveryStateBadge, setDeliveryStateBadge] = useState(0);
+ const router = useRouter();
+ const [isMapModalVisible, setIsMapModalVisible] = useState(false);
- if (order.success) {
- console.log('Datos del pedido:', order.data); // Log para verificar los datos
- setOrder(order.data);
- } else {
- console.error('Error al obtener el pedido:', order.error);
- }
- } catch (error) {
- console.error('Error en fetchOrder:', error);
- } finally {
- setLoading(false);
- }
- };
+ const branchLocation = {
+ latitude: branchNames[orderDetails?.branchId ?? '']?.latitude || 0,
+ longitude: branchNames[orderDetails?.branchId ?? '']?.longitude || 0,
+ };
- fetchOrder();
- }, [orderDetails]);
+ const customerLocation = {
+ latitude: orderDetails?.address?.latitude || 0,
+ longitude: orderDetails?.address?.longitude || 0,
+ };
const deliveryState = useSelector(
(state: RootState) => state.delivery.deliveryState[id as string] || 0,
);
- const [branchNames, setBranchNames] = useState<
- Record
- >({});
- const [loading, setLoading] = useState(true);
- const [deliveryStateBadge, setDeliveryStateBadge] = useState(0);
- const router = useRouter();
-
// Estados para las alertas
const [showAlert, setShowAlert] = useState(false);
const [alertType, setAlertType] = useState<'success' | 'error' | 'info'>(
@@ -104,26 +95,31 @@ const DeliveryDetailScreen: React.FC = () => {
];
const [showConfirmationPopup, setShowConfirmationPopup] = useState(false);
-
- const branchLocation = {
- latitude: branchNames[orderDetails?.branchId ?? '']?.latitude || 0,
- longitude: branchNames[orderDetails?.branchId ?? '']?.longitude || 0,
- };
-
- const customerLocation = {
- latitude: orderDetails?.address?.latitude || 0,
- longitude: orderDetails?.address?.longitude || 0,
- };
+ const [deliveryLocation, setDeliveryLocation] = useState<{
+ latitude: number;
+ longitude: number;
+ } | null>(null);
useEffect(() => {
const fetchOrderDetails = async () => {
- if (!orderDetails) {
- const details = await DeliveryService.getOrderDetails(id as string);
- dispatch(setOrderDetails({ id: id as string, details }));
+ try {
+ if (!orderDetails) {
+ const details = await DeliveryService.getOrderDetails(id as string);
+ dispatch(setOrderDetails({ id: id as string, details }));
+ }
+ } catch (error) {
+ console.error('Error al obtener los detalles del pedido:', error);
+ } finally {
+ setIsOrderDetailsLoading(false);
}
+ };
+ fetchOrderDetails();
+ }, [id, orderDetails, dispatch]);
+
+ useEffect(() => {
+ const fetchBranches = async () => {
try {
- // Obtener todas las sucursales y mapear sus nombres y coordenadas
const branches = await BranchService.findAll({ page: 1, limit: 100 });
const branchMap = branches.results.reduce(
(
@@ -145,14 +141,188 @@ const DeliveryDetailScreen: React.FC = () => {
setBranchNames(branchMap);
} catch (error) {
- console.error('Error al obtener los detalles del pedido:', error);
+ console.error('Error al obtener las sucursales:', error);
} finally {
- setLoading(false);
+ setIsFetchingOrder(false);
}
};
- fetchOrderDetails();
- }, [id, orderDetails, dispatch]);
+ if (orderDetails) {
+ fetchBranches();
+ }
+ }, [orderDetails]);
+
+ useEffect(() => {
+ const fetchOrder = async () => {
+ setIsFetchingOrder(true); // Iniciar el indicador de carga
+ let timerId: NodeJS.Timeout | null = null; // Identificador del temporizador
+
+ try {
+ if (!orderDetails) {
+ // Agregar un temporizador de 2 segundos antes de mostrar el error
+ timerId = setTimeout(() => {
+ if (!orderDetails) {
+ console.error(
+ 'No se encontraron detalles de la orden de tipo delivery.',
+ );
+ }
+ }, 2000);
+ return;
+ }
+
+ const orderId = orderDetails.orderId;
+
+ const order = await UserService.getOrder(orderId);
+
+ if (order.success) {
+ setOrder(order.data);
+ } else {
+ console.error('Error al obtener el pedido:', order.error);
+ }
+ } catch (error) {
+ console.error('Error en fetchOrder:', error);
+ } finally {
+ setIsFetchingOrder(false); // Finalizar el indicador de carga
+ if (timerId) {
+ clearTimeout(timerId); // Cancelar el temporizador si los datos están disponibles
+ }
+ }
+ };
+
+ fetchOrder();
+ }, [orderDetails]);
+
+ // Configuración del WebSocket para recibir actualizaciones en tiempo real
+ useEffect(() => {
+ let socket: Socket;
+
+ const setupSocket = async () => {
+ try {
+ socket = await initializeSocket();
+ console.log('Conectando al WebSocket...');
+ socket.connect();
+
+ socket.on('connect', () => {
+ console.log('WebSocket conectado en DeliveryDetailScreen');
+ });
+
+ socket.on('connect_error', (error) => {
+ console.error(
+ 'Error de conexión al WebSocket en DeliveryDetailScreen:',
+ error,
+ );
+ });
+
+ socket.on('deliveryUpdated', (data: { id: string; status: string }) => {
+ console.log('Evento recibido: deliveryUpdated', data);
+ if (data.id === id) {
+ dispatch(
+ updateDeliveryStatus({
+ id: data.id,
+ status: data.status as OrderDeliveryStatus,
+ }),
+ );
+ }
+ });
+
+ socket.on('disconnect', (reason) => {
+ console.warn(
+ 'WebSocket desconectado en DeliveryDetailScreen. Razón:',
+ reason,
+ );
+ });
+ } catch (error) {
+ console.error(
+ 'Error configurando el WebSocket en DeliveryDetailScreen:',
+ error,
+ );
+ }
+ };
+
+ setupSocket();
+
+ return () => {
+ if (socket) {
+ console.log(
+ 'Limpiando listeners y desconectando WebSocket en DeliveryDetailScreen',
+ );
+ socket.off('deliveryUpdated');
+ disconnectSocket();
+ }
+ };
+ }, [id, dispatch]);
+
+ // ...existing code...
+
+ useEffect(() => {
+ let socket: Socket;
+ let locationSubscription: Location.LocationSubscription | null = null;
+
+ const startTracking = async () => {
+ try {
+ socket = await initializeSocket();
+ socket.connect();
+
+ // Permisos de ubicación
+ const { status } = await Location.requestForegroundPermissionsAsync();
+ if (status !== 'granted') {
+ console.warn('Permiso de ubicación denegado');
+ return;
+ }
+
+ // Suscribirse a cambios de ubicación
+ locationSubscription = await Location.watchPositionAsync(
+ {
+ accuracy: Location.Accuracy.High,
+ timeInterval: 5000, // cada 5 segundos
+ distanceInterval: 10, // o cada 10 metros
+ },
+ (location) => {
+ const { latitude, longitude } = location.coords;
+ setDeliveryLocation({ latitude, longitude });
+
+ // Emitir coordenadas por WebSocket
+ if (orderDetails?.id) {
+ socket.emit('updateCoordinates', {
+ orderId: orderDetails.id,
+ latitude,
+ longitude,
+ });
+ }
+ },
+ );
+ } catch (error) {
+ console.error('Error en el tracking de ubicación:', error);
+ }
+ };
+
+ startTracking();
+
+ return () => {
+ if (locationSubscription) {
+ locationSubscription.remove();
+ }
+ if (socket) {
+ disconnectSocket();
+ }
+ };
+ }, [orderDetails?.id]);
+
+ // Mostrar un indicador de carga mientras se obtienen los detalles del pedido o la orden
+ if (isOrderDetailsLoading || isFetchingOrder) {
+ return ;
+ }
+
+ if (!orderDetails) {
+ // Mostrar un mensaje de error si no se encuentran los detalles del pedido
+ return (
+
+
+ No se encontraron datos del pedido.
+
+
+ );
+ }
const handleNextState = async () => {
try {
@@ -162,29 +332,35 @@ const DeliveryDetailScreen: React.FC = () => {
return;
}
+ let nextStatus: OrderDeliveryStatus | null = null;
+
// Lógica para otros estados
switch (buttonStates[deliveryState]) {
case 'Comenzar entrega':
- await DeliveryService.updateOrderStatus(
- orderDetails!.id,
- OrderDeliveryStatus.WAITING_CONFIRMATION,
- );
+ nextStatus = OrderDeliveryStatus.WAITING_CONFIRMATION;
+ await DeliveryService.updateOrderStatus(orderDetails!.id, nextStatus);
break;
case 'Ya tengo los productos del pedido':
- await DeliveryService.updateOrderStatus(
- orderDetails!.id,
- OrderDeliveryStatus.PICKED_UP,
- );
+ nextStatus = OrderDeliveryStatus.PICKED_UP;
+ await DeliveryService.updateOrderStatus(orderDetails!.id, nextStatus);
break;
case 'Ir a destino de entrega':
- await DeliveryService.updateOrderStatus(
- orderDetails!.id,
- OrderDeliveryStatus.IN_ROUTE,
- );
+ nextStatus = OrderDeliveryStatus.IN_ROUTE;
+ await DeliveryService.updateOrderStatus(orderDetails!.id, nextStatus);
setDeliveryStateBadge(1); // Cambiar a "Haciendo entrega del pedido"
break;
}
+ if (nextStatus) {
+ // Emitir el evento al WebSocket
+ const socket = await initializeSocket();
+ socket.emit('deliveryUpdated', {
+ id: orderDetails!.id,
+ status: nextStatus,
+ });
+ }
+
+ // Mostrar alerta de éxito
setAlertType('info');
setAlertMessage('Se actualizó el estado del pedido.');
setShowAlert(true);
@@ -193,12 +369,14 @@ const DeliveryDetailScreen: React.FC = () => {
setShowAlert(false);
}, 2500);
+ // Actualizar el estado local
dispatch(
setDeliveryState({ id: id as string, state: deliveryState + 1 }),
);
} catch (error) {
console.error('Error al actualizar el estado del delivery:', error);
+ // Mostrar alerta de error
setAlertType('error');
setAlertMessage('Hubo un problema al actualizar el estado del pedido.');
setShowAlert(true);
@@ -242,20 +420,6 @@ const DeliveryDetailScreen: React.FC = () => {
}
};
- if (loading) {
- return ;
- }
-
- if (!orderDetails) {
- return (
-
-
- No se encontraron datos del pedido.
-
-
- );
- }
-
return (
{/* Mostrar alertas */}
@@ -380,6 +544,16 @@ const DeliveryDetailScreen: React.FC = () => {
deliveryState={deliveryState}
branchLocation={branchLocation}
customerLocation={customerLocation}
+ deliveryLocation={deliveryLocation}
+ style={{ height: 300, marginBottom: 16 }}
+ />
+
+
- {order?.details?.map((detail, index) => {
- console.log(`Producto ${index + 1}:`, detail); // Log para depuración
+ {order?.details?.map((detail) => {
return (
{
}}
onClose={() => setShowConfirmationPopup(false)}
/>
+
+ {/* Modal para el mapa ampliado */}
+ setIsMapModalVisible(false)}
+ >
+
+ {/* Botón de cierre */}
+ setIsMapModalVisible(false)}
+ >
+
+
+
+ {/* Mapa ampliado */}
+
+
+ {/* Botón flotante sobre el modal */}
+
+
+
);
};
@@ -647,6 +855,35 @@ const styles = StyleSheet.create({
top: 20,
zIndex: 1000,
},
+ expandButton: {
+ marginBottom: 12,
+ },
+ modalContainer: {
+ flex: 1,
+ backgroundColor: Colors.bgColor,
+ },
+ closeButton: {
+ position: 'absolute',
+ top: 16,
+ right: 16,
+ zIndex: 10,
+ backgroundColor: Colors.textWhite,
+ padding: 8,
+ borderRadius: 16,
+ },
+ fullScreenMapContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginBottom: 16,
+ },
+ modalFloatingButton: {
+ position: 'absolute',
+ bottom: 16,
+ left: 16,
+ right: 16,
+ zIndex: 10,
+ },
});
export default DeliveryDetailScreen;
diff --git a/src/screens/delivery/DeliveryHistoryDetailScreen.tsx b/src/screens/delivery/DeliveryHistoryDetailScreen.tsx
index 1d1b4c3..8dca6d3 100644
--- a/src/screens/delivery/DeliveryHistoryDetailScreen.tsx
+++ b/src/screens/delivery/DeliveryHistoryDetailScreen.tsx
@@ -5,9 +5,15 @@ import {
ScrollView,
ActivityIndicator,
Image,
+ Modal,
+ TouchableOpacity,
} from 'react-native';
import { useLocalSearchParams } from 'expo-router';
-import { PhoneIcon, EnvelopeIcon } from 'react-native-heroicons/solid';
+import {
+ PhoneIcon,
+ EnvelopeIcon,
+ XMarkIcon,
+} from 'react-native-heroicons/solid';
import Badge from '../../components/Badge';
import PoppinsText from '../../components/PoppinsText';
import CustomerAvatar from '../../components/CustomerAvatar';
@@ -21,6 +27,7 @@ import {
OrderDetailedResponse,
} from '@pharmatech/sdk';
import { UserService } from '../../services/user';
+import Button from '../../components/Button';
const DeliveryHistoryDetailScreen: React.FC = () => {
const { id } = useLocalSearchParams();
@@ -31,6 +38,8 @@ const DeliveryHistoryDetailScreen: React.FC = () => {
);
const [loading, setLoading] = useState(true);
+ const [isMapModalVisible, setIsMapModalVisible] = useState(false);
+
const [order, setOrder] = useState(
undefined,
);
@@ -111,19 +120,6 @@ const DeliveryHistoryDetailScreen: React.FC = () => {
minute: '2-digit',
},
);
- const creationTime = new Date(orderDetails.createdAt).toLocaleTimeString(
- 'es-VE',
- {
- hour: '2-digit',
- minute: '2-digit',
- },
- );
- const creationTimePlusOneHour = new Date(
- new Date(orderDetails.createdAt).getTime() + 60 * 43 * 1000,
- ).toLocaleTimeString('es-VE', {
- hour: '2-digit',
- minute: '2-digit',
- });
return (
@@ -211,23 +207,22 @@ const DeliveryHistoryDetailScreen: React.FC = () => {
+ {/* Historial de la entrega
+
Historial de la entrega
- {/* Contenedor de la información de la sucursal */}
+
- {/* Línea vertical punteada */}
+
- {/* Línea vertical punteada */}
- {/* Círculo middle */}
- {/* Círculo inferior */}
@@ -261,7 +256,7 @@ const DeliveryHistoryDetailScreen: React.FC = () => {
-
+ */}
{/* Recorrido de entrega */}
@@ -280,6 +275,15 @@ const DeliveryHistoryDetailScreen: React.FC = () => {
latitude: orderDetails.address.latitude,
longitude: orderDetails.address.longitude,
}}
+ style={{ marginBottom: 16, height: 300 }}
+ />
+
+
+
+ {/* Modal para el mapa ampliado */}
+ setIsMapModalVisible(false)}
+ >
+
+ {/* Botón de cierre */}
+ setIsMapModalVisible(false)}
+ >
+
+
+
+ {/* Mapa ampliado */}
+
+
+
);
};
@@ -610,6 +648,22 @@ const styles = StyleSheet.create({
scrollSpacer: {
height: 64,
},
+ expandButton: {
+ marginBottom: 12,
+ },
+ modalContainer: {
+ flex: 1,
+ backgroundColor: Colors.bgColor,
+ },
+ closeButton: {
+ position: 'absolute',
+ top: 16,
+ right: 16,
+ zIndex: 10,
+ backgroundColor: Colors.textWhite,
+ padding: 8,
+ borderRadius: 16,
+ },
});
export default DeliveryHistoryDetailScreen;
diff --git a/src/screens/delivery/DeliveryHistoryScreen.tsx b/src/screens/delivery/DeliveryHistoryScreen.tsx
index 482b988..5112bc5 100644
--- a/src/screens/delivery/DeliveryHistoryScreen.tsx
+++ b/src/screens/delivery/DeliveryHistoryScreen.tsx
@@ -119,10 +119,15 @@ const DeliveryHistoryScreen: React.FC = () => {
orderType="pedido"
address={order.address?.adress || 'Dirección no disponible'}
branch={branchNames[order.branchId] || 'Sucursal no disponible'}
- estimatedTime={calculateElapsedTime(
- order.createdAt,
- order.updatedAt,
- )} // Tiempo transcurrido entre creación y última actualización
+ estimatedTime={new Date(order.updatedAt).toLocaleDateString(
+ 'es-VE',
+ {
+ weekday: 'long',
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ },
+ )} // Fecha de la última actualización
elapsedTime={calculateElapsedTime(order.createdAt)}
completionTime={new Date(order.updatedAt).toLocaleTimeString(
'es-VE',
@@ -139,6 +144,7 @@ const DeliveryHistoryScreen: React.FC = () => {
/>
))
)}
+
);
@@ -172,6 +178,9 @@ const styles = StyleSheet.create({
padding: 16,
marginHorizontal: 4,
},
+ height: {
+ height: 64,
+ },
title: {
fontSize: FontSizes.h5.size,
lineHeight: FontSizes.h5.lineHeight,
diff --git a/src/screens/delivery/DeliveryHomeScreen.tsx b/src/screens/delivery/DeliveryHomeScreen.tsx
index 3f63b72..3485722 100644
--- a/src/screens/delivery/DeliveryHomeScreen.tsx
+++ b/src/screens/delivery/DeliveryHomeScreen.tsx
@@ -1,5 +1,5 @@
import { useFocusEffect } from '@react-navigation/native'; // Importar useFocusEffect
-import React, { useCallback, useState } from 'react';
+import React, { useCallback, useEffect, useState } from 'react';
import {
View,
StyleSheet,
@@ -17,17 +17,68 @@ import { DeliveryService } from '../../services/delivery';
import { BranchService } from '../../services/branches';
import {
OrderDeliveryDetailedResponse,
- BranchResponse,
OrderDeliveryStatus,
} from '@pharmatech/sdk';
import { useAlert } from '../../components/AlertProvider'; // Importar el hook useAlert
+import * as Location from 'expo-location'; // Importar Location para obtener la ubicación actual
+import { Config } from '../../config'; // Importar la configuración de la API de Google Maps
+import {
+ initializeSocket,
+ disconnectSocket,
+} from '../../lib/deliverySocket/deliverySocket'; // Importar funciones de socket
+import { Socket } from 'socket.io-client'; // Importar el tipo Socket
+
+// Definir el tipo para un leg de la API de Google Maps Directions
+interface GoogleMapsLeg {
+ duration: {
+ value: number; // Duración en segundos
+ };
+}
+
+const calculateTravelTime = async (
+ origin: { latitude: number; longitude: number },
+ destination: { latitude: number; longitude: number },
+): Promise => {
+ try {
+ const response = await fetch(
+ `https://maps.googleapis.com/maps/api/directions/json?origin=${origin.latitude},${origin.longitude}&destination=${destination.latitude},${destination.longitude}&key=${Config.googleMapsApiKey}`,
+ );
+ const data = await response.json();
+
+ if (data.routes && data.routes.length > 0) {
+ // Extraer el tiempo estimado en segundos
+ const durationInSeconds = data.routes[0].legs.reduce(
+ (total: number, leg: GoogleMapsLeg) => total + leg.duration.value,
+ 0,
+ );
+
+ // Convertir a minutos
+ return Math.ceil(durationInSeconds / 60);
+ } else {
+ console.error('No se pudo calcular el tiempo estimado.');
+ return 0;
+ }
+ } catch (error) {
+ console.error('Error al calcular el tiempo estimado:', error);
+ return 0;
+ }
+};
+
+// Extender el tipo OrderDeliveryDetailedResponse
+type ExtendedOrderDeliveryDetailedResponse = OrderDeliveryDetailedResponse & {
+ formattedEstimatedTime: string; // Nueva propiedad para el tiempo formateado
+};
export default function DeliveryHomeScreen() {
const router = useRouter();
const { showAlert } = useAlert();
- const [orders, setOrders] = useState([]);
- const [branchNames, setBranchNames] = useState>({});
+ const [orders, setOrders] = useState(
+ [],
+ );
+ const [branchNames, setBranchNames] = useState<
+ Record
+ >({});
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [selectedOrder, setSelectedOrder] =
@@ -44,30 +95,85 @@ export default function DeliveryHomeScreen() {
const payload = JSON.parse(atob(jwt.split('.')[1]));
const employeeId = payload.sub;
+ // Obtener las órdenes asignadas
const response = await DeliveryService.getAssignedOrders(employeeId);
- const detailedOrders = await Promise.all(
- response.results.map(async (order) => {
- const details = await DeliveryService.getOrderDetails(order.id);
- return details;
- }),
- );
-
- const branchIds = [
- ...new Set(detailedOrders.map((order) => order.branchId)),
- ];
+ // Obtener las sucursales
const branches = await BranchService.findAll({ page: 1, limit: 100 });
const branchMap = branches.results.reduce(
- (acc: Record, branch: BranchResponse) => {
- if (branchIds.includes(branch.id)) {
- acc[branch.id] = branch.name;
- }
+ (
+ acc: Record<
+ string,
+ { name: string; latitude: number; longitude: number }
+ >,
+ branch,
+ ) => {
+ acc[branch.id] = {
+ name: branch.name,
+ latitude: branch.latitude,
+ longitude: branch.longitude,
+ };
return acc;
},
{},
);
-
setBranchNames(branchMap);
+
+ // Procesar las órdenes con cálculo de tiempo estimado
+ const detailedOrders: ExtendedOrderDeliveryDetailedResponse[] =
+ await Promise.all(
+ response.results.map(async (order) => {
+ const details = await DeliveryService.getOrderDetails(order.id);
+
+ // Obtener la ubicación actual del repartidor
+ const location = await Location.getCurrentPositionAsync({});
+ const deliveryLocation = {
+ latitude: location.coords.latitude,
+ longitude: location.coords.longitude,
+ };
+
+ // Obtener la ubicación de la sucursal
+ const branchLocation = {
+ latitude: branchMap[details.branchId]?.latitude || 0,
+ longitude: branchMap[details.branchId]?.longitude || 0,
+ };
+
+ // Obtener la ubicación del cliente
+ const customerLocation = {
+ latitude: details.address.latitude,
+ longitude: details.address.longitude,
+ };
+
+ // Calcular tiempos de viaje
+ const timeToBranch = await calculateTravelTime(
+ deliveryLocation,
+ branchLocation,
+ );
+ const timeToCustomer = await calculateTravelTime(
+ branchLocation,
+ customerLocation,
+ );
+
+ // Tiempo total estimado en minutos
+ const totalEstimatedTime = timeToBranch + timeToCustomer;
+
+ // Formatear el tiempo estimado
+ const formattedEstimatedTime =
+ totalEstimatedTime < 60
+ ? `${totalEstimatedTime} minutos`
+ : `${Math.floor(totalEstimatedTime / 60)} horas ${
+ totalEstimatedTime % 60
+ } minutos`;
+
+ // Retornar la orden con el tiempo estimado formateado
+ return {
+ ...details,
+ estimatedTime: new Date(), // Mantener el tipo original como Date
+ formattedEstimatedTime, // Asignar el tiempo formateado
+ };
+ }),
+ );
+
setOrders(detailedOrders);
} catch (error) {
console.error('Error al obtener las órdenes asignadas:', error);
@@ -89,6 +195,41 @@ export default function DeliveryHomeScreen() {
}, []),
);
+ useEffect(() => {
+ let socket: Socket;
+
+ const setupSocket = async () => {
+ try {
+ socket = await initializeSocket();
+ socket.connect();
+
+ socket.on('deliveryUpdated', (data: { id: string; status: string }) => {
+ setOrders((prevOrders) =>
+ prevOrders.map((order) =>
+ order.id === data.id
+ ? {
+ ...order,
+ deliveryStatus: data.status as OrderDeliveryStatus,
+ }
+ : order,
+ ),
+ );
+ });
+ } catch (error) {
+ console.error('Error configurando el WebSocket:', error);
+ }
+ };
+
+ setupSocket();
+
+ return () => {
+ if (socket) {
+ socket.off('deliveryUpdated');
+ disconnectSocket();
+ }
+ };
+ }, []);
+
const handleTakeOrder = (order: OrderDeliveryDetailedResponse) => {
const query = encodeURIComponent(JSON.stringify(order));
router.push(`/deliveryDetail/${order.id}?data=${query}`);
@@ -127,14 +268,6 @@ export default function DeliveryHomeScreen() {
setSelectedOrder(null);
};
- const formatTime = (isoDate: string | Date): string => {
- const date = typeof isoDate === 'string' ? new Date(isoDate) : isoDate;
- return date.toLocaleTimeString('es-VE', {
- hour: '2-digit',
- minute: '2-digit',
- });
- };
-
const calculateElapsedTime = (createdAt: string): string => {
const now = new Date();
const createdDate = new Date(createdAt);
@@ -186,8 +319,10 @@ export default function DeliveryHomeScreen() {
orderCode={order.orderId.split('-')[0]} // Mostrar solo los primeros 8 caracteres
orderType="pedido"
address={order.address?.adress || 'Dirección no disponible'}
- branch={branchNames[order.branchId] || 'Sucursal no disponible'}
- estimatedTime={formatTime(order.estimatedTime)} // Pasar directamente la cadena ISO
+ branch={
+ branchNames[order.branchId]?.name || 'Sucursal no disponible'
+ }
+ estimatedTime={order.formattedEstimatedTime} // Mostrar el tiempo estimado
elapsedTime={calculateElapsedTime(order.createdAt)}
deliveryStatus={
order.deliveryStatus as
@@ -205,6 +340,7 @@ export default function DeliveryHomeScreen() {
/>
))
)}
+
{/* Popup de confirmación para descartar orden */}
@@ -233,6 +369,9 @@ const styles = StyleSheet.create({
padding: 16,
marginHorizontal: 4,
},
+ height: {
+ height: 64,
+ },
title: {
fontSize: FontSizes.h5.size,
lineHeight: FontSizes.h5.lineHeight,
diff --git a/src/screens/delivery/DeliverySupportScreen.tsx b/src/screens/delivery/DeliverySupportScreen.tsx
deleted file mode 100644
index 5b841c7..0000000
--- a/src/screens/delivery/DeliverySupportScreen.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import React from 'react';
-import { View, StyleSheet } from 'react-native';
-import PoppinsText from '../../components/PoppinsText';
-import { Colors } from '../../styles/theme';
-
-export default function SupportScreen() {
- return (
-
- Pantalla Soporte
-
- );
-}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
- backgroundColor: Colors.bgColor,
- },
-});
diff --git a/src/screens/tab/BranchesScreen.tsx b/src/screens/tab/BranchesScreen.tsx
index f74e03d..9d70be8 100644
--- a/src/screens/tab/BranchesScreen.tsx
+++ b/src/screens/tab/BranchesScreen.tsx
@@ -144,6 +144,7 @@ export default function BranchesScreen() {
+
);
}
@@ -155,6 +156,9 @@ const styles = StyleSheet.create({
padding: 16,
paddingTop: -16,
},
+ height: {
+ height: 64,
+ },
title: {
fontSize: FontSizes.s1.size,
lineHeight: FontSizes.s1.lineHeight,
diff --git a/src/screens/tab/CategoriesScreen.tsx b/src/screens/tab/CategoriesScreen.tsx
index ab692a7..67f7b1b 100644
--- a/src/screens/tab/CategoriesScreen.tsx
+++ b/src/screens/tab/CategoriesScreen.tsx
@@ -83,6 +83,7 @@ export default function CategoriesScreen() {
showsVerticalScrollIndicator={true}
alwaysBounceVertical={true}
/>
+
);
}
@@ -92,6 +93,9 @@ const styles = StyleSheet.create({
backgroundColor: Colors.bgColor,
flex: 1,
},
+ height: {
+ height: 64,
+ },
alertContainer: {
position: 'absolute',
width: 326,
diff --git a/src/screens/tab/HomeScreen.tsx b/src/screens/tab/HomeScreen.tsx
index 5465fa3..92a711f 100644
--- a/src/screens/tab/HomeScreen.tsx
+++ b/src/screens/tab/HomeScreen.tsx
@@ -64,7 +64,9 @@ export default function HomeScreen() {
const productsData = await ProductService.getProducts(1, 20);
if (productsData.success) {
const pd = productsData.data.results;
- const carouselProducts = pd.map((p) => {
+ // Filtra solo los que tengan stock > 0
+ const availableProducts = pd.filter((p) => p.stock > 0);
+ const carouselProducts = availableProducts.map((p) => {
// Usa el descuento real si hay promo, si no, no lo agregues
// @ts-expect-error: promo puede estar en p o en p.presentation
const promo: Promo | undefined = p.promo ?? p.presentation.promo;
@@ -203,6 +205,7 @@ export default function HomeScreen() {
)}
+
+
);
}
@@ -165,4 +166,7 @@ const styles = StyleSheet.create({
alignItems: 'center',
backgroundColor: Colors.bgColor,
},
+ height: {
+ height: 64,
+ },
});
diff --git a/src/screens/tab/SupportScreen.tsx b/src/screens/tab/SupportScreen.tsx
deleted file mode 100644
index ee0cf25..0000000
--- a/src/screens/tab/SupportScreen.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import React from 'react';
-import { View, StyleSheet } from 'react-native';
-import PoppinsText from '../../components/PoppinsText';
-import { Colors } from '../../styles/theme';
-
-export default function SupportScreen() {
- return (
-
- Pantalla Ofertas
-
- );
-}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
- backgroundColor: Colors.bgColor,
- },
-});
diff --git a/src/styles/theme.ts b/src/styles/theme.ts
index 22a60be..c41121f 100644
--- a/src/styles/theme.ts
+++ b/src/styles/theme.ts
@@ -36,6 +36,10 @@ export const Colors = {
gray_100: '#E7E7E6',
gray_500: '#6E6D6C',
+ tertiary_600: '#247D74',
+ tertiary_500: '#319B8F',
+ tertiary_400: '#4AB7A8',
+
secondary_300: '#8BEAB3',
primary_300: '#A2BEEE',