update the slot managenemt and Implement User Booking Api

This commit is contained in:
jaanvi 2025-03-20 18:27:52 +05:30
parent 80f603703c
commit 61963d6fe2
10 changed files with 620 additions and 358 deletions

View file

@ -0,0 +1,224 @@
import { Controller, useForm } from "react-hook-form";
import {
Box,
Button,
Typography,
Modal,
IconButton,
InputAdornment,
} from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { Visibility, VisibilityOff } from "@mui/icons-material";
import { useDispatch } from "react-redux";
import { addBooking, bookingList } from "../../redux/slices/bookSlice.ts";
import React, { useState } from "react";
import {
CustomIconButton,
CustomTextField,
} from "../AddEditUserModel/styled.css.tsx";
export default function AddBookingModal({
open,
handleClose,
}: {
open: boolean;
handleClose: () => void;
}) {
const dispatch = useDispatch();
const {
control,
register,
handleSubmit,
formState: { errors },
reset,
watch,
} = useForm();
const [showPassword, setShowPassword] = useState(false);
const togglePasswordVisibility = () => setShowPassword((prev) => !prev);
const onSubmit = async (data: any) => {
const bookingData = {
stationId: data.stationId,
date: data.date,
startTime: data.startTime,
endTime: data.endTime,
};
try {
await dispatch(addBooking(bookingData));
dispatch(bookingList());
handleClose(); // Close modal after successful addition
reset(); // Reset form fields
} catch (error) {
console.error("Error adding booking:", error);
}
};
return (
<Modal
open={open}
onClose={(e, reason) => {
if (reason === "backdropClick") {
return;
}
handleClose();
}}
aria-labelledby="add-booking-modal"
>
<Box
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: "background.paper",
boxShadow: 24,
p: 3,
borderRadius: 2,
}}
>
{/* Header */}
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<Typography variant="h6" fontWeight={600}>
Add Booking
</Typography>
<CustomIconButton onClick={handleClose}>
<CloseIcon />
</CustomIconButton>
</Box>
<Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
<form onSubmit={handleSubmit(onSubmit)}>
{/* Station ID Input */}
<Box sx={{ display: "flex", gap: 2, mb: 2 }}>
<Box sx={{ flex: 1 }}>
<Typography variant="body2" fontWeight={500}>
Station ID
</Typography>
<CustomTextField
fullWidth
placeholder="Enter Station Id"
size="small"
error={!!errors.stationId}
helperText={
errors.stationId
? errors.stationId.message
: ""
}
{...register("stationId", {
required: "Station ID is required",
})}
/>
</Box>
</Box>
{/* Date Input */}
<Box sx={{ display: "flex", gap: 2, mb: 2 }}>
<Box sx={{ flex: 1 }}>
<Typography variant="body2" fontWeight={500}>
Date
</Typography>
<CustomTextField
fullWidth
placeholder="Select Booking Date"
size="small"
type="date"
error={!!errors.date}
helperText={
errors.date ? errors.date.message : ""
}
{...register("date", {
required: "Date is required",
})}
/>
</Box>
</Box>
{/* Start Time Input */}
<Box sx={{ display: "flex", gap: 2, mb: 2 }}>
<Box sx={{ flex: 1 }}>
<Typography variant="body2" fontWeight={500}>
Start Time
</Typography>
<CustomTextField
fullWidth
placeholder="Enter Start Time"
size="small"
type="time"
error={!!errors.startTime}
helperText={
errors.startTime
? errors.startTime.message
: ""
}
{...register("startTime", {
required: "Start Time is required",
})}
/>
</Box>
</Box>
{/* End Time Input */}
<Box sx={{ display: "flex", gap: 2, mb: 2 }}>
<Box sx={{ flex: 1 }}>
<Typography variant="body2" fontWeight={500}>
End Time
</Typography>
<CustomTextField
fullWidth
placeholder="Enter End Time"
size="small"
type="time"
error={!!errors.endTime}
helperText={
errors.endTime ? errors.endTime.message : ""
}
{...register("endTime", {
required: "End Time is required",
validate: (value) => {
const startTime = watch("startTime");
if (value <= startTime) {
return "End time must be after start time.";
}
return true;
},
})}
/>
</Box>
</Box>
{/* Submit Button */}
<Box
sx={{
display: "flex",
justifyContent: "flex-end",
mt: 3,
}}
>
<Button
type="submit"
sx={{
backgroundColor: "#52ACDF",
color: "white",
borderRadius: "8px",
width: "117px",
"&:hover": { backgroundColor: "#439BC1" },
}}
>
Add Booking
</Button>
</Box>
</form>
</Box>
</Modal>
);
}

