Debug School

rakesh kumar
rakesh kumar

Posted on

how to manage state and action using Redux implementation in React Native

Redux Implementation and Purpose in React Native
Centralized State Management
Predictable Data Flow
Enhanced Debugging
Improved Code Maintainability
Better Team Collaboration

How to Setting Up Redux
How to Creating the Store(how the state changes in response to actions)
How to Action Types and Actions
How to Connecting Redux to React Native(Wrap your main component with the Provider to make the store available)
Connect components to Redux using hooks:

how the state changes in response to actions
When to Use Redux
When Not to Use Redux
what is slice and its benefit
Difference between reducers and extrareducers
When to Use extraReducers
How to State Handling for Different Async States using extraReducers
Listout real time application of reducers

Redux Implementation and Purpose in React Native

Redux is a predictable state management library that provides a robust solution for managing application state in React Native apps. It centralizes state management, creating a more maintainable and predictable codebase, especially for complex applications.

Purpose of Redux in React Native
Centralized State Management
Redux creates a single "store" that holds the entire application state, making data accessible to any component without complex prop drilling. This central store acts as a single source of truth, which is particularly valuable in larger applications where data needs to be shared across multiple screens and components.

Predictable Data Flow
Redux implements a unidirectional data flow that makes state changes more predictable and easier to track. This predictability comes from:

State can only change through dispatched actions

Reducers are pure functions that produce a new state based on the previous state and action

Changes occur in a strict, deterministic order

Enhanced Debugging
Redux provides powerful developer tools that allow real-time monitoring of:

State changes

Action dispatches

State history (time-travel debugging)
This makes troubleshooting and development significantly easier, as you can inspect exactly how and when your application state changes.

Improved Code Maintainability
By separating state management logic from UI components, Redux creates a cleaner architecture where concerns are properly separated. This separation makes the codebase more maintainable as it grows, simplifying testing and feature development.

Better Team Collaboration
The strict structure and predictable patterns in Redux make it easier for multiple developers to work on the same project without conflicts. The clear action-reducer-state pattern provides a common language for discussing application behavior.

Redux Implementation in React Native

  1. Setting Up Redux First, install the necessary packages:
npm install redux react-redux
Enter fullscreen mode Exit fullscreen mode

For modern Redux development, also consider Redux Toolkit:

npm install @reduxjs/toolkit
Enter fullscreen mode Exit fullscreen mode
  1. Creating the Store The store is the central piece that holds the application state:
import { createStore } from 'redux';

// Define the initial state
const initialState = {
  counter: 0
};

// Create a reducer
function reducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, counter: state.counter + 1 };
    case 'DECREMENT':
      return { ...state, counter: state.counter - 1 };
    default:
      return state;
  }
}

// Create and export the store
const store = createStore(reducer);
export default store;
Enter fullscreen mode Exit fullscreen mode
  1. Organizing Redux with a Folder Structure For larger applications, organize Redux code with a clear structure:
src/
├── redux/
│   ├── actions/
│   │   └── counterActions.js
│   ├── constants/
│   │   └── counterActionTypes.js
│   ├── reducers/
│   │   └── counterReducer.js
│   └── store.js
Enter fullscreen mode Exit fullscreen mode

Action Types and Actions
Define action types as constants to avoid typos and improve maintainability:

export const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
export const DECREMENT_COUNTER = 'DECREMENT_COUNTER';

// counterActions.js
import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../constants/counterActionTypes';

export const incrementCounter = (value = 1) => ({
  type: INCREMENT_COUNTER,
  payload: value
});

export const decrementCounter = () => ({
  type: DECREMENT_COUNTER
});
Enter fullscreen mode Exit fullscreen mode

Creating Reducers
Reducers specify how the state changes in response to actions:

import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../constants/counterActionTypes';

const initialState = {
  counter: 0
};

const counterReducer = (state = initialState, action) => {
  switch (action.type) {
    case INCREMENT_COUNTER:
      return { ...state, counter: state.counter + action.payload };
    case DECREMENT_COUNTER:
      return { ...state, counter: state.counter - 1 };
    default:
      return state;
  }
};

export default counterReducer;
Enter fullscreen mode Exit fullscreen mode

Connecting Redux to React Native
Wrap your main component with the Provider to make the store available:

import React from 'react';
import { Provider } from 'react-redux';
import store from './redux/store';
import App from './App';

const Root = () => (
  <Provider store={store}>
    <App />
  </Provider>
);

export default Root;
Enter fullscreen mode Exit fullscreen mode

Using Redux in Components
Connect components to Redux using hooks:


import React from 'react';
import { View, Text, Button } from 'react-native';
import { useSelector, useDispatch } from 'react-redux';
import { incrementCounter, decrementCounter } from './redux/actions/counterActions';

const CounterScreen = () => {
  // Get state from Redux store
  const counter = useSelector(state => state.counter);

  // Get dispatch function
  const dispatch = useDispatch();

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text style={{ fontSize: 24 }}>Counter: {counter}</Text>
      <Button 
        title="Increment" 
        onPress={() => dispatch(incrementCounter())} 
      />
      <Button 
        title="Decrement" 
        onPress={() => dispatch(decrementCounter())} 
      />
    </View>
  );
};

export default CounterScreen;
Enter fullscreen mode Exit fullscreen mode

Modern Approach with Redux Toolkit
Redux Toolkit simplifies Redux implementation:

import { createSlice, configureStore } from '@reduxjs/toolkit';

// Create a slice
const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0
  },
  reducers: {
    increment: state => {
      state.value += 1;
    },
    decrement: state => {
      state.value -= 1;
    }
  }
});

// Export actions
export const { increment, decrement } = counterSlice.actions;

// Create and export store
const store = configureStore({
  reducer: {
    counter: counterSlice.reducer
  }
});

export default store;
Enter fullscreen mode Exit fullscreen mode

When to Use Redux

  • For medium to large applications with complex state management needs
  • When state needs to be shared across multiple components or screens
  • When you need predictable state updates and debugging capabilities
  • When multiple team members need to work on state management in a structured way

When Not to Use Redux

  • For simple applications with minimal state requirements
  • When component local state is sufficient
  • For very small teams or solo developers building simple apps

Redux provides significant benefits for React Native applications by centralizing state management, making data flow predictable, and improving debugging capabilities. While it adds some complexity to your project setup, these benefits become increasingly valuable as your application grows in size and complexity.

Redux Toolkit provides powerful abstractions to simplify Redux state management. Let's explore three core concepts that work together to handle complex state logic, particularly for asynchronous operations.

