Implement the Slot Management UI
This commit is contained in:
parent
ef2e6a3b51
commit
25f4563484
|
@ -60,7 +60,11 @@ export default function MenuContent({ hidden }: PropType) {
|
||||||
icon: <ManageAccountsOutlinedIcon />,
|
icon: <ManageAccountsOutlinedIcon />,
|
||||||
url: "/panel/vehicle-list", // Placeholder for now
|
url: "/panel/vehicle-list", // Placeholder for now
|
||||||
},
|
},
|
||||||
|
userRole === "manager" && {
|
||||||
|
text: "Add Slots",
|
||||||
|
icon: <ManageAccountsOutlinedIcon />,
|
||||||
|
url: "/panel/EVslots", // Placeholder for now
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const filteredMenuItems = baseMenuItems.filter(Boolean);
|
const filteredMenuItems = baseMenuItems.filter(Boolean);
|
||||||
|
|
379
src/pages/EVSlotManagement/index.tsx
Normal file
379
src/pages/EVSlotManagement/index.tsx
Normal file
|
@ -0,0 +1,379 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import {
|
||||||
|
Tabs,
|
||||||
|
Tab,
|
||||||
|
Box,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
Button,
|
||||||
|
Typography,
|
||||||
|
IconButton,
|
||||||
|
Stack,
|
||||||
|
Snackbar,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { CustomTextField } from "../../components/AddEditUserModel/styled.css.tsx";
|
||||||
|
import AddCircleIcon from "@mui/icons-material/AddCircle";
|
||||||
|
import DeleteIcon from "@mui/icons-material/Delete";
|
||||||
|
import CalendarTodayRoundedIcon from "@mui/icons-material/CalendarTodayRounded";
|
||||||
|
import { LocalizationProvider, DatePicker } from "@mui/x-date-pickers";
|
||||||
|
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
const days = [
|
||||||
|
"Monday",
|
||||||
|
"Tuesday",
|
||||||
|
"Wednesday",
|
||||||
|
"Thursday",
|
||||||
|
"Friday",
|
||||||
|
"Saturday",
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function EVSlotManagement() {
|
||||||
|
const [selectedDay, setSelectedDay] = useState("Monday");
|
||||||
|
const [openingTime, setOpeningTime] = useState("");
|
||||||
|
const [closingTime, setClosingTime] = useState("");
|
||||||
|
const [slots, setSlots] = useState<any>({});
|
||||||
|
const [breakTime, setBreakTime] = useState<any>([]);
|
||||||
|
const [error, setError] = useState<string>("");
|
||||||
|
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
||||||
|
const [selectedDate, setSelectedDate] = useState<dayjs.Dayjs | null>(
|
||||||
|
dayjs()
|
||||||
|
);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const handleBack = () => {
|
||||||
|
navigate("/panel/dashboard"); // Navigate back to Role List
|
||||||
|
};
|
||||||
|
// Add slot function with basic validation
|
||||||
|
const addSlot = (start: string, end: string) => {
|
||||||
|
if (!start || !end) {
|
||||||
|
setError("Start and End times are required.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slots[selectedDay]) {
|
||||||
|
// Check if start time is before end time
|
||||||
|
const newSlot = { start, end };
|
||||||
|
const overlap = slots[selectedDay].some(
|
||||||
|
(slot: any) =>
|
||||||
|
(slot.start <= newSlot.start && slot.end > newSlot.start) ||
|
||||||
|
(slot.start < newSlot.end && slot.end >= newSlot.end)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (overlap) {
|
||||||
|
setError("The selected time overlaps with an existing slot.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
slots[selectedDay] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
slots[selectedDay].push({ start, end });
|
||||||
|
setSlots({ ...slots });
|
||||||
|
setError("");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add break time function
|
||||||
|
const addBreak = (start: string, end: string) => {
|
||||||
|
if (!start || !end) {
|
||||||
|
setError("Break Start and End times are required.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setBreakTime([...breakTime, { start, end }]);
|
||||||
|
setError("");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Delete slot function
|
||||||
|
const deleteSlot = (start: string, end: string) => {
|
||||||
|
const updatedSlots = slots[selectedDay].filter(
|
||||||
|
(slot: any) => !(slot.start === start && slot.end === end)
|
||||||
|
);
|
||||||
|
setSlots({
|
||||||
|
...slots,
|
||||||
|
[selectedDay]: updatedSlots,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Delete break function
|
||||||
|
const deleteBreak = (start: string, end: string) => {
|
||||||
|
const updatedBreaks = breakTime.filter(
|
||||||
|
(breakItem: any) =>
|
||||||
|
!(breakItem.start === start && breakItem.end === end)
|
||||||
|
);
|
||||||
|
setBreakTime(updatedBreaks);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Save function
|
||||||
|
const saveData = () => {
|
||||||
|
if (!openingTime || !closingTime) {
|
||||||
|
setError("Operating hours are required.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulate saving data (e.g., to a database or API)
|
||||||
|
setSuccessMessage(
|
||||||
|
`Data for ${selectedDay} has been saved successfully!`
|
||||||
|
);
|
||||||
|
setError(""); // Clear any previous errors
|
||||||
|
};
|
||||||
|
|
||||||
|
// Close the success message
|
||||||
|
const handleCloseSnackbar = () => {
|
||||||
|
setSuccessMessage(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ maxWidth: 600, mx: "auto", p: 2 }}>
|
||||||
|
<Typography variant="h4" gutterBottom align="center">
|
||||||
|
EV Station Slot Management
|
||||||
|
</Typography>
|
||||||
|
{/* Date Picker */}
|
||||||
|
<Box
|
||||||
|
sx={{ display: "flex", justifyContent: "space-between", mt: 3 }}
|
||||||
|
>
|
||||||
|
<Typography variant="h4" gutterBottom align="center">
|
||||||
|
Select Date
|
||||||
|
</Typography>
|
||||||
|
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||||
|
<DatePicker
|
||||||
|
value={selectedDate}
|
||||||
|
onChange={(newDate) => setSelectedDate(newDate)}
|
||||||
|
renderInput={(props) => (
|
||||||
|
<CustomTextField
|
||||||
|
{...props}
|
||||||
|
fullWidth
|
||||||
|
label="Select Date"
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: (
|
||||||
|
<CalendarTodayRoundedIcon fontSize="small" />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</LocalizationProvider>
|
||||||
|
</Box>
|
||||||
|
<Tabs
|
||||||
|
value={selectedDay}
|
||||||
|
onChange={(event, newValue) => setSelectedDay(newValue)}
|
||||||
|
variant="scrollable"
|
||||||
|
scrollButtons="auto"
|
||||||
|
sx={{ mt: 3 }}
|
||||||
|
>
|
||||||
|
{days.map((day) => (
|
||||||
|
<Tab
|
||||||
|
key={day}
|
||||||
|
label={day}
|
||||||
|
value={day}
|
||||||
|
sx={{
|
||||||
|
fontWeight: "bold",
|
||||||
|
fontSize: "16px",
|
||||||
|
"&.Mui-selected": {
|
||||||
|
backgroundColor: "#52ACDF",
|
||||||
|
color: "white",
|
||||||
|
borderRadius: "8px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
<Card sx={{ mt: 2, p: 2 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Set Operating Hours for {selectedDay}
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ display: "flex", gap: 2, mt: 1 }}>
|
||||||
|
<CustomTextField
|
||||||
|
type="time"
|
||||||
|
label="Opening Time"
|
||||||
|
value={openingTime}
|
||||||
|
onChange={(e) => setOpeningTime(e.target.value)}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<CustomTextField
|
||||||
|
type="time"
|
||||||
|
label="Closing Time"
|
||||||
|
value={closingTime}
|
||||||
|
onChange={(e) => setClosingTime(e.target.value)}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card sx={{ mt: 2, p: 2 }}>
|
||||||
|
<Typography variant="h6">Add Slots</Typography>
|
||||||
|
<Box sx={{ display: "flex", gap: 2, mt: 1 }}>
|
||||||
|
<CustomTextField
|
||||||
|
type="time"
|
||||||
|
label="Start Time"
|
||||||
|
id="slotStart"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<CustomTextField
|
||||||
|
type="time"
|
||||||
|
label="End Time"
|
||||||
|
id="slotEnd"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "#52ACDF",
|
||||||
|
color: "white",
|
||||||
|
borderRadius: "8px",
|
||||||
|
width: "250px",
|
||||||
|
"&:hover": { backgroundColor: "#439BC1" },
|
||||||
|
}}
|
||||||
|
onClick={() =>
|
||||||
|
addSlot(
|
||||||
|
document.getElementById("slotStart").value,
|
||||||
|
document.getElementById("slotEnd").value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AddCircleIcon sx={{ mr: 1 }} /> Add
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
{error && (
|
||||||
|
<Typography color="error" mt={1}>
|
||||||
|
{error}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card sx={{ mt: 2, p: 2 }}>
|
||||||
|
<Typography variant="h6">Break Time</Typography>
|
||||||
|
<Box sx={{ display: "flex", gap: 2, mt: 1 }}>
|
||||||
|
<CustomTextField
|
||||||
|
type="time"
|
||||||
|
label="Break Start"
|
||||||
|
id="breakStart"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<CustomTextField
|
||||||
|
type="time"
|
||||||
|
label="Break End"
|
||||||
|
id="breakEnd"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "#52ACDF",
|
||||||
|
color: "white",
|
||||||
|
borderRadius: "8px",
|
||||||
|
width: "250px",
|
||||||
|
"&:hover": { backgroundColor: "#439BC1" },
|
||||||
|
}}
|
||||||
|
onClick={() =>
|
||||||
|
addBreak(
|
||||||
|
document.getElementById("breakStart").value,
|
||||||
|
document.getElementById("breakEnd").value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AddCircleIcon sx={{ mr: 1 }} /> Add
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card sx={{ mt: 2, p: 2 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Slots for {selectedDay}
|
||||||
|
</Typography>
|
||||||
|
{slots[selectedDay]?.length ? (
|
||||||
|
slots[selectedDay].map((slot: any, index: number) => (
|
||||||
|
<Box
|
||||||
|
key={index}
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
mb: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="body1">
|
||||||
|
{slot.start} - {slot.end}
|
||||||
|
</Typography>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => deleteSlot(slot.start, slot.end)}
|
||||||
|
>
|
||||||
|
<DeleteIcon color="error" />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Typography>No slots added</Typography>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card sx={{ mt: 2, p: 2 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Break Times for {selectedDay}
|
||||||
|
</Typography>
|
||||||
|
{breakTime.length ? (
|
||||||
|
breakTime.map((breakItem: any, index: number) => (
|
||||||
|
<Box
|
||||||
|
key={index}
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
mb: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="body1">
|
||||||
|
{breakItem.start} - {breakItem.end}
|
||||||
|
</Typography>
|
||||||
|
<IconButton
|
||||||
|
onClick={() =>
|
||||||
|
deleteBreak(breakItem.start, breakItem.end)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DeleteIcon color="error" />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Typography>No break times added</Typography>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
<Box
|
||||||
|
sx={{ display: "flex", justifyContent: "space-between", mt: 3 }}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "#52ACDF",
|
||||||
|
color: "white",
|
||||||
|
borderRadius: "8px",
|
||||||
|
"&:hover": { backgroundColor: "#439BC1" },
|
||||||
|
width: "117px",
|
||||||
|
}}
|
||||||
|
onClick={handleBack}
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
{/* Save Button */}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "#52ACDF",
|
||||||
|
color: "white",
|
||||||
|
borderRadius: "8px",
|
||||||
|
width: "117px",
|
||||||
|
"&:hover": { backgroundColor: "#439BC1" },
|
||||||
|
}}
|
||||||
|
onClick={saveData}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
{/* Success Snackbar */}
|
||||||
|
<Snackbar
|
||||||
|
open={!!successMessage}
|
||||||
|
autoHideDuration={6000}
|
||||||
|
onClose={handleCloseSnackbar}
|
||||||
|
message={successMessage}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import roleReducer from "./slices/roleSlice.ts";
|
||||||
import vehicleReducer from "./slices/VehicleSlice.ts";
|
import vehicleReducer from "./slices/VehicleSlice.ts";
|
||||||
import managerReducer from "../redux/slices/managerSlice.ts";
|
import managerReducer from "../redux/slices/managerSlice.ts";
|
||||||
import stationReducer from "../redux/slices/stationSlice.ts";
|
import stationReducer from "../redux/slices/stationSlice.ts";
|
||||||
|
import slotReducer from "../redux/slices/slotSlice.ts";
|
||||||
|
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
|
@ -18,7 +19,8 @@ const rootReducer = combineReducers({
|
||||||
roleReducer,
|
roleReducer,
|
||||||
vehicleReducer,
|
vehicleReducer,
|
||||||
managerReducer,
|
managerReducer,
|
||||||
stationReducer
|
stationReducer,
|
||||||
|
slotReducer,
|
||||||
// Add other reducers here...
|
// Add other reducers here...
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ interface Admin {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AuthState {
|
interface AuthState {
|
||||||
|
managerId: any;
|
||||||
user: User | null;
|
user: User | null;
|
||||||
admins: Admin[];
|
admins: Admin[];
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean;
|
||||||
|
|
190
src/redux/slices/slotSlice.ts
Normal file
190
src/redux/slices/slotSlice.ts
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
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";
|
||||||
|
|
||||||
|
// Define TypeScript types
|
||||||
|
interface Slot {
|
||||||
|
id: number;
|
||||||
|
stationId: number;
|
||||||
|
startTime: Date;
|
||||||
|
endTime: Date;
|
||||||
|
status: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface SlotState {
|
||||||
|
slots: Slot[];
|
||||||
|
availableSlots: Slot[]; // Ensure it's initialized as an empty array
|
||||||
|
loading: boolean;
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial state
|
||||||
|
const initialState: SlotState = {
|
||||||
|
slots: [],
|
||||||
|
availableSlots: [], // <-- Initialize this as an empty array
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch Available Slots
|
||||||
|
export const fetchAvailableSlots = createAsyncThunk<
|
||||||
|
Slot[], // Return type
|
||||||
|
number, // Argument type (stationId)
|
||||||
|
{ rejectValue: string }
|
||||||
|
>("slots/fetchAvailableSlots", async (stationId, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
const response = await http.get("/available", { stationId });
|
||||||
|
return response.data.data; // Return the available slot data
|
||||||
|
} catch (error: any) {
|
||||||
|
toast.error("Error fetching available slots: " + error?.message);
|
||||||
|
return rejectWithValue(error.response?.data?.message || "An error occurred");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const createSlot = createAsyncThunk<
|
||||||
|
Slot, // Return type (Slot)
|
||||||
|
{
|
||||||
|
stationId: number;
|
||||||
|
startTime: Date;
|
||||||
|
endTime: Date;
|
||||||
|
isAvailable: boolean;
|
||||||
|
}, // Argument type (slot data)
|
||||||
|
{ rejectValue: string } // Error type
|
||||||
|
>("slots/createSlot", async (slotData, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
const { stationId, startTime, endTime, isAvailable } = slotData;
|
||||||
|
|
||||||
|
// Ensure that the start and end time are correctly formatted in ISO 8601
|
||||||
|
const startDateTime = new Date(startTime).toISOString(); // convert to ISO string
|
||||||
|
const endDateTime = new Date(endTime).toISOString(); // convert to ISO string
|
||||||
|
|
||||||
|
// Make sure the formatted times are valid
|
||||||
|
if (
|
||||||
|
isNaN(new Date(startDateTime).getTime()) ||
|
||||||
|
isNaN(new Date(endDateTime).getTime())
|
||||||
|
) {
|
||||||
|
throw new Error("Invalid date format");
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
stationId,
|
||||||
|
startTime: startDateTime,
|
||||||
|
endTime: endDateTime,
|
||||||
|
isAvailable,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send the request to create the slot
|
||||||
|
const response = await http.post("/create-slot", payload);
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
toast.success("Slot created successfully");
|
||||||
|
|
||||||
|
// Return the response data (created slot)
|
||||||
|
return response.data.data;
|
||||||
|
} catch (error: any) {
|
||||||
|
// Show error message
|
||||||
|
toast.error("Error creating slot: " + error?.message);
|
||||||
|
|
||||||
|
// Return a detailed error message if possible
|
||||||
|
return rejectWithValue(
|
||||||
|
error.response?.data?.message || "An error occurred"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Update Slot details
|
||||||
|
export const updateSlot = createAsyncThunk<
|
||||||
|
Slot, // Return type
|
||||||
|
{ id: number; startTime: string; endTime: string }, // Argument type (slot update data)
|
||||||
|
{ rejectValue: string }
|
||||||
|
>("slots/updateSlot", async ({ id, ...slotData }, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
const response = await http.patch(`/slots/${id}`, slotData);
|
||||||
|
toast.success("Slot updated successfully");
|
||||||
|
return response.data.data; // Return updated slot data
|
||||||
|
} catch (error: any) {
|
||||||
|
toast.error("Error updating the slot: " + error?.message);
|
||||||
|
return rejectWithValue(error.response?.data?.message || "An error occurred");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete Slot
|
||||||
|
export const deleteSlot = createAsyncThunk<
|
||||||
|
number, // Return type (id of deleted slot)
|
||||||
|
number, // Argument type (id of the slot)
|
||||||
|
{ rejectValue: string }
|
||||||
|
>("slots/deleteSlot", async (id, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
const response = await http.delete(`/slots/${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(createSlot.pending, (state) => {
|
||||||
|
state.loading = true;
|
||||||
|
})
|
||||||
|
.addCase(createSlot.fulfilled, (state, action: PayloadAction<Slot>) => {
|
||||||
|
state.loading = false;
|
||||||
|
state.slots.push(action.payload);
|
||||||
|
})
|
||||||
|
.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.slots.findIndex((slot) => slot.id === action.payload.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
state.slots[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(deleteSlot.fulfilled, (state, action: PayloadAction<number>) => {
|
||||||
|
state.loading = false;
|
||||||
|
// Remove the deleted slot from the state
|
||||||
|
state.slots = state.slots.filter((slot) => slot.id !== action.payload);
|
||||||
|
})
|
||||||
|
.addCase(deleteSlot.rejected, (state, action) => {
|
||||||
|
state.loading = false;
|
||||||
|
state.error = action.payload || "Failed to delete slot";
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default slotSlice.reducer;
|
|
@ -19,6 +19,7 @@ const AddEditRolePage = lazy(() => import("./pages/AddEditRolePage"));
|
||||||
const RoleList = lazy(() => import("./pages/RoleList"));
|
const RoleList = lazy(() => import("./pages/RoleList"));
|
||||||
const ManagerList = lazy(() => import("./pages/ManagerList"));
|
const ManagerList = lazy(() => import("./pages/ManagerList"));
|
||||||
const StationList = lazy(() => import("./pages/StationList"));
|
const StationList = lazy(() => import("./pages/StationList"));
|
||||||
|
const EVSlotManagement = lazy(() => import("./pages/EVSlotManagement"));
|
||||||
|
|
||||||
|
|
||||||
interface ProtectedRouteProps {
|
interface ProtectedRouteProps {
|
||||||
|
@ -91,6 +92,12 @@ export default function AppRouter() {
|
||||||
path="profile"
|
path="profile"
|
||||||
element={<ProtectedRoute component={<ProfilePage />} />}
|
element={<ProtectedRoute component={<ProfilePage />} />}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="EVslots"
|
||||||
|
element={
|
||||||
|
<ProtectedRoute component={<EVSlotManagement />} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
{/* Catch-all Route */}
|
{/* Catch-all Route */}
|
||||||
|
|
Loading…
Reference in a new issue