View file

@ -37,6 +37,7 @@ import { deleteUser, userList } from "../../redux/slices/userSlice.ts";
import { deleteStation } from "../../redux/slices/stationSlice.ts"; import { deleteStation } from "../../redux/slices/stationSlice.ts";
import StationViewModal from "../Modals/StationViewModal/index.tsx"; import StationViewModal from "../Modals/StationViewModal/index.tsx";
import { fetchAvailableSlots } from "../../redux/slices/slotSlice.ts"; import { fetchAvailableSlots } from "../../redux/slices/slotSlice.ts";
import { bookingList } from "../../redux/slices/bookSlice.ts";
// Styled components for customization // Styled components for customization
const StyledTableCell = styled(TableCell)(({ theme }) => ({ const StyledTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: { [`&.${tableCellClasses.head}`]: {
@ -112,14 +113,6 @@ const CustomTable: React.FC<CustomTableProps> = ({
setSelectedRow(row); setSelectedRow(row);
setRowData(row); setRowData(row);
}; };
// const handleViewButton = (id: string | undefined) => {
// if (!id) {
// console.error("ID not found for viewing.");
// return;
// }
// setViewModal(true);
// };
const handleClose = () => { const handleClose = () => {
setAnchorEl(null); setAnchorEl(null);
}; };
@ -150,6 +143,9 @@ const CustomTable: React.FC<CustomTableProps> = ({
case "station": case "station":
dispatch(deleteStation(id || "")); dispatch(deleteStation(id || ""));
break; break;
// case "booking":
// dispatch(deleteBooking(id || ""));
// break;
default: default:
console.error("Unknown table type:", tableType); console.error("Unknown table type:", tableType);
return; return;
@ -173,8 +169,8 @@ const CustomTable: React.FC<CustomTableProps> = ({
case "user": case "user":
dispatch(userList()); dispatch(userList());
break; break;
case "slot": case "booking":
dispatch(fetchAvailableSlots(1)); dispatch(bookingList());
break; break;
default: default:
console.error("Unknown table type:", tableType); console.error("Unknown table type:", tableType);
@ -242,6 +238,8 @@ const CustomTable: React.FC<CustomTableProps> = ({
? "Vehicles" ? "Vehicles"
: tableType === "station" : tableType === "station"
? "Charging Station" ? "Charging Station"
: tableType === "booking"
? "Booking"
: "List"} : "List"}
</Typography> </Typography>
@ -315,6 +313,8 @@ const CustomTable: React.FC<CustomTableProps> = ({
? "Vehicle" ? "Vehicle"
: tableType === "station" : tableType === "station"
? "Charging Station" ? "Charging Station"
: tableType === "booking"
? "Booking"
: "Item"} : "Item"}
</Button> </Button>
</Box> </Box>

View file

@ -65,10 +65,10 @@ export default function MenuContent({ hidden }: PropType) {
icon: <ManageAccountsOutlinedIcon />, icon: <ManageAccountsOutlinedIcon />,
url: "/panel/EVslots", // Placeholder for now url: "/panel/EVslots", // Placeholder for now
}, },
userRole === "manager" && { userRole === "user" && {
text: "Slot List", text: "Bookings",
icon: <ManageAccountsOutlinedIcon />, icon: <ManageAccountsOutlinedIcon />,
url: "/panel/slot-list", // Placeholder for now url: "/panel/booking-list", // Placeholder for now
}, },
]; ];

View file

@ -0,0 +1,116 @@
import React, { useEffect, useState } from "react";
import { Box, Button, TextField, Typography } from "@mui/material";
import CustomTable, { Column } from "../../components/CustomTable";
import { useDispatch, useSelector } from "react-redux";
import { RootState, AppDispatch } from "../../redux/store/store";
import { useForm } from "react-hook-form";
import { addBooking, bookingList } from "../../redux/slices/bookSlice";
import AddBookingModal from "../../components/AddBookingModal";
export default function ManagerList() {
const [addModalOpen, setAddModalOpen] = useState<boolean>(false);
const [editModalOpen, setEditModalOpen] = useState<boolean>(false);
const [editRow, setEditRow] = useState<any>(null);
const { reset } = useForm();
const [deleteModal, setDeleteModal] = useState<boolean>(false);
const [viewModal, setViewModal] = useState<boolean>(false);
const [rowData, setRowData] = useState<any | null>(null);
const [searchTerm, setSearchTerm] = useState("");
const dispatch = useDispatch<AppDispatch>();
const bookings = useSelector(
(state: RootState) => state?.bookReducer.bookings
);
const users = useSelector((state: RootState) => state?.profileReducer.user);
useEffect(() => {
dispatch(bookingList());
}, [dispatch]);
const handleClickOpen = () => {
setRowData(null);
setAddModalOpen(true);
};
const handleCloseModal = () => {
setAddModalOpen(false);
setEditModalOpen(false);
setRowData(null);
reset();
};
const handleAddBooking = async (data: {
stationId: string;
date: string;
startTime: string;
endTime: string;
}) => {
try {
await dispatch(addBooking(data));
await dispatch(bookingList());
handleCloseModal(); // Close the modal
} catch (error) {
console.error("Error adding manager", error);
}
};
const categoryColumns: Column[] = [
{ id: "srno", label: "Sr No" },
{ id: "name", label: "User Name" },
// { id: "stationName", label: "Station Name" },
// { id: "registeredAddress", label: "Station Location" },
{ id: "stationId", label: "Station Id" },
{ id: "date", label: "Date" },
{ id: "startTime", label: "Start Time" },
{ id: "endTime", label: "End Time" },
{ id: "action", label: "Action", align: "center" },
];
const categoryRows = bookings?.length
? bookings?.map(function (
booking: {
id: number;
name: string;
stationId: string;
date: string;
startTime: string;
endTime: string;
},
index: number
) {
return {
id: booking?.id ?? "NA",
srno: index + 1,
name: users?.name ?? "NA",
stationId: booking?.stationId ?? "NA",
date: booking?.date ?? "NA",
startTime: booking?.startTime ?? "NA",
endTime: booking?.endTime ?? "NA",
};
})
: [];
// console.log("Rowa", categoryRows);
return (
<>
<CustomTable
columns={categoryColumns}
rows={categoryRows}
setDeleteModal={setDeleteModal}
deleteModal={deleteModal}
setViewModal={setViewModal}
viewModal={viewModal}
setRowData={setRowData}
setModalOpen={() => setEditModalOpen(true)}
tableType="booking"
handleClickOpen={handleClickOpen}
/>
{/* Add Manager Modal */}
<AddBookingModal
open={addModalOpen}
handleClose={handleCloseModal}
handleAddManager={handleAddBooking}
/>
</>
);
}

View file

@ -49,44 +49,35 @@ export default function EVSlotManagement() {
const navigate = useNavigate(); const navigate = useNavigate();
const dispatch = useDispatch(); const dispatch = useDispatch();
// Fetch slots from the Redux state
const { slots, availableSlots } = useSelector( const { slots, availableSlots } = useSelector(
(state: RootState) => state?.slotReducer (state: RootState) => state?.slotReducer
); );
const manager = useSelector((state: RootState) => state.managerReducer.managers);
// console.log("availableSlots",availableSlots); console.log("availableSlots", availableSlots);
useEffect(() => { useEffect(() => {
dispatch(fetchAvailableSlots()); dispatch(fetchAvailableSlots());
}, [dispatch]); }, [dispatch]);
const handleBack = () => { const handleBack = () => {
navigate("/panel/dashboard"); navigate("/panel/dashboard");
}; };
const addSlot = (start: string, end: string) => {
const selectedDateFormatted = selectedDate?.format("YYYY-MM-DD");
const addSlot = (start: string, end: string) => { const startTime = start;
const endTime = end;
const selectedDateFormatted = selectedDate.format("YYYY-MM-DD"); dispatch(
createSlot({
date: selectedDateFormatted,
const startTime = start; startHour: startTime,
const endTime = end; endHour: endTime,
isAvailable: true,
})
dispatch( );
createSlot({ // dispatch(fetchAvailableSlots());
stationId: manager.stationId, };
date: selectedDateFormatted,
startHour: startTime,
endHour: endTime,
isAvailable: true, // Ensure it's available by default
})
);
};
// Delete slot function that dispatches deleteSlot // Delete slot function that dispatches deleteSlot
const addBreak = (start: string, end: string) => { const addBreak = (start: string, end: string) => {
@ -99,22 +90,11 @@ const addSlot = (start: string, end: string) => {
setError(""); 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,
// });
// };
const handleDeleteSlot = (slotId: number) => { const handleDeleteSlot = (slotId: number) => {
// Call the deleteSlot action with the correct slotId
dispatch(deleteSlot(slotId)); dispatch(deleteSlot(slotId));
dispatch(fetchAvailableSlots());
}; };
// Delete break function // Delete break function
const deleteBreak = (start: string, end: string) => { const deleteBreak = (start: string, end: string) => {
const updatedBreaks = breakTime.filter( const updatedBreaks = breakTime.filter(
@ -138,11 +118,10 @@ const addSlot = (start: string, end: string) => {
setError(""); // Clear any previous errors setError(""); // Clear any previous errors
}; };
// Close the success message
const handleCloseSnackbar = () => { const handleCloseSnackbar = () => {
setSuccessMessage(null); setSuccessMessage(null);
}; };
console.log("Slots: ",slots)
return ( return (
<Box sx={{ maxWidth: 600, mx: "auto", p: 2 }}> <Box sx={{ maxWidth: 600, mx: "auto", p: 2 }}>
<Typography variant="h4" gutterBottom align="center"> <Typography variant="h4" gutterBottom align="center">
@ -201,27 +180,6 @@ const addSlot = (start: string, end: string) => {
))} ))}
</Tabs> </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 }}> <Card sx={{ mt: 2, p: 2 }}>
<Typography variant="h6">Add Slots</Typography> <Typography variant="h6">Add Slots</Typography>
<Box sx={{ display: "flex", gap: 2, mt: 1 }}> <Box sx={{ display: "flex", gap: 2, mt: 1 }}>
@ -299,37 +257,29 @@ const addSlot = (start: string, end: string) => {
<Typography variant="h6" gutterBottom> <Typography variant="h6" gutterBottom>
Slots for {selectedDay} Slots for {selectedDay}
</Typography> </Typography>
{/* Ensure that `slots` is always an array */}
{Array.isArray(availableSlots) && availableSlots.length ? ( {Array.isArray(availableSlots) && availableSlots.length ? (
availableSlots availableSlots.map((slot: any, index: number) => (
.filter( <Box
(slot: any) => key={index}
dayjs(slot.date).format("YYYY-MM-DD") === sx={{
selectedDate.format("YYYY-MM-DD") && display: "flex",
dayjs(slot.date).format("dddd") === selectedDay justifyContent: "space-between",
) alignItems: "center",
.map((slot: any, index: number) => ( mb: 1,
<Box }}
key={index} >
sx={{ <Typography variant="body1">
display: "flex", {dayjs(slot.date).format("YYYY-MM-DD")} -{" "}
justifyContent: "space-between", {dayjs(slot.startTime).format("HH:mm")} -{" "}
alignItems: "center", {dayjs(slot.endTime).format("HH:mm")}
mb: 1, </Typography>
}} <IconButton
onClick={() => handleDeleteSlot(slot.id)}
> >
<Typography variant="body1"> <DeleteIcon color="error" />
{dayjs(slot.startHour).format("HH:mm")} -{" "} </IconButton>
{dayjs(slot.endHour).format("HH:mm")} </Box>
</Typography> ))
<IconButton
onClick={() => handleDeleteSlot(slot.id)}
>
<DeleteIcon color="error" />
</IconButton>
</Box>
))
) : ( ) : (
<Typography>No slots added for {selectedDay}</Typography> <Typography>No slots added for {selectedDay}</Typography>
)} )}
@ -366,7 +316,7 @@ const addSlot = (start: string, end: string) => {
<Typography>No break times added</Typography> <Typography>No break times added</Typography>
)} )}
</Card> </Card>
<Box {/* <Box
sx={{ display: "flex", justifyContent: "space-between", mt: 3 }} sx={{ display: "flex", justifyContent: "space-between", mt: 3 }}
> >
<Button <Button
@ -394,15 +344,15 @@ const addSlot = (start: string, end: string) => {
> >
Save Save
</Button> </Button>
</Box> </Box> */}
{/* Success Snackbar */} {/* Success Snackbar */}
<Snackbar {/* <Snackbar
open={!!successMessage} open={!!successMessage}
autoHideDuration={6000} autoHideDuration={6000}
onClose={handleCloseSnackbar} onClose={handleCloseSnackbar}
message={successMessage} message={successMessage}
/> /> */}
</Box> </Box>
); );
} }

View file

@ -1,147 +0,0 @@
import React, { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import CustomTable, { Column } from "../../components/CustomTable";
import { RootState } from "../../redux/reducers";
import { useDispatch, useSelector } from "react-redux";
import { AppDispatch } from "../../redux/store/store";
import {
createSlot,
updateSlot,
fetchAvailableSlots,
deleteSlot,
} from "../../redux/slices/slotSlice";
import EVSlotManagement from "../EVSlotManagement";
import AddSlotModal from "../../components/AddSlotModal";
import dayjs from "dayjs";
// import AddSlotModal from "../../components/AddSlotModal";
// import EditSlotModal from "../../components/EditSlotModal";
export default function EvSlotList() {
const [addModalOpen, setAddModalOpen] = useState<boolean>(false);
const [editModalOpen, setEditModalOpen] = useState<boolean>(false);
const [editRow, setEditRow] = useState<any>(null);
const { reset } = useForm();
const [deleteModal, setDeleteModal] = useState<boolean>(false);
const [viewModal, setViewModal] = useState<boolean>(false);
const [rowData, setRowData] = useState<any | null>(null);
const [searchTerm, setSearchTerm] = useState("");
const dispatch = useDispatch<AppDispatch>();
const slots = useSelector((state: RootState) => state.slotReducer.slots);
useEffect(() => {
dispatch(fetchAvailableSlots(1)); // Fetch slots when component mounts
}, [dispatch]);
const handleClickOpen = () => {
setRowData(null); // Reset row data when opening for new slot
setAddModalOpen(true);
};
const handleCloseModal = () => {
setAddModalOpen(false);
setEditModalOpen(false);
setRowData(null);
reset();
};
const handleAddSlot = async (data: {
startHour: string;
endHour: string;
stationId: number;
date: string;
}) => {
try {
await dispatch(createSlot(data)); // Create the slot with all necessary fields
await dispatch(fetchAvailableSlots()); // Refetch updated slots
handleCloseModal(); // Close modal after successful creation
} catch (error) {
console.error("Error adding slot", error);
toast.error("Failed to add slot");
}
};
// const handleUpdateSlot = async (
// id: number,
// startHour: string,
// endHour: string
// ) => {
// try {
// await dispatch(updateSlot({ id, startHour, endHour }));
// await dispatch(fetchAvailableSlots());
// handleCloseModal();
// } catch (error) {
// console.error("Update failed", error);
// }
// };
// const handleDeleteSlot = async (id: number) => {
// try {
// await dispatch(deleteSlot(id));
// await dispatch(fetchAvailableSlots());
// } catch (error) {
// console.error("Delete failed", error);
// }
// };
const slotColumns: Column[] = [
{ id: "srno", label: "Sr No" },
{ id: "stationId", label: "Station Id" },
{ id: "date", label: "Date" },
{ id: "startHour", label: "Start Hour" },
{ id: "endHour", label: "End Hour" },
{ id: "action", label: "Action", align: "center" },
];
const filteredSlots = slots?.filter(
(slot) =>
dayjs(slot.startTime)
.format("HH:mm")
.toLowerCase()
.includes(searchTerm.toLowerCase()) ||
dayjs(slot.endTime)
.format("HH:mm")
.toLowerCase()
.includes(searchTerm.toLowerCase())
);
const slotRows = filteredSlots?.length
? filteredSlots.map((slot: any, index: number) => ({
id: slot?.id,
srno: index + 1,
startHour: dayjs(slot?.startTime).format("HH:mm"), // Format startTime
endHour: dayjs(slot?.endTime).format("HH:mm"), // Format endTime
}))
: [];
return (
<>
<CustomTable
columns={slotColumns}
rows={slotRows}
setDeleteModal={setDeleteModal}
deleteModal={deleteModal}
setViewModal={setViewModal}
viewModal={viewModal}
setRowData={setRowData}
setModalOpen={() => setEditModalOpen(true)}
tableType="slot"
handleClickOpen={handleClickOpen}
// handleDelete={handleDeleteSlot} // Allow deleting
/>
<AddSlotModal
open={addModalOpen}
handleClose={handleCloseModal}
handleAddSlot={handleAddSlot}
/>
{/* <EditSlotModal
open={editModalOpen}
handleClose={handleCloseModal}
handleUpdateSlot={handleUpdateSlot}
editRow={rowData}
/> */}
</>
);
}

View file

@ -9,6 +9,7 @@ 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"; import slotReducer from "../redux/slices/slotSlice.ts";
import bookReducer from "../redux/slices/bookSlice.ts";
const rootReducer = combineReducers({ const rootReducer = combineReducers({
@ -21,6 +22,7 @@ const rootReducer = combineReducers({
managerReducer, managerReducer,
stationReducer, stationReducer,
slotReducer, slotReducer,
bookReducer,
// Add other reducers here... // Add other reducers here...
}); });

View file

@ -0,0 +1,107 @@
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
import http from "../../lib/https";
import { toast } from "sonner";
interface Booking {
id: number;
name:string;
stationId: string;
date: string;
startTime: string;
endTime: string;
}
interface BookingState {
bookings: Booking[];
loading: boolean;
error: string | null;
}
const initialState: BookingState = {
bookings: [],
loading: false,
error: null,
};
export const bookingList = createAsyncThunk<
Booking,
void,
{ rejectValue: string }
>("fetchBooking", async (_, { rejectWithValue }) => {
try {
const token = localStorage?.getItem("authToken");
if (!token) throw new Error("No token found");
const response = await http.get("/user-bookings");
if (!response.data?.data) throw new Error("Invalid API response");
return response.data.data;
} catch (error: any) {
toast.error("Error Fetching User Booking List" + error);
return rejectWithValue(
error?.response?.data?.message || "An error occurred"
);
}
});
export const addBooking = createAsyncThunk<
Booking,
{
stationId: string;
date: string;
startTime: string;
endTime: string;
},
{ rejectValue: string }
>("/AddBooking", async (data, { rejectWithValue }) => {
try {
const response = await http.post("/book-slot", data);
return response.data;
} catch (error: any) {
return rejectWithValue(
error.response?.data?.message || "An error occurred"
);
}
});
const bookSlice = createSlice({
name: "booking",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(bookingList.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(
bookingList.fulfilled,
(state, action: PayloadAction<Booking[]>) => {
state.loading = false;
state.bookings = action.payload;
}
)
.addCase(bookingList.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message || "Failed to fetch users";
})
.addCase(addBooking.pending, (state) => {
state.loading = true;
// state.error = null;
})
.addCase(
addBooking.fulfilled,
(state, action: PayloadAction<Booking>) => {
state.loading = false;
state.bookings.push(action.payload);
}
)
.addCase(
addBooking.rejected,
(state, action: PayloadAction<string | undefined>) => {
state.loading = false;
}
);
},
});
export default bookSlice.reducer;

View file

@ -6,16 +6,14 @@ import { toast } from "sonner";
interface Slot { interface Slot {
id: number; id: number;
date: string; date: string;
stationId: number;
startHour: Date; startHour: Date;
endHour: Date; endHour: Date;
isAvailable: boolean; isAvailable: boolean;
} }
interface SlotState { interface SlotState {
slots: Slot[]; slots: Slot[];
availableSlots: Slot[]; // Ensure it's initialized as an empty array availableSlots: Slot[];
loading: boolean; loading: boolean;
error: string | null; error: string | null;
} }
@ -28,7 +26,6 @@ const initialState: SlotState = {
error: null, error: null,
}; };
export const fetchAvailableSlots = createAsyncThunk< export const fetchAvailableSlots = createAsyncThunk<
Slot[], Slot[],
void, void,
@ -54,7 +51,6 @@ export const fetchAvailableSlots = createAsyncThunk<
export const createSlot = createAsyncThunk< export const createSlot = createAsyncThunk<
Slot, Slot,
{ {
stationId: number;
date: string; date: string;
startHour: string; startHour: string;
endHour: string; endHour: string;
@ -63,26 +59,19 @@ export const createSlot = createAsyncThunk<
{ rejectValue: string } { rejectValue: string }
>( >(
"slots/createSlot", "slots/createSlot",
async ( async (payload, { rejectWithValue }) => {
{ stationId, date, startHour, endHour, isAvailable },
{ rejectWithValue }
) => {
try { try {
const payload = { // const payload = {
stationId, // date,
date, // This should be in the format 'YYYY-MM-DD' // startHour,
startHour, // This should be in 'HH:mm' format // endHour,
endHour, // isAvailable,
isAvailable, // };
};
// Send the request to create the slot const token = localStorage?.getItem("authToken");
if (!token) throw new Error("No token found");
const response = await http.post("/create-slot", payload); const response = await http.post("/create-slot", payload);
// Show success message
toast.success("Slot created successfully"); toast.success("Slot created successfully");
// Return the response data (created slot)
return response.data.data; return response.data.data;
} catch (error: any) { } catch (error: any) {
// Show error message // Show error message
@ -98,93 +87,113 @@ export const createSlot = createAsyncThunk<
// Update Slot details // Update Slot details
export const updateSlot = createAsyncThunk< export const updateSlot = createAsyncThunk<
Slot, // Return type Slot,
{ id: number; startTime: string; endTime: string }, // Argument type (slot update data) { id: number; startTime: string; endTime: string }, // Argument type (slot update data)
{ rejectValue: string } { rejectValue: string }
>("slots/updateSlot", async ({ id, ...slotData }, { rejectWithValue }) => { >("slots/updateSlot", async ({ id, ...slotData }, { rejectWithValue }) => {
try { try {
const response = await http.patch(`/slots/${id}`, slotData); const response = await http.patch(`/slots/${id}`, slotData);
toast.success("Slot updated successfully"); toast.success("Slot updated successfully");
return response.data.data; // Return updated slot data return response.data.data; // Return updated slot data
} catch (error: any) { } catch (error: any) {
toast.error("Error updating the slot: " + error?.message); toast.error("Error updating the slot: " + error?.message);
return rejectWithValue(error.response?.data?.message || "An error occurred"); return rejectWithValue(
} error.response?.data?.message || "An error occurred"
);
}
}); });
// Delete Slot
export const deleteSlot = createAsyncThunk< export const deleteSlot = createAsyncThunk<
number, // Return type (id of deleted slot) number, // Return type (id of deleted slot)
number, // Argument type (id of the slot) number,
{ rejectValue: string } { rejectValue: string }
>("slots/deleteSlot", async (id, { rejectWithValue }) => { >("slots/deleteSlot", async (id, { rejectWithValue }) => {
try { try {
const response = await http.delete(`/delete-slot/${id}`); const response = await http.delete(`/delete-slot/${id}`);
toast.success("Slot deleted successfully"); toast.success("Slot deleted successfully");
return id; // Return the id of the deleted slot return id; // Return the id of the deleted slot
} catch (error: any) { } catch (error: any) {
toast.error("Error deleting the slot: " + error?.message); toast.error("Error deleting the slot: " + error?.message);
return rejectWithValue(error.response?.data?.message || "An error occurred"); return rejectWithValue(
} error.response?.data?.message || "An error occurred"
);
}
}); });
const slotSlice = createSlice({ const slotSlice = createSlice({
name: "slots", name: "slots",
initialState, initialState,
reducers: {}, reducers: {},
extraReducers: (builder) => { extraReducers: (builder) => {
builder builder
.addCase(fetchAvailableSlots.pending, (state) => { .addCase(fetchAvailableSlots.pending, (state) => {
state.loading = true; state.loading = true;
state.error = null; state.error = null;
}) })
.addCase(fetchAvailableSlots.fulfilled, (state, action: PayloadAction<Slot[]>) => { .addCase(
state.loading = false; fetchAvailableSlots.fulfilled,
state.availableSlots = action.payload; (state, action: PayloadAction<Slot[]>) => {
}) state.loading = false;
.addCase(fetchAvailableSlots.rejected, (state, action) => { state.availableSlots = action.payload;
state.loading = false; }
state.error = action.payload || "Failed to fetch available slots"; )
}) .addCase(fetchAvailableSlots.rejected, (state, action) => {
.addCase(createSlot.pending, (state) => { state.loading = false;
state.loading = true; state.error =
}) action.payload || "Failed to fetch available slots";
.addCase(createSlot.fulfilled, (state, action: PayloadAction<Slot>) => { })
state.loading = false; .addCase(createSlot.pending, (state) => {
state.slots.push(action.payload); state.loading = true;
}) })
.addCase(createSlot.rejected, (state, action) => { .addCase(
state.loading = false; createSlot.fulfilled,
state.error = action.payload || "Failed to create slot"; (state, action: PayloadAction<Slot>) => {
}) state.loading = false;
.addCase(updateSlot.pending, (state) => { state.slots.push(action.payload);
state.loading = true; }
}) )
.addCase(updateSlot.fulfilled, (state, action: PayloadAction<Slot>) => { .addCase(createSlot.rejected, (state, action) => {
state.loading = false; state.loading = false;
// Update the slot in the state with the updated data state.error = action.payload || "Failed to create slot";
const index = state.slots.findIndex((slot) => slot.id === action.payload.id); })
if (index !== -1) { .addCase(updateSlot.pending, (state) => {
state.slots[index] = action.payload; state.loading = true;
} })
}) .addCase(
.addCase(updateSlot.rejected, (state, action) => { updateSlot.fulfilled,
state.loading = false; (state, action: PayloadAction<Slot>) => {
state.error = action.payload || "Failed to update slot"; state.loading = false;
}) // Update the slot in the state with the updated data
.addCase(deleteSlot.pending, (state) => { const index = state.slots.findIndex(
state.loading = true; (slot) => slot.id === action.payload.id
}) );
.addCase(deleteSlot.fulfilled, (state, action: PayloadAction<number>) => { if (index !== -1) {
state.loading = false; state.slots[index] = action.payload;
// Remove the deleted slot from the state }
state.slots = state.slots.filter((slot) => slot.id !== action.payload); }
}) )
.addCase(deleteSlot.rejected, (state, action) => { .addCase(updateSlot.rejected, (state, action) => {
state.loading = false; state.loading = false;
state.error = action.payload || "Failed to delete slot"; 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; export default slotSlice.reducer;

View file

@ -20,7 +20,8 @@ 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")); const EVSlotManagement = lazy(() => import("./pages/EVSlotManagement"));
const EvSlotList = lazy(() => import("./pages/EvSlotList")); const BookingList = lazy(() => import("./pages/BookingList"));
interface ProtectedRouteProps { interface ProtectedRouteProps {
@ -100,9 +101,9 @@ export default function AppRouter() {
} }
/> />
<Route <Route
path="slot-list" path="booking-list"
element={ element={
<ProtectedRoute component={<EvSlotList />} /> <ProtectedRoute component={<BookingList />} />
} }
/> />
</Route> </Route>