Slices

A Redux slice is a collection of reducer logic and actions related to a specific feature or domain within your application's state.

What is a Slice?
A slice represents a portion of your Redux store, typically organized around a specific feature (e.g., users, posts, authentication).

It combines reducers, action creators, and action types in a single, cohesive unit.

Using createSlice
Redux Toolkit's createSlice function automatically generates action creators and action types based on the reducer functions you provide:

import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment(state) {
      state.value += 1;
    },
    decrement(state) {
      state.value -= 1;
    }
  }
});

export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;
Enter fullscreen mode Exit fullscreen mode

Benefits of Slices

  • Reduces boilerplate code significantly
  • Enforces best practices like immutable updates (via Immer)
  • Improves code organization by feature domain
  • Automatically generates action creators and types

ExtraReducers

While reducers handle actions created within a slice, extraReducers allow a slice to respond to actions defined elsewhere.

Key Differences from Regular Reducers
No Action Generation: extraReducers respond to external actions but don't generate new action creators.

External Action Handling: Used for actions defined in other slices or by createAsyncThunk.

Shared Action Response: Allows multiple reducers to respond to the same action.

When to Use extraReducers
Handling async thunk actions (pending, fulfilled, rejected states)

Responding to actions from other slices

Handling global actions (like user logout) that affect multiple slices

The modern approach uses a "builder callback" pattern:

extraReducers: (builder) => {
  builder
    .addCase(fetchVehicles.pending, (state) => {
      state.loading = true;
      state.error = null;
    })
    .addCase(fetchVehicles.fulfilled, (state, action) => {
      state.loading = false;
      state.data = action.payload;
    })
    .addCase(fetchVehicles.rejected, (state, action) => {
      state.loading = false;
      state.error = action.payload;
    });
}
Enter fullscreen mode Exit fullscreen mode

CreateAsyncThunk
createAsyncThunk simplifies handling asynchronous operations in Redux by generating thunks that dispatch standardized actions.

How It Works
Creates a thunk action creator for async operations

Automatically dispatches lifecycle actions: pending, fulfilled, and rejected

Manages promise-based operations like API calls

Parameters
Action Type String: Used as a prefix for generated action types (e.g., 'users/fetchByIdStatus')

Payload Creator: Async callback that returns a promise

Options Object: Optional configuration settings

Generated Actions
For an action type 'posts/getPosts', createAsyncThunk generates three action types:

posts/getPosts/pending: Dispatched when the async operation starts

posts/getPosts/fulfilled: Dispatched when the operation succeeds

posts/getPosts/rejected: Dispatched when the operation fails

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';

// Create the async thunk
export const fetchPosts = createAsyncThunk(
  'posts/fetchPosts',
  async () => {
    const response = await fetch('https://api.example.com/posts');
    return response.json();
  }
);

// Create a slice with extraReducers to handle the async thunk
const postsSlice = createSlice({
  name: 'posts',
  initialState: {
    entities: [],
    loading: false,
    error: null
  },
  reducers: {
    // Local synchronous reducers go here
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchPosts.pending, (state) => {
        state.loading = true;
      })
      .addCase(fetchPosts.fulfilled, (state, action) => {
        state.loading = false;
        state.entities = action.payload;
      })
      .addCase(fetchPosts.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      });
  }
});

export default postsSlice.reducer;
Enter fullscreen mode Exit fullscreen mode

How These Concepts Work Together
Create async thunks using createAsyncThunk for API calls and other async operations.

Define a slice with createSlice to organize your feature's state management.

Use extraReducers to respond to the async thunk's lifecycle actions (pending/fulfilled/rejected).

This pattern creates a clean, efficient workflow for handling async operations in Redux, significantly reducing boilerplate and improving maintainability of your application state.

How to State Handling for Different Async States using extraReducers

extraReducers: (builder) => {
  builder
    .addCase(fetchVehicles.pending, (state) => {
      state.loading = true;
      state.error = null;
    })
    .addCase(fetchVehicles.fulfilled, (state, action) => {
      state.loading = false;
      state.data = action.payload;
    })
    .addCase(fetchVehicles.rejected, (state, action) => {
      state.loading = false;
      state.error = action.payload;
    });
}
Enter fullscreen mode Exit fullscreen mode

This handles the three states of the async operation:

Pending: When the request starts

Fulfilled: When data is successfully received

Rejected: When an error occurs

Part 2: Store Configuration

const store = configureStore({
  reducer: {   
    country: countryReducer,
    otp: otpReducer,
    vehicles: vehicleReducer, // Key relationship here
  },
});

Enter fullscreen mode Exit fullscreen mode

Practical Example

extraReducers: (builder) => {
  builder
    .addCase(fetchVehicles.pending, (state) => {
      state.loading = true;
      state.error = null;
    })
    .addCase(fetchVehicles.fulfilled, (state, action) => {
      state.loading = false;
      state.data = action.payload;
    })
    .addCase(fetchVehicles.rejected, (state, action) => {
      state.loading = false;
      state.error = action.payload;
    });
}
Enter fullscreen mode Exit fullscreen mode

User Interface Synchronization
frontend implemention

function VehicleListScreen() {
  const { data: vehicles, loading, error } = useSelector(state => state.vehicles);

  // Show loading spinner when fetching
  if (loading) return <LoadingSpinner />;

  // Show error message if request failed
  if (error) return <ErrorMessage message={error} />;

  // Render vehicles when data is available
  return (
    <FlatList
      data={vehicles}
      renderItem={({item}) => <VehicleCard vehicle={item} />}
      keyExtractor={item => item.id.toString()}
    />
  );
}
Enter fullscreen mode Exit fullscreen mode
 reducers: {
  clearVehicles: (state) => {
    state.data = [];
  }
}
Enter fullscreen mode Exit fullscreen mode

frontend implemention

 const handleClearVehicles = () => {
    dispatch(clearVehicles());
  };
Enter fullscreen mode Exit fullscreen mode
  reducers: {
    resetOtpState: (state) => {
      state.loading = false;
      state.sendSuccess = false;
      state.verifySuccess = false;
      state.error = null;
    }
  },
Enter fullscreen mode Exit fullscreen mode

