330 lines
8.6 KiB
TypeScript
330 lines
8.6 KiB
TypeScript
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<Slot[]>) => {
|
|
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<Slot[]>) => {
|
|
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<Slot[]>) => {
|
|
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<Slot>) => {
|
|
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<any>) => {
|
|
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<string | undefined>) => {
|
|
state.loading = false;
|
|
state.error =
|
|
action.payload || "Failed to toggle station status";
|
|
}
|
|
)
|
|
.addCase(
|
|
deleteSlot.fulfilled,
|
|
(state, action: PayloadAction<string>) => {
|
|
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;
|