HabitatMap/AirCasting

View on GitHub
app/javascript/react/store/timelapseSlice.ts

Summary

Maintainability
C
1 day
Test Coverage
import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit";
import { AxiosResponse } from "axios";
import { apiClient } from "../api/apiClient";
import { API_ENDPOINTS } from "../api/apiEndpoints";
import { ApiError, StatusEnum } from "../types/api";
import { TimelapseData, TimeRanges } from "../types/timelapse";
import { getErrorMessage } from "../utils/getErrorMessage";
import { RootState } from "./index";
import { logError } from "../utils/logController";

interface TimelapseState {
  data: TimelapseData;
  status: StatusEnum;
  error: ApiError | null;
  isLoading: boolean;
  currentTimestamp: string | null;
  timelapseTimeRange: TimeRanges;
}

const initialState: TimelapseState = {
  data: {},
  status: StatusEnum.Idle,
  error: null,
  isLoading: false,
  currentTimestamp: null,
  timelapseTimeRange: TimeRanges.HOURS_24,
};

interface TimelapseFilters {
  filters: string;
}

export const fetchTimelapseData = createAsyncThunk<
  TimelapseData,
  TimelapseFilters,
  { rejectValue: ApiError }
>(
  "timelapse/fetchData",
  async (sessionsData, { rejectWithValue }) => {
    try {
      const response: AxiosResponse<TimelapseData> = await apiClient.get(
        API_ENDPOINTS.fetchTimelapseData(sessionsData.filters)
      );
      return response.data;
    } catch (error) {
      const message = getErrorMessage(error);

      const apiError: ApiError = {
        message,
        additionalInfo: {
          action: "fetchTimelapseData",
          endpoint: API_ENDPOINTS.fetchTimelapseData(sessionsData.filters),
        },
      };

      logError(error, apiError);

      return rejectWithValue(apiError);
    }
  },
  {
    condition: (_, { getState }) => {
      const { timelapse } = getState() as RootState;
      if (timelapse.status === StatusEnum.Pending) {
        return false;
      }
    },
  }
);

const timelapseSlice = createSlice({
  name: "timelapse",
  initialState,
  reducers: {
    setCurrentTimestamp(state, action: PayloadAction<string>) {
      state.currentTimestamp = action.payload;
    },
    setTimelapseTimeRange(state, action: PayloadAction<TimeRanges>) {
      state.timelapseTimeRange = action.payload;
    },
  },
  extraReducers: (builder) => {
    builder.addCase(fetchTimelapseData.pending, (state) => {
      state.status = StatusEnum.Pending;
      state.isLoading = true;
      state.error = null;
    });
    builder.addCase(
      fetchTimelapseData.fulfilled,
      (state, action: PayloadAction<TimelapseData>) => {
        state.status = StatusEnum.Fulfilled;
        state.data = action.payload;
        state.isLoading = false;

        const timestamps = Object.keys(action.payload);
        if (timestamps.length > 0) {
          state.currentTimestamp = timestamps[timestamps.length - 1];
        }
      }
    );
    builder.addCase(
      fetchTimelapseData.rejected,
      (state, action: PayloadAction<ApiError | undefined>) => {
        state.status = StatusEnum.Rejected;
        state.error = action.payload || {
          message: "An unknown error occurred",
        };
        state.isLoading = false;
      }
    );
  },
});

export const { setCurrentTimestamp, setTimelapseTimeRange } =
  timelapseSlice.actions;
export default timelapseSlice.reducer;