Another Practical Example

  extraReducers: (builder) => {
    builder
      .addCase(fetchVehicles.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(fetchVehicles.fulfilled, (state, action) => {
        state.loading = false;
        state.data = action.payload;
      })
      .addCase(fetchVehicles.rejected, (state, action) => {
        state.loading = false;
        state.error = action.payload;
      });
  }
Enter fullscreen mode Exit fullscreen mode
  const vehicles = vehiclesState.data || [];
  const loading = vehiclesState.loading || false;
  const error = vehiclesState.error || null;

  if (loading) {
    return (
      <View style={[styles.container, styles.centered]}>
        <ActivityIndicator size="large" color="#FF5733" />
        <Text>Loading vehicles...</Text>
      </View>
    );
  }
Enter fullscreen mode Exit fullscreen mode

Listout real time application of reducers

User Interface Synchronization

function VehicleListScreen() {
  const { data: vehicles, loading, error } = useSelector(state => state.vehicles);

  // Show loading spinner when fetching
  if (loading) return <LoadingSpinner />;

  // Show error message if request failed
  if (error) return <ErrorMessage message={error} />;

  // Render vehicles when data is available
  return (
    <FlatList
      data={vehicles}
      renderItem={({item}) => <VehicleCard vehicle={item} />}
      keyExtractor={item => item.id.toString()}
    />
  );
}
Enter fullscreen mode Exit fullscreen mode
  1. Pull-to-Refresh Implementation
function RefreshableVehicleList() {
  const dispatch = useDispatch();
  const { baseUrl } = useBaseUrl();
  const { loading } = useSelector(state => state.vehicles);

  const handleRefresh = () => {
    dispatch(fetchVehicles({ baseUrl }));
  };

  return (
    <FlatList
      refreshing={loading}
      onRefresh={handleRefresh}
      // other props
    />
  );
}
Enter fullscreen mode Exit fullscreen mode
  1. Filter Implementation
function FilterableVehicleList() {
  const dispatch = useDispatch();
  const [filters, setFilters] = useState({});

  // Apply new filters and fetch filtered data
  const applyFilters = (newFilters) => {
    setFilters(newFilters);
    dispatch(clearVehicles()); // Clear existing data
    dispatch(fetchVehicles({ baseUrl, filters: newFilters }));
  };

  // Component JSX
}
Enter fullscreen mode Exit fullscreen mode
  1. Error Retry Logic
function VehicleListWithRetry() {
  const dispatch = useDispatch();
  const { error } = useSelector(state => state.vehicles);

  return (
    <>
      {error && (
        <RetryButton 
          onPress={() => dispatch(fetchVehicles({ baseUrl }))}
          message="Failed to load vehicles. Tap to retry."
        />
      )}
      {/* Rest of component */}
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Vehicle Filtering and Search Functionality

  reducers: {
    filterCountries(state, action) {
      const searchTerm = action.payload.toLowerCase();
      state.filteredCountries = state.countries.filter((country) =>
        country.name.toLowerCase().includes(searchTerm)
      );
    },
  },
Enter fullscreen mode Exit fullscreen mode

frontend implemention

  const handleSearchChange = (text) => {
    setSearchTerm(text); // Update local search term state
    dispatch(filterCountries(text)); // Dispatch Redux action to filter countries
  };
Enter fullscreen mode Exit fullscreen mode
const vehicleSlice = createSlice({
  name: 'vehicles',
  initialState: {
    loading: false,
    data: [],
    error: null,
    filters: {
      priceRange: [0, 10000],
      brands: [],
      availability: null
    },
    filteredResults: []
  },
  reducers: {
    updateFilters: (state, action) => {
      state.filters = {...state.filters, ...action.payload};
      state.filteredResults = state.data.filter(vehicle => 
        filterVehicle(vehicle, state.filters)
      );
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

frontend implemention

const VehicleSearchScreen = () => {
  const dispatch = useDispatch();
  const { data: vehicles, filters, filteredResults, loading } = useSelector(state => state.vehicles);
  const [localFilters, setLocalFilters] = useState(filters);

  // Apply filters to Redux state when user submits
  const applyFilters = () => {
    dispatch(updateFilters(localFilters));
  };

  return (
    <View style={styles.container}>
      {/* Filter Controls */}
      <View style={styles.filterSection}>
        <Text style={styles.sectionTitle}>Filters</Text>

        {/* Price Range Slider */}
        <Text>Price Range: ${localFilters.priceRange[0]} - ${localFilters.priceRange[1]}</Text>
        <Slider
          values={localFilters.priceRange}
          min={0}
          max={10000}
          onValuesChange={(values) => setLocalFilters({...localFilters, priceRange: values})}
        />

        {/* Brand Selection */}
        <Text>Select Brands:</Text>
        <FlatList
          data={['Toyota', 'Honda', 'BMW', 'Mercedes']}
          renderItem={({item}) => (
            <CheckBox
              title={item}
              checked={localFilters.brands.includes(item)}
              onPress={() => {
                const newBrands = localFilters.brands.includes(item)
                  ? localFilters.brands.filter(brand => brand !== item)
                  : [...localFilters.brands, item];
                setLocalFilters({...localFilters, brands: newBrands});
              }}
            />
          )}
          keyExtractor={item => item}
        />

        {/* Apply Button */}
        <TouchableOpacity 
          style={styles.applyButton} 
          onPress={applyFilters}
        >
          <Text style={styles.buttonText}>Apply Filters</Text>
        </TouchableOpacity>
      </View>

      {/* Results Section */}
      {loading ? (
        <ActivityIndicator size="large" color="#FF5733" />
      ) : (
        <FlatList
          data={filteredResults.length > 0 ? filteredResults : vehicles}
          renderItem={({item}) => <VehicleCard vehicle={item} />}
          keyExtractor={item => item.id.toString()}
        />
      )}
    </View>
  );
};
Enter fullscreen mode Exit fullscreen mode

Booking Management System

const bookingSlice = createSlice({
  name: 'bookings',
  initialState: {
    activeBookings: [],
    pastBookings: [],
    pendingBookings: [],
    loading: false,
    error: null
  },
  reducers: {
    acceptBooking: (state, action) => {
      const bookingId = action.payload;
      const bookingIndex = state.pendingBookings.findIndex(b => b.id === bookingId);
      if (bookingIndex >= 0) {
        const booking = state.pendingBookings[bookingIndex];
        state.activeBookings.push({...booking, status: 'active'});
        state.pendingBookings.splice(bookingIndex, 1);
      }
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

frontend implemention

const BookingManagementScreen = () => {
  const dispatch = useDispatch();
  const { activeBookings, pendingBookings, pastBookings, loading } = useSelector(state => state.bookings);
  const [selectedTab, setSelectedTab] = useState('active');

  useEffect(() => {
    dispatch(fetchBookings());
  }, []);

  const handleAcceptBooking = (bookingId) => {
    dispatch(acceptBooking(bookingId))
      .then(() => {
        Alert.alert('Success', 'Booking accepted successfully');
      })
      .catch(error => {
        Alert.alert('Error', error.message);
      });
  };

  const renderBookingItem = (booking) => (
    <View style={styles.bookingCard}>
      <Text style={styles.vehicleInfo}>{booking.vehicleName}</Text>
      <Text>Booking Date: {new Date(booking.bookingDate).toLocaleDateString()}</Text>
      <Text>Duration: {booking.duration} hours</Text>
      <Text>Status: <Text style={styles[booking.status]}>{booking.status}</Text></Text>

      {booking.status === 'pending' && (
        <View style={styles.actionButtons}>
          <TouchableOpacity 
            style={styles.acceptButton}
            onPress={() => handleAcceptBooking(booking.id)}
          >
            <Text style={styles.buttonText}>Accept</Text>
          </TouchableOpacity>
          <TouchableOpacity 
            style={styles.rejectButton}
            onPress={() => dispatch(rejectBooking(booking.id))}
          >
            <Text style={styles.buttonText}>Reject</Text>
          </TouchableOpacity>
        </View>
      )}
    </View>
  );

  return (
    <View style={styles.container}>
      {/* Tab Navigation */}
      <View style={styles.tabBar}>
        {['active', 'pending', 'past'].map(tab => (
          <TouchableOpacity
            key={tab}
            style={[styles.tab, selectedTab === tab && styles.activeTab]}
            onPress={() => setSelectedTab(tab)}
          >
            <Text style={styles.tabText}>{tab.charAt(0).toUpperCase() + tab.slice(1)}</Text>
          </TouchableOpacity>
        ))}
      </View>

      {/* Booking List based on Selected Tab */}
      {loading ? (
        <ActivityIndicator size="large" color="#FF5733" />
      ) : (
        <FlatList
          data={
            selectedTab === 'active' ? activeBookings : 
            selectedTab === 'pending' ? pendingBookings : 
            pastBookings
          }
          renderItem={({item}) => renderBookingItem(item)}
          keyExtractor={item => item.id.toString()}
          ListEmptyComponent={<Text style={styles.emptyText}>No {selectedTab} bookings</Text>}
        />
      )}
    </View>
  );
};
Enter fullscreen mode Exit fullscreen mode

User Ratings and Reviews System

const ratingsSlice = createSlice({
  name: 'ratings',
  initialState: {
    vehicleRatings: {},
    userRatings: {},
    loading: false
  },
  reducers: {
    submitRating: (state, action) => {
      const { vehicleId, rating, review } = action.payload;
      if (!state.vehicleRatings[vehicleId]) {
        state.vehicleRatings[vehicleId] = [];
      }
      state.vehicleRatings[vehicleId].push({ rating, review, date: new Date().toISOString() });
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

frontend implemention

const RatingsReviewScreen = () => {
  const dispatch = useDispatch();
  const { vehicleId } = useRoute().params;
  const { vehicleRatings, loading } = useSelector(state => state.ratings);
  const [rating, setRating] = useState(0);
  const [review, setReview] = useState('');

  // Get ratings for this specific vehicle
  const vehicleSpecificRatings = vehicleRatings[vehicleId] || [];
  const averageRating = vehicleSpecificRatings.length > 0 
    ? vehicleSpecificRatings.reduce((sum, item) => sum + item.rating, 0) / vehicleSpecificRatings.length 
    : 0;

  const handleSubmitRating = () => {
    if (rating === 0) {
      Alert.alert('Error', 'Please select a rating');
      return;
    }

    dispatch(submitRating({
      vehicleId,
      rating,
      review
    }))
      .then(() => {
        Alert.alert('Success', 'Rating submitted successfully');
        setRating(0);
        setReview('');
      })
      .catch(error => {
        Alert.alert('Error', error.message);
      });
  };

  return (
    <View style={styles.container}>
      {/* Average Rating Display */}
      <View style={styles.averageRatingContainer}>
        <Text style={styles.averageRatingText}>{averageRating.toFixed(1)}</Text>
        <StarRating
          disabled={true}
          maxStars={5}
          rating={averageRating}
          fullStarColor="#FFD700"
        />
        <Text style={styles.reviewCountText}>
          {vehicleSpecificRatings.length} {vehicleSpecificRatings.length === 1 ? 'review' : 'reviews'}
        </Text>
      </View>

      {/* Submit New Rating */}
      <View style={styles.submitRatingContainer}>
        <Text style={styles.sectionTitle}>Write a Review</Text>
        <StarRating
          disabled={false}
          maxStars={5}
          rating={rating}
          selectedStar={(rating) => setRating(rating)}
          fullStarColor="#FFD700"
        />
        <TextInput
          style={styles.reviewInput}
          placeholder="Share your experience..."
          value={review}
          onChangeText={setReview}
          multiline
        />
        <TouchableOpacity
          style={styles.submitButton}
          onPress={handleSubmitRating}
          disabled={loading}
        >
          <Text style={styles.buttonText}>
            {loading ? 'Submitting...' : 'Submit Review'}
          </Text>
        </TouchableOpacity>
      </View>

      {/* Past Reviews */}
      <Text style={styles.sectionTitle}>Reviews</Text>
      <FlatList
        data={vehicleSpecificRatings}
        renderItem={({item}) => (
          <View style={styles.reviewCard}>
            <View style={styles.reviewHeader}>
              <StarRating
                disabled={true}
                maxStars={5}
                rating={item.rating}
                starSize={18}
                fullStarColor="#FFD700"
              />
              <Text style={styles.reviewDate}>
                {new Date(item.date).toLocaleDateString()}
              </Text>
            </View>
            <Text style={styles.reviewText}>{item.review}</Text>
          </View>
        )}
        keyExtractor={(item, index) => index.toString()}
        ListEmptyComponent={<Text style={styles.emptyText}>No reviews yet</Text>}
      />
    </View>
  );
};
Enter fullscreen mode Exit fullscreen mode

Location-Based Vehicle Search

const locationSlice = createSlice({
  name: 'location',
  initialState: {
    userLocation: null,
    nearbyVehicles: [],
    searchRadius: 5, // kilometers
    loading: false
  },
  reducers: {
    setUserLocation: (state, action) => {
      state.userLocation = action.payload;
    },
    setSearchRadius: (state, action) => {
      state.searchRadius = action.payload;
    }
  },
  extraReducers: (builder) => {
    builder
      .addCase(searchNearbyVehicles.pending, (state) => {
        state.loading = true;
      })
      .addCase(searchNearbyVehicles.fulfilled, (state, action) => {
        state.loading = false;
        state.nearbyVehicles = action.payload;
      });
  }
});
Enter fullscreen mode Exit fullscreen mode

frontend implemention

const NearbyVehiclesScreen = () => {
  const dispatch = useDispatch();
  const { userLocation, nearbyVehicles, searchRadius, loading } = useSelector(state => state.location);
  const { baseUrl } = useBaseUrl();

  useEffect(() => {
    // Get user's current location
    getCurrentPosition()
      .then(position => {
        const { latitude, longitude } = position.coords;
        dispatch(setUserLocation({ latitude, longitude }));

        // Search for nearby vehicles once location is obtained
        if (baseUrl) {
          dispatch(searchNearbyVehicles({ 
            baseUrl, 
            latitude, 
            longitude, 
            radius: searchRadius 
          }));
        }
      })
      .catch(error => {
        Alert.alert('Error', 'Unable to get your location. Please enable location services.');
      });
  }, [baseUrl]);

  // Search with updated radius
  const updateSearchRadius = (radius) => {
    dispatch(setSearchRadius(radius));

    if (userLocation) {
      dispatch(searchNearbyVehicles({
        baseUrl,
        latitude: userLocation.latitude,
        longitude: userLocation.longitude,
        radius
      }));
    }
  };

  return (
    <View style={styles.container}>
      {/* Map View */}
      <View style={styles.mapContainer}>
        {userLocation ? (
          <MapView
            style={styles.map}
            initialRegion={{
              latitude: userLocation.latitude,
              longitude: userLocation.longitude,
              latitudeDelta: 0.0922,
              longitudeDelta: 0.0421,
            }}
          >
            {/* User Location Marker */}
            <Marker
              coordinate={userLocation}
              title="Your Location"
              pinColor="blue"
            />

            {/* Radius Circle */}
            <Circle
              center={userLocation}
              radius={searchRadius * 1000} // Convert km to meters
              strokeWidth={1}
              strokeColor="rgba(66, 133, 244, 0.5)"
              fillColor="rgba(66, 133, 244, 0.1)"
            />

            {/* Vehicle Markers */}
            {nearbyVehicles.map(vehicle => (
              <Marker
                key={vehicle.id}
                coordinate={{
                  latitude: vehicle.latitude,
                  longitude: vehicle.longitude
                }}
                title={`${vehicle.brand} ${vehicle.model}`}
                description={`$${vehicle.price}/hour`}
                onCalloutPress={() => navigation.navigate('VehicleDetails', { vehicle })}
              />
            ))}
          </MapView>
        ) : (
          <ActivityIndicator size="large" color="#FF5733" />
        )}
      </View>

      {/* Search Radius Control */}
      <View style={styles.radiusControl}>
        <Text style={styles.radiusLabel}>Search Radius: {searchRadius} km</Text>
        <Slider
          value={searchRadius}
          minimumValue={1}
          maximumValue={20}
          step={1}
          onValueChange={updateSearchRadius}
        />
      </View>

      {/* Nearby Vehicles List */}
      <View style={styles.listContainer}>
        <Text style={styles.sectionTitle}>
          {loading ? 'Searching...' : `${nearbyVehicles.length} Vehicles Nearby`}
        </Text>
        <FlatList
          data={nearbyVehicles}
          renderItem={({item}) => (
            <TouchableOpacity
              style={styles.vehicleCard}
              onPress={() => navigation.navigate('VehicleDetails', { vehicle: item })}
            >
              <Image source={{ uri: item.image }} style={styles.vehicleImage} />
              <View style={styles.vehicleInfo}>
                <Text style={styles.vehicleName}>{item.brand} {item.model}</Text>
                <Text style={styles.vehiclePrice}>${item.price}/hour</Text>
                <Text style={styles.vehicleDistance}>
                  {calculateDistance(
                    userLocation.latitude,
                    userLocation.longitude,
                    item.latitude,
                    item.longitude
                  ).toFixed(1)} km away
                </Text>
              </View>
            </TouchableOpacity>
          )}
          keyExtractor={item => item.id.toString()}
        />
      </View>
    </View>
  );
};
Enter fullscreen mode Exit fullscreen mode

Payment Processing System

const paymentSlice = createSlice({
  name: 'payments',
  initialState: {
    paymentMethods: [],
    transactions: [],
    pendingPayment: null,
    processingPayment: false,
    error: null
  },
  reducers: {
    addPaymentMethod: (state, action) => {
      state.paymentMethods.push(action.payload);
    },
    selectPaymentMethod: (state, action) => {
      state.pendingPayment = {
        ...state.pendingPayment,
        paymentMethodId: action.payload
      };
    }
  },
  extraReducers: (builder) => {
    builder
      .addCase(processPayment.pending, (state) => {
        state.processingPayment = true;
        state.error = null;
      })
      .addCase(processPayment.fulfilled, (state, action) => {
        state.processingPayment = false;
        state.transactions.push(action.payload);
        state.pendingPayment = null;
      })
      .addCase(processPayment.rejected, (state, action) => {
        state.processingPayment = false;
        state.error = action.payload;
      });
  }
});
Enter fullscreen mode Exit fullscreen mode

frontend implemention

const PaymentScreen = () => {
  const dispatch = useDispatch();
  const { booking } = useRoute().params;
  const { paymentMethods, processingPayment, pendingPayment, error } = useSelector(state => state.payments);
  const [selectedPaymentId, setSelectedPaymentId] = useState(null);

  useEffect(() => {
    dispatch(fetchPaymentMethods());

    // Create a pending payment record
    dispatch(createPendingPayment({
      bookingId: booking.id,
      amount: booking.totalAmount,
      description: `Booking for ${booking.vehicleName}`,
      currency: 'USD'
    }));
  }, [booking]);

  const handlePayment = () => {
    if (!selectedPaymentId) {
      Alert.alert('Error', 'Please select a payment method');
      return;
    }

    dispatch(selectPaymentMethod(selectedPaymentId));

    // Process payment with selected method
    dispatch(processPayment())
      .then(() => {
        Alert.alert('Success', 'Payment processed successfully');
        navigation.navigate('BookingConfirmation', { bookingId: booking.id });
      })
      .catch(error => {
        Alert.alert('Payment Failed', error.message);
      });
  };

  // Add a new payment method
  const handleAddPaymentMethod = () => {
    navigation.navigate('AddPaymentMethod', {
      onPaymentAdded: (newMethod) => {
        dispatch(addPaymentMethod(newMethod));
        setSelectedPaymentId(newMethod.id);
      }
    });
  };

  return (
    <View style={styles.container}>
      {/* Booking Summary */}
      <View style={styles.bookingSummary}>
        <Text style={styles.sectionTitle}>Booking Summary</Text>
        <Text style={styles.vehicleName}>{booking.vehicleName}</Text>
        <Text>Start: {new Date(booking.startTime).toLocaleString()}</Text>
        <Text>End: {new Date(booking.endTime).toLocaleString()}</Text>
        <Text>Duration: {booking.duration} hours</Text>
        <View style={styles.priceSummary}>
          <Text style={styles.priceLabel}>Total:</Text>
          <Text style={styles.priceValue}>${booking.totalAmount.toFixed(2)}</Text>
        </View>
      </View>

      {/* Payment Method Selection */}
      <View style={styles.paymentSelection}>
        <Text style={styles.sectionTitle}>Select Payment Method</Text>
        {paymentMethods.length > 0 ? (
          <FlatList
            data={paymentMethods}
            renderItem={({item}) => (
              <TouchableOpacity
                style={[
                  styles.paymentMethod,
                  selectedPaymentId === item.id && styles.selectedPaymentMethod
                ]}
                onPress={() => setSelectedPaymentId(item.id)}
              >
                <Image source={getPaymentIcon(item.type)} style={styles.paymentIcon} />
                <View style={styles.paymentDetails}>
                  <Text style={styles.paymentName}>{item.name}</Text>
                  <Text style={styles.paymentInfo}>
                    {item.type === 'card' 
                      ? `**** **** **** ${item.lastFour}`
                      : item.email}
                  </Text>
                </View>
                {selectedPaymentId === item.id && (
                  <Icon name="check-circle" size={24} color="#4CAF50" />
                )}
              </TouchableOpacity>
            )}
            keyExtractor={item => item.id.toString()}
          />
        ) : (
          <Text style={styles.emptyText}>No payment methods available</Text>
        )}

        <TouchableOpacity
          style={styles.addPaymentButton}
          onPress={handleAddPaymentMethod}
        >
          <Icon name="plus" size={16} color="#FFF" />
          <Text style={styles.buttonText}>Add Payment Method</Text>
        </TouchableOpacity>
      </View>

      {/* Error Message */}
      {error && (
        <Text style={styles.errorText}>{error}</Text>
      )}

      {/* Pay Button */}
      <TouchableOpacity
        style={[styles.payButton, (!selectedPaymentId || processingPayment) && styles.disabledButton]}
        onPress={handlePayment}
        disabled={!selectedPaymentId || processingPayment}
      >
        <Text style={styles.buttonText}>
          {processingPayment ? 'Processing...' : `Pay $${booking.totalAmount.toFixed(2)}`}
        </Text>
      </TouchableOpacity>
    </View>
  );
};
Enter fullscreen mode Exit fullscreen mode

User Notification System

const notificationSlice = createSlice({
  name: 'notifications',
  initialState: {
    notifications: [],
    unread: 0,
    settings: {
      pushEnabled: true,
      emailEnabled: true,
      bookingAlerts: true,
      promotionalAlerts: false
    }
  },
  reducers: {
    addNotification: (state, action) => {
      state.notifications.unshift(action.payload);
      state.unread += 1;
    },
    markAsRead: (state, action) => {
      const id = action.payload;
      const notification = state.notifications.find(n => n.id === id);
      if (notification && !notification.read) {
        notification.read = true;
        state.unread = Math.max(0, state.unread - 1);
      }
    },
    updateSettings: (state, action) => {
      state.settings = {...state.settings, ...action.payload};
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

frontend implemention

const NotificationsScreen = () => {
  const dispatch = useDispatch();
  const { notifications, unread, settings } = useSelector(state => state.notifications);

  useEffect(() => {
    dispatch(fetchNotifications());
  }, []);

  const handleMarkAsRead = (notificationId) => {
    dispatch(markAsRead(notificationId));
  };

  const handleMarkAllAsRead = () => {
    dispatch(markAllAsRead());
  };

  const handleSettingToggle = (setting) => {
    dispatch(updateSettings({
      [setting]: !settings[setting]
    }));
  };

  const renderNotification = (notification) => {
    const isUnread = !notification.read;

    return (
      <TouchableOpacity 
        style={[styles.notificationItem, isUnread && styles.unreadNotification]}
        onPress={() => handleMarkAsRead(notification.id)}
      >
        <View style={styles.notificationIcon}>
          {getNotificationIcon(notification.type)}
        </View>
        <View style={styles.notificationContent}>
          <Text style={[styles.notificationTitle, isUnread && styles.boldText]}>
            {notification.title}
          </Text>
          <Text style={styles.notificationMessage}>{notification.message}</Text>
          <Text style={styles.notificationTime}>
            {formatTimeAgo(notification.timestamp)}
          </Text>
        </View>
        {isUnread && <View style={styles.unreadIndicator} />}
      </TouchableOpacity>
    );
  };

  return (
    <View style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.screenTitle}>Notifications</Text>
        {unread > 0 && (
          <TouchableOpacity style={styles.markAllButton} onPress={handleMarkAllAsRead}>
            <Text style={styles.markAllText}>Mark all as read</Text>
          </TouchableOpacity>
        )}
      </View>

      {/* Notification List */}
      <FlatList
        data={notifications}
        renderItem={({item}) => renderNotification(item)}
        keyExtractor={item => item.id.toString()}
        ListEmptyComponent={
          <View style={styles.emptyContainer}>
            <Icon name="bell-slash" size={48} color="#ccc" />
            <Text style={styles.emptyText}>No notifications yet</Text>
          </View>
        }
      />

      {/* Notification Settings */}
      <View style={styles.settingsContainer}>
        <Text style={styles.sectionTitle}>Notification Settings</Text>

        <View style={styles.settingItem}>
          <Text style={styles.settingLabel}>Push Notifications</Text>
          <Switch
            value={settings.pushEnabled}
            onValueChange={() => handleSettingToggle('pushEnabled')}
          />
        </View>

        <View style={styles.settingItem}>
          <Text style={styles.settingLabel}>Email Notifications</Text>
          <Switch
            value={settings.emailEnabled}
            onValueChange={() => handleSettingToggle('emailEnabled')}
          />
        </View>

        <View style={styles.settingItem}>
          <Text style={styles.settingLabel}>Booking Alerts</Text>
          <Switch
            value={settings.bookingAlerts}
            onValueChange={() => handleSettingToggle('bookingAlerts')}
          />
        </View>

        <View style={styles.settingItem}>
          <Text style={styles.settingLabel}>Promotional Alerts</Text>
          <Switch
            value={settings.promotionalAlerts}
            onValueChange={() => handleSettingToggle('promotionalAlerts')}
          />
        </View>
      </View>
    </View>
  );
};
Enter fullscreen mode Exit fullscreen mode

Vehicle Maintenance Tracking

const maintenanceSlice = createSlice({
  name: 'maintenance',
  initialState: {
    records: {},
    scheduledMaintenance: [],
    loading: false
  },
  reducers: {
    addMaintenanceRecord: (state, action) => {
      const { vehicleId, record } = action.payload;
      if (!state.records[vehicleId]) {
        state.records[vehicleId] = [];
      }
      state.records[vehicleId].push({
        ...record,
        date: new Date().toISOString()
      });
    },
    scheduleMaintenanceAlert: (state, action) => {
      state.scheduledMaintenance.push(action.payload);
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

frontend implemention

const MaintenanceScreen = () => {
  const dispatch = useDispatch();
  const { vehicleId } = useRoute().params;
  const { records, scheduledMaintenance, loading } = useSelector(state => state.maintenance);
  const [maintenanceType, setMaintenanceType] = useState('');
  const [notes, setNotes] = useState('');
  const [showAddForm, setShowAddForm] = useState(false);

  useEffect(() => {
    dispatch(fetchMaintenanceRecords(vehicleId));
    dispatch(fetchScheduledMaintenance(vehicleId));
  }, [vehicleId]);

  const vehicleRecords = records[vehicleId] || [];

  const handleAddRecord = () => {
    if (!maintenanceType) {
      Alert.alert('Error', 'Please select maintenance type');
      return;
    }

    dispatch(addMaintenanceRecord({
      vehicleId,
      record: {
        type: maintenanceType,
        notes,
        timestamp: new Date().toISOString()
      }
    }));

    // Reset form
    setMaintenanceType('');
    setNotes('');
    setShowAddForm(false);
  };

  const scheduleNextMaintenance = (type, daysFromNow) => {
    const date = new Date();
    date.setDate(date.getDate() + daysFromNow);

    dispatch(scheduleMaintenanceAlert({
      vehicleId,
      type,
      scheduledDate: date.toISOString()
    }));

    Alert.alert('Success', `${type} scheduled for ${date.toLocaleDateString()}`);
  };

  return (
    <View style={styles.container}>
      {loading ? (
        <ActivityIndicator size="large" color="#FF5733" />
      ) : (
        <>
          {/* Scheduled Maintenance Alerts */}
          <View style={styles.scheduledContainer}>
            <Text style={styles.sectionTitle}>Upcoming Maintenance</Text>
            {scheduledMaintenance.length > 0 ? (
              <FlatList
                data={scheduledMaintenance.filter(item => item.vehicleId === vehicleId)}
                renderItem={({item}) => (
                  <View style={styles.alertCard}>
                    <Icon name="wrench" size={24} color="#FF5733" />
                    <View style={styles.alertInfo}>
                      <Text style={styles.alertTitle}>{item.type}</Text>
                      <Text style={styles.alertDate}>
                        Scheduled for: {new Date(item.scheduledDate).toLocaleDateString()}
                      </Text>
                    </View>
                    <TouchableOpacity
                      style={styles.completeButton}
                      onPress={() => {
                        // Complete maintenance and add to records
                        dispatch(addMaintenanceRecord({
                          vehicleId,
                          record: {
                            type: item.type,
                            notes: 'Completed scheduled maintenance',
                            timestamp: new Date().toISOString()
                          }
                        }));
                        dispatch(removeScheduledMaintenance(item.id));
                      }}
                    >
                      <Text style={styles.buttonText}>Complete</Text>
                    </TouchableOpacity>
                  </View>
                )}
                keyExtractor={item => item.id.toString()}
                ListEmptyComponent={<Text>No upcoming maintenance scheduled</Text>}
              />
            ) : (
              <Text>No upcoming maintenance scheduled</Text>
            )}
          </View>

          {/* Quick Schedule Buttons */}
          <View style={styles.quickSchedule}>
            <Text style={styles.subTitle}>Quick Schedule:</Text>
            <View style={styles.buttonRow}>
              <TouchableOpacity 
                style={styles.scheduleButton}
                onPress={() => scheduleNextMaintenance('Oil Change', 90)}
              >
                <Text style={styles.buttonText}>Oil Change</Text>
              </TouchableOpacity>
              <TouchableOpacity 
                style={styles.scheduleButton}
                onPress={() => scheduleNextMaintenance('Tire Rotation', 180)}
              >
                <Text style={styles.buttonText}>Tire Rotation</Text>
              </TouchableOpacity>
              <TouchableOpacity 
                style={styles.scheduleButton}
                onPress={() => scheduleNextMaintenance('Full Service', 365)}
              >
                <Text style={styles.buttonText}>Full Service</Text>
              </TouchableOpacity>
            </View>
          </View>

          {/* Maintenance History */}
          <View style={styles.historyContainer}>
            <View style={styles.historyHeader}>
              <Text style={styles.sectionTitle}>Maintenance History</Text>
              <TouchableOpacity
                style={styles.addButton}
                onPress={() => setShowAddForm(!showAddForm)}
              >
                <Text style={styles.buttonText}>
                  {showAddForm ? 'Cancel' : 'Add Record'}
                </Text>
              </TouchableOpacity>
            </View>

            {showAddForm && (
              <View style={styles.addForm}>
                <Picker
                  selectedValue={maintenanceType}
                  onValueChange={setMaintenanceType}
                  style={styles.picker}
                >
                  <Picker.Item label="Select type..." value="" />
                  <Picker.Item label="Oil Change" value="Oil Change" />
                  <Picker.Item label="Tire Rotation" value="Tire Rotation" />
                  <Picker.Item label="Brake Service" value="Brake Service" />
                  <Picker.Item label="Filter Replacement" value="Filter Replacement" />
                  <Picker.Item label="Other" value="Other" />
                </Picker>
                <TextInput
                  style={styles.notesInput}
                  placeholder="Maintenance notes..."
                  value={notes}
                  onChangeText={setNotes}
                  multiline
                />
                <TouchableOpacity
                  style={styles.submitButton}
                  onPress={handleAddRecord}
                >
                  <Text style={styles.buttonText}>Add Record</Text>
                </TouchableOpacity>
              </View>
            )}

            <FlatList
              data={vehicleRecords}
              renderItem={({item}) => (
                <View style={styles.recordCard}>
                  <View style={styles.recordHeader}>
                    <Text style={styles.recordType}>{item.type}</Text>
                    <Text style={styles.recordDate}>
                      {new Date(item.timestamp).toLocaleDateString()}
                    </Text>
                  </View>
                  <Text style={styles.recordNotes}>{item.notes}</Text>
                </View>
              )}
              keyExtractor={(item, index) => index.toString()}
              ListEmptyComponent={<Text>No maintenance records found</Text>}
            />
          </View>
        </>
      )}
    </View>
  );
};
Enter fullscreen mode Exit fullscreen mode
  1. Analytics Dashboard
const analyticsSlice = createSlice({
  name: 'analytics',
  initialState: {
    bookingTrends: [],
    popularVehicles: [],
    revenueData: {},
    timeRange: 'month',
    loading: false
  },
  reducers: {
    setTimeRange: (state, action) => {
      state.timeRange = action.payload;
    }
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchAnalytics.pending, (state) => {
        state.loading = true;
      })
      .addCase(fetchAnalytics.fulfilled, (state, action) => {
        state.loading = false;
        const { bookingTrends, popularVehicles, revenueData } = action.payload;
        state.bookingTrends = bookingTrends;
        state.popularVehicles = popularVehicles;
        state.revenueData = revenueData;
      });
  }
});
Enter fullscreen mode Exit fullscreen mode

frontend implemention

const AnalyticsDashboardScreen = () => {
  const dispatch = useDispatch();
  const { bookingTrends, popularVehicles, revenueData, timeRange, loading } = useSelector(state => state.analytics);

  useEffect(() => {
    dispatch(fetchAnalytics({ timeRange }));
  }, [timeRange]);

  const handleTimeRangeChange = (range) => {
    dispatch(setTimeRange(range));
  };

  const renderBookingTrendChart = () => {
    if (!bookingTrends || bookingTrends.length === 0) return null;

    return (
      <View style={styles.chartContainer}>
        <Text style={styles.chartTitle}>Booking Trends</Text>
        <LineChart
          data={{
            labels: bookingTrends.map(item => item.label),
            datasets: [{
              data: bookingTrends.map(item => item.count)
            }]
          }}
          width={width - 40}
          height={220}
          chartConfig={{
            backgroundColor: '#FFFFFF',
            backgroundGradientFrom: '#FFFFFF',
            backgroundGradientTo: '#FFFFFF',
            decimalPlaces: 0,
            color: (opacity = 1) => `rgba(66, 133, 244, ${opacity})`,
            labelColor: (opacity = 1) => `rgba(0, 0, 0, ${opacity})`,
          }}
          bezier
          style={styles.chart}
        />
      </View>
    );
  };

  const renderPopularVehiclesChart = () => {
    if (!popularVehicles || popularVehicles.length === 0) return null;

    return (
      <View style={styles.chartContainer}>
        <Text style={styles.chartTitle}>Most Popular Vehicles</Text>
        <BarChart
          data={{
            labels: popularVehicles.map(item => item.name.split(' ')[0]), // First word of name
            datasets: [{
              data: popularVehicles.map(item => item.bookingCount)
            }]
          }}
          width={width - 40}
          height={220}
          chartConfig={{
            backgroundColor: '#FFFFFF',
            backgroundGradientFrom: '#FFFFFF',
            backgroundGradientTo: '#FFFFFF',
            decimalPlaces: 0,
            color: (opacity = 1) => `rgba(255, 87, 51, ${opacity})`,
            labelColor: (opacity = 1) => `rgba(0, 0, 0, ${opacity})`,
          }}
          style={styles.chart}
        />
      </View>
    );
  };

  const renderRevenueCard = () => {
    if (!revenueData) return null;

    return (
      <View style={styles.revenueCard}>
        <Text style={styles.revenueTitle}>Revenue Summary</Text>
        <View style={styles.metricsContainer}>
          <View style={styles.metric}>
            <Text style={styles.metricValue}>${revenueData.total.toFixed(2)}</Text>
            <Text style={styles.metricLabel}>Total Revenue</Text>
          </View>
          <View style={styles.metric}>
            <Text style={styles.metricValue}>${revenueData.average.toFixed(2)}</Text>
            <Text style={styles.metricLabel}>Avg. per Booking</Text>
          </View>
          <View style={styles.metric}>
            <Text style={[
              styles.metricValue,
              revenueData.percentChange >= 0 ? styles.positiveChange : styles.negativeChange
            ]}>
              {revenueData.percentChange >= 0 ? '+' : ''}{revenueData.percentChange}%
            </Text>
            <Text style={styles.metricLabel}>vs. Previous</Text>
          </View>
        </View>
      </View>
    );
  };

  return (
    <View style={styles.container}>
      {/* Time Range Selector */}
      <View style={styles.timeRangeSelector}>
        {['week', 'month', 'quarter', 'year'].map(range => (
          <TouchableOpacity
            key={range}
            style={[styles.rangeButton, timeRange === range && styles.activeRangeButton]}
            onPress={() => handleTimeRangeChange(range)}
          >
            <Text style={[
              styles.rangeButtonText,
              timeRange === range && styles.activeRangeButtonText
            ]}>
              {range.charAt(0).toUpperCase() + range.slice(1)}
            </Text>
          </TouchableOpacity>
        ))}
      </View>

      {loading ? (
        <ActivityIndicator size="large" color="#FF5733" style={styles.loader} />
      ) : (
        <ScrollView contentContainerStyle={styles.scrollContent}>
          {renderRevenueCard()}
          {renderBookingTrendChart()}
          {renderPopularVehiclesChart()}

          {/* Top Performing Locations */}
          <View style={styles.locationsContainer}>
            <Text style={styles.sectionTitle}>Top Performing Locations</Text>
            {revenueData?.topLocations?.map((location, index) => (
              <View key={index} style={styles.locationItem}>
                <Text style={styles.locationName}>{location.name}</Text>
                <View style={styles.locationStats}>
                  <Text style={styles.bookingCount}>{location.bookings} bookings</Text>
                  <Text style={styles.locationRevenue}>${location.revenue.toFixed(2)}</Text>
                </View>
                <View style={styles.progressBar}>
                  <View 
                    style={[
                      styles.progressFill, 
                      { width: `${(location.revenue / revenueData.topLocations[0].revenue) * 100}%` }
                    ]} 
                  />
                </View>
              </View>
            ))}
          </View>
        </ScrollView>
      )}
    </View>
  );
};
Enter fullscreen mode Exit fullscreen mode

Top comments (0)