import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit"; import http from "../../lib/https"; // Assuming you have a custom HTTP library for requests import { toast } from "sonner"; import dayjs from "dayjs"; // Define TypeScript types interface Slot { id: string; stationId: string; date?: string; startTime: string; startingDate?: string; endingDate?: string; endTime: string; duration: number; isAvailable: boolean; stationName: string; ChargingStation: { name: string }; } interface SlotState { slots: Slot[]; availableSlots: Slot[]; loading: boolean; error: string | null; } // Initial state const initialState: SlotState = { slots: [], availableSlots: [], // <-- Initialize this as an empty array loading: false, error: null, }; export const fetchAvailableSlots = createAsyncThunk< Slot[], void, { rejectValue: string } >("fetchVehicles", async (_, { rejectWithValue }) => { try { const token = localStorage?.getItem("authToken"); if (!token) throw new Error("No token found"); const response = await http.get("/available"); if (!response.data?.data) throw new Error("Invalid API response"); return response.data.data; } catch (error: any) { toast.error("Error Fetching Profile" + error); return rejectWithValue( error?.response?.data?.message || "An error occurred" ); } }); export const fetchManagersSlots = createAsyncThunk< Slot[], void, { rejectValue: string } >("fetchManagersSlots", async (_, { rejectWithValue }) => { try { const token = localStorage?.getItem("authToken"); if (!token) throw new Error("No token found"); const response = await http.get("/manager-slots"); if (!response.data?.data) throw new Error("Invalid API response"); return response.data.data; } catch (error: any) { toast.error("Error Fetching Profile" + error); return rejectWithValue( error?.response?.data?.message || "An error occurred" ); } }); export const createSlot = createAsyncThunk< Slot[], // <-- updated from Slot to Slot[] { date?: string; startingDate?: string; endingDate?: string; startHour: string; endHour: string; isAvailable: boolean; duration: number; stationId: number; }, { rejectValue: string } >("slots/createSlot", async (payload, { rejectWithValue }) => { try { const token = localStorage?.getItem("authToken"); if (!token) throw new Error("No token found"); // Make the API call to create the slots const response = await http.post("create-slot", payload); toast.success("Slot(s) created successfully"); return response.data.data; // Return the array of created slots } catch (error: any) { toast.error("Error creating slot: " + error?.message); return rejectWithValue( error.response?.data?.message || "An error occurred" ); } }); // Update Slot details export const updateSlot = createAsyncThunk< Slot, { id: string; startTime: string; endTime: string; }, { rejectValue: string } >("slots/updateSlot", async ({ id, ...slotData }, { rejectWithValue }) => { try { const response = await http.patch(`/update-availability/${id}`, { ...slotData, // Ensure data matches exactly what backend expects startHour: slotData.startTime, endHour: slotData.endTime, }); toast.success("Slot updated successfully"); return response.data.data; } catch (error: any) { toast.error("Error updating the slot: " + error?.message); return rejectWithValue( error.response?.data?.message || "An error occurred" ); } }); export const toggleStatus = createAsyncThunk< any, { id: string; isAvailable: boolean }, { rejectValue: string } >("slot/toggleStatus", async ({ id, isAvailable }, { rejectWithValue }) => { try { const response = await http.patch(`/update-availability/${id}`, { isAvailable, }); if (response.data.statusCode === 200) { toast.success( response.data.message || "Status updated successfully" ); return { responseData: response.data, id, isAvailable, }; } else { throw new Error(response.data.message || "Failed to update status"); } } catch (error: any) { toast.error( "Error updating status: " + (error.message || "Unknown error") ); return rejectWithValue( error.response?.data?.message || error.message || "An error occurred" ); } }); export const deleteSlot = createAsyncThunk< string, // Return type (id of deleted slot) string, { rejectValue: string } >("slots/deleteSlot", async (id, { rejectWithValue }) => { try { const response = await http.delete(`/delete-slot/${id}`); toast.success("Slot deleted successfully"); return id; // Return the id of the deleted slot } catch (error: any) { toast.error("Error deleting the slot: " + error?.message); return rejectWithValue( error.response?.data?.message || "An error occurred" ); } }); const slotSlice = createSlice({ name: "slots", initialState, reducers: {}, extraReducers: (builder) => { builder .addCase(fetchAvailableSlots.pending, (state) => { state.loading = true; state.error = null; }) .addCase( fetchAvailableSlots.fulfilled, (state, action: PayloadAction) => { state.loading = false; state.availableSlots = action.payload; } ) .addCase(fetchAvailableSlots.rejected, (state, action) => { state.loading = false; state.error = action.payload || "Failed to fetch available slots"; }) .addCase(fetchManagersSlots.pending, (state) => { state.loading = true; state.error = null; }) .addCase( fetchManagersSlots.fulfilled, (state, action: PayloadAction) => { state.loading = false; state.availableSlots = action.payload; } ) .addCase(fetchManagersSlots.rejected, (state, action) => { state.loading = false; state.error = action.payload || "Failed to fetch available slots"; }) .addCase(createSlot.pending, (state) => { state.loading = true; }) .addCase( createSlot.fulfilled, (state, action: PayloadAction) => { state.loading = false; const normalizedSlots = action.payload.map((slot) => { const combinedStart = `${slot.date} ${slot.startTime}`; // Keep raw for now const combinedEnd = `${slot.date} ${slot.endTime}`; return { ...slot, startTime: combinedStart, endTime: combinedEnd, }; }); console.log("Normalized Slots →", normalizedSlots); // Check this in console state.slots.push(...normalizedSlots); state.availableSlots.push(...normalizedSlots); } ) .addCase(createSlot.rejected, (state, action) => { state.loading = false; state.error = action.payload || "Failed to create slot"; }) .addCase(updateSlot.pending, (state) => { state.loading = true; }) .addCase( updateSlot.fulfilled, (state, action: PayloadAction) => { state.loading = false; // Update the slot in the state with the updated data const index = state.availableSlots.findIndex( (slot) => slot.id === action.payload.id ); if (index !== -1) { state.availableSlots[index] = action.payload; } } ) .addCase(updateSlot.rejected, (state, action) => { state.loading = false; state.error = action.payload || "Failed to update slot"; }) .addCase(deleteSlot.pending, (state) => { state.loading = true; }) .addCase(toggleStatus.pending, (state) => { state.loading = true; }) .addCase( toggleStatus.fulfilled, (state, action: PayloadAction) => { state.loading = false; const { id, isAvailable } = action.payload; const stationIndex = state.availableSlots.findIndex( (slot) => slot.id === id ); if (stationIndex !== -1) { state.availableSlots[stationIndex] = { ...state.availableSlots[stationIndex], isAvailable: isAvailable, }; } } ) .addCase( toggleStatus.rejected, (state, action: PayloadAction) => { state.loading = false; state.error = action.payload || "Failed to toggle station status"; } ) .addCase( deleteSlot.fulfilled, (state, action: PayloadAction) => { state.loading = false; // Ensure we're filtering the correct array (availableSlots) state.availableSlots = state.availableSlots.filter( (slot) => String(slot.id) !== String(action.payload) ); // Also update slots array if it exists if (state.slots) { state.slots = state.slots.filter( (slot) => String(slot.id) !== String(action.payload) ); } } ) .addCase(deleteSlot.rejected, (state, action) => { state.loading = false; state.error = action.payload || "Failed to delete slot"; }); }, }); export default slotSlice.reducer;