UserBooking and slots api implementation

This commit is contained in:
jaanvi 2025-03-21 18:07:55 +05:30
parent 61963d6fe2
commit bbb980c042
11 changed files with 633 additions and 591 deletions

View file

@ -1,3 +1,4 @@
import React, { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { import {
Box, Box,
@ -5,13 +6,22 @@ import {
Typography, Typography,
Modal, Modal,
IconButton, IconButton,
InputAdornment, MenuItem,
Select,
InputLabel,
FormControl,
TextField,
} from "@mui/material"; } from "@mui/material";
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
import { Visibility, VisibilityOff } from "@mui/icons-material";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { addBooking, bookingList } from "../../redux/slices/bookSlice.ts"; import {
import React, { useState } from "react"; addBooking,
bookingList,
getCarNames,
getCarPorts,
} from "../../redux/slices/bookSlice";
import { AppDispatch } from "../../redux/store/store";
import { toast } from "sonner";
import { import {
CustomIconButton, CustomIconButton,
CustomTextField, CustomTextField,
@ -24,19 +34,35 @@ export default function AddBookingModal({
open: boolean; open: boolean;
handleClose: () => void; handleClose: () => void;
}) { }) {
const dispatch = useDispatch(); const dispatch = useDispatch<AppDispatch>();
const { const {
control, control,
register, register,
handleSubmit, handleSubmit,
formState: { errors }, formState: { errors },
reset, reset,
watch,
} = useForm(); } = useForm();
const [carNames, setCarNames] = useState<any[]>([]); // To hold the car names
const [carPorts, setCarPorts] = useState<any[]>([]); // To hold the car ports
const [showPassword, setShowPassword] = useState(false); useEffect(() => {
// Fetch car names and car ports
dispatch(getCarNames()).then((response: any) => {
const fetchedCarNames = response.payload || [];
setCarNames(fetchedCarNames);
});
const togglePasswordVisibility = () => setShowPassword((prev) => !prev); dispatch(getCarPorts()).then((response: any) => {
const fetchedCarPorts = response.payload || [];
setCarPorts(fetchedCarPorts);
});
// Fetch the bookings after this
dispatch(bookingList());
}, [dispatch]);
// Get today's date in yyyy-mm-dd format
const today = new Date().toISOString().split("T")[0];
const onSubmit = async (data: any) => { const onSubmit = async (data: any) => {
const bookingData = { const bookingData = {
@ -44,6 +70,9 @@ export default function AddBookingModal({
date: data.date, date: data.date,
startTime: data.startTime, startTime: data.startTime,
endTime: data.endTime, endTime: data.endTime,
carName: data.carName,
carNumber: data.carNumber,
carPort: data.carPort,
}; };
try { try {
@ -53,6 +82,7 @@ export default function AddBookingModal({
reset(); // Reset form fields reset(); // Reset form fields
} catch (error) { } catch (error) {
console.error("Error adding booking:", error); console.error("Error adding booking:", error);
toast.error("Failed to add booking.");
} }
}; };
@ -60,9 +90,7 @@ export default function AddBookingModal({
<Modal <Modal
open={open} open={open}
onClose={(e, reason) => { onClose={(e, reason) => {
if (reason === "backdropClick") { if (reason === "backdropClick") return;
return;
}
handleClose(); handleClose();
}} }}
aria-labelledby="add-booking-modal" aria-labelledby="add-booking-modal"
@ -80,7 +108,6 @@ export default function AddBookingModal({
borderRadius: 2, borderRadius: 2,
}} }}
> >
{/* Header */}
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
@ -98,8 +125,9 @@ export default function AddBookingModal({
<Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} /> <Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
{/* Station ID Input */} {/* Station ID and Car Name */}
<Box sx={{ display: "flex", gap: 2, mb: 2 }}> <Box sx={{ display: "flex", gap: 2, mb: 2 }}>
{/* Station ID */}
<Box sx={{ flex: 1 }}> <Box sx={{ flex: 1 }}>
<Typography variant="body2" fontWeight={500}> <Typography variant="body2" fontWeight={500}>
Station ID Station ID
@ -119,41 +147,134 @@ export default function AddBookingModal({
})} })}
/> />
</Box> </Box>
</Box>
{/* Date Input */}
<Box sx={{ display: "flex", gap: 2, mb: 2 }}>
<Box sx={{ flex: 1 }}> <Box sx={{ flex: 1 }}>
<Typography variant="body2" fontWeight={500}> <Typography variant="body2" fontWeight={500}>
Date Date
</Typography> </Typography>
<CustomTextField <CustomTextField
fullWidth fullWidth
placeholder="Select Booking Date"
size="small"
type="date" type="date"
size="small"
error={!!errors.date} error={!!errors.date}
helperText={ helperText={
errors.date ? errors.date.message : "" errors.date ? errors.date.message : ""
} }
{...register("date", { {...register("date", {
required: "Date is required", required: "Date is required",
validate: (value) =>
value >= today ||
"Date cannot be in the past",
})} })}
InputLabelProps={{
shrink: true,
}}
// Setting minimum date to today
inputProps={{ min: today }}
/> />
</Box> </Box>
</Box> </Box>
{/* Start Time Input */} {/* Car Port and Date */}
<Box sx={{ display: "flex", gap: 2, mb: 2 }}> <Box sx={{ display: "flex", gap: 2, mb: 2 }}>
{/* Car Name */}
<Box sx={{ flex: 1 }}>
<Typography variant="body2" fontWeight={500}>
Car Name
</Typography>
<Controller
control={control}
name="carName"
render={({ field }) => (
<FormControl
fullWidth
size="small"
error={!!errors.carName}
>
<InputLabel>Car Name</InputLabel>
<Select
{...field}
label="Car Name"
defaultValue=""
>
{carNames.map((car, index) => (
<MenuItem
key={car.id}
value={car.id}
>
{car.name}
</MenuItem>
))}
</Select>
{errors.carName && (
<Typography
color="error"
variant="body2"
sx={{ mt: 1 }}
>
{errors.carName.message}
</Typography>
)}
</FormControl>
)}
rules={{ required: "Car Name is required" }}
/>
</Box>
{/* Car Port */}
<Box sx={{ flex: 1 }}>
<Typography variant="body2" fontWeight={500}>
Car Port
</Typography>
<Controller
control={control}
name="carPort"
render={({ field }) => (
<FormControl
fullWidth
size="small"
error={!!errors.carPort}
>
<InputLabel>Car Port</InputLabel>
<Select
{...field}
label="Car Port"
defaultValue=""
>
{carPorts.map((port, index) => (
<MenuItem
key={index}
value={port.chargeType}
>
{port.chargeType}
</MenuItem>
))}
</Select>
{errors.carPort && (
<Typography
color="error"
variant="body2"
sx={{ mt: 1 }}
>
{errors.carPort.message}
</Typography>
)}
</FormControl>
)}
rules={{ required: "Car Port is required" }}
/>
</Box>
</Box>
{/* Start Time and End Time */}
<Box sx={{ display: "flex", gap: 2, mb: 2 }}>
{/* Start Time */}
<Box sx={{ flex: 1 }}> <Box sx={{ flex: 1 }}>
<Typography variant="body2" fontWeight={500}> <Typography variant="body2" fontWeight={500}>
Start Time Start Time
</Typography> </Typography>
<CustomTextField <CustomTextField
fullWidth fullWidth
placeholder="Enter Start Time"
size="small"
type="time" type="time"
size="small"
error={!!errors.startTime} error={!!errors.startTime}
helperText={ helperText={
errors.startTime errors.startTime
@ -165,32 +286,45 @@ export default function AddBookingModal({
})} })}
/> />
</Box> </Box>
</Box>
{/* End Time Input */} {/* End Time */}
<Box sx={{ display: "flex", gap: 2, mb: 2 }}>
<Box sx={{ flex: 1 }}> <Box sx={{ flex: 1 }}>
<Typography variant="body2" fontWeight={500}> <Typography variant="body2" fontWeight={500}>
End Time End Time
</Typography> </Typography>
<CustomTextField <CustomTextField
fullWidth fullWidth
placeholder="Enter End Time"
size="small"
type="time" type="time"
size="small"
error={!!errors.endTime} error={!!errors.endTime}
helperText={ helperText={
errors.endTime ? errors.endTime.message : "" errors.endTime ? errors.endTime.message : ""
} }
{...register("endTime", { {...register("endTime", {
required: "End Time is required", required: "End Time is required",
validate: (value) => { })}
const startTime = watch("startTime"); />
if (value <= startTime) { </Box>
return "End time must be after start time."; </Box>
}
return true; {/* Car Number */}
}, <Box sx={{ display: "flex", gap: 2, mb: 2 }}>
<Box sx={{ flex: 1 }}>
<Typography variant="body2" fontWeight={500}>
Car Number
</Typography>
<CustomTextField
fullWidth
placeholder="Enter Car Number"
size="small"
error={!!errors.carNumber}
helperText={
errors.carNumber
? errors.carNumber.message
: ""
}
{...register("carNumber", {
required: "Car Number is required",
})} })}
/> />
</Box> </Box>
@ -201,7 +335,7 @@ export default function AddBookingModal({
sx={{ sx={{
display: "flex", display: "flex",
justifyContent: "flex-end", justifyContent: "flex-end",
mt: 3, mt: 2,
}} }}
> >
<Button <Button

View file

@ -378,7 +378,5 @@ const AddUserModal: React.FC<AddUserModalProps> = ({
export default AddUserModal; export default AddUserModal;
function setValue(arg0: string, name: any) {
throw new Error("Function not implemented.");
}

View file

@ -1,316 +1,130 @@
import React, { useEffect, useState } from "react"; import React, { useState } from "react";
import { import {
Dialog, Dialog,
DialogActions, DialogActions,
DialogContent, DialogContent,
DialogTitle, DialogTitle,
Button, Button,
TextField,
Typography, Typography,
IconButton,
Box, Box,
Snackbar,
Stack,
Tabs,
Tab,
Card,
} from "@mui/material"; } from "@mui/material";
import { useForm } from "react-hook-form";
import { CustomTextField } from "../../components/AddEditUserModel/styled.css.tsx"; const AddSlotModal = ({ open, handleClose, handleAddSlot }: any) => {
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 { useDispatch, useSelector } from "react-redux";
import {
fetchAvailableSlots,
createSlot,
deleteSlot,
} from "../../redux/slices/slotSlice.ts";
const days = [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
];
interface AddSlotModalProps {
open: boolean;
onClose: () => void;
}
export default function AddSlotModal({ open, onClose }: AddSlotModalProps) {
const [selectedDay, setSelectedDay] = useState("Monday");
const [openingTime, setOpeningTime] = useState("");
const [closingTime, setClosingTime] = useState("");
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 dispatch = useDispatch();
// Fetch slots from the Redux state
const { const {
slots, register,
loading, handleSubmit,
error: apiError, reset,
} = useSelector((state: any) => state.slotReducer.slots); formState: { errors },
} = useForm();
const [isAvailable, setIsAvailable] = useState<boolean>(true); // Default is available
// useEffect(() => { // Get today's date in the format yyyy-mm-dd
// if (selectedDay) { const today = new Date().toISOString().split("T")[0];
// dispatch(fetchAvailableSlots(1)); // Replace with actual stationId if needed
// }
// }, [dispatch, selectedDay]);
const addSlot = (start: string, end: string) => { const onSubmit = (data: {
const selectedDateFormatted = selectedDate.format("YYYY-MM-DD"); date: string;
const startTime = start; startHour: string;
const endTime = end; endHour: string;
}) => {
dispatch( handleAddSlot({ ...data, isAvailable });
createSlot({ reset();
stationId: 1, handleClose();
date: selectedDateFormatted,
startHour: startTime,
endHour: endTime,
isAvailable: true,
})
);
};
const addBreak = (start: string, end: string) => {
if (!start || !end) {
setError("Break Start and End times are required.");
return;
}
setBreakTime([...breakTime, { start, end }]);
setError("");
};
const deleteSlot = (start: string, end: string) => {
const updatedSlots = slots[selectedDay].filter(
(slot: any) => !(slot.start === start && slot.end === end)
);
setSlots({
...slots,
[selectedDay]: updatedSlots,
});
};
const deleteBreak = (start: string, end: string) => {
const updatedBreaks = breakTime.filter(
(breakItem: any) =>
!(breakItem.start === start && breakItem.end === end)
);
setBreakTime(updatedBreaks);
};
const saveData = () => {
if (!openingTime || !closingTime) {
setError("Operating hours are required.");
return;
}
setSuccessMessage(
`Data for ${selectedDay} has been saved successfully!`
);
setError("");
};
const handleCloseSnackbar = () => {
setSuccessMessage(null);
}; };
return ( return (
<Dialog open={open} onClose={onClose} maxWidth="md" fullWidth> <Dialog open={open} onClose={handleClose}>
<DialogTitle align="center">EV Station Slot Management</DialogTitle> <DialogTitle>Add EV Slot</DialogTitle>
<DialogContent> <DialogContent>
{/* Date Picker */} <form onSubmit={handleSubmit(onSubmit)}>
<Box <TextField
sx={{ {...register("date", {
display: "flex", required: "Date is required",
justifyContent: "space-between", validate: (value) =>
mt: 3, value >= today || "Date cannot be in the past",
}} })}
> label="Date"
<Typography variant="h4" gutterBottom> type="date"
Select Date fullWidth
</Typography> margin="normal"
<LocalizationProvider dateAdapter={AdapterDayjs}> InputLabelProps={{
<DatePicker shrink: true,
value={selectedDate} }}
onChange={(newDate) => setSelectedDate(newDate)} error={!!errors.date}
renderInput={(props) => ( helperText={errors.date?.message}
<CustomTextField // Set the min value to today's date
{...props} inputProps={{ min: today }}
fullWidth />
label="Select Date" <TextField
InputProps={{ {...register("startHour", {
startAdornment: ( required: "Start hour is required",
<CalendarTodayRoundedIcon fontSize="small" /> })}
), label="Start Hour"
}} type="time"
/> fullWidth
)} margin="normal"
/> InputLabelProps={{
</LocalizationProvider> shrink: true,
</Box> }}
error={!!errors.startHour}
<Tabs helperText={errors.startHour?.message}
value={selectedDay} />
onChange={(event, newValue) => setSelectedDay(newValue)} <TextField
variant="scrollable" {...register("endHour", {
scrollButtons="auto" required: "End hour is required",
sx={{ mt: 3 }} })}
> label="End Hour"
{days.map((day) => ( type="time"
<Tab fullWidth
key={day} margin="normal"
label={day} InputLabelProps={{
value={day} shrink: true,
sx={{ }}
fontWeight: "bold", error={!!errors.endHour}
fontSize: "16px", helperText={errors.endHour?.message}
"&.Mui-selected": { />
{/* Availability Toggle */}
<Box
display="flex"
alignItems="center"
justifyContent="space-between"
gap={2}
>
<Button
onClick={() => setIsAvailable((prev) => !prev)}
variant={isAvailable ? "contained" : "outlined"}
color="primary"
>
Check Availability
</Button>
<Typography>
{isAvailable ? "Available" : "Not Available"}
</Typography>
</Box>
<DialogActions>
<Button onClick={handleClose} color="secondary">
Cancel
</Button>
<Button
type="submit"
sx={{
backgroundColor: "#52ACDF", backgroundColor: "#52ACDF",
color: "white", color: "white",
borderRadius: "8px", borderRadius: "8px",
}, width: "100px",
}} "&:hover": { backgroundColor: "#439BC1" },
/> }}
))} >
</Tabs> Add Booking
</Button>
{/* Operating Hours */}
<Card sx={{ mt: 2, p: 2 }}> </DialogActions>
<Typography variant="h6" gutterBottom> </form>
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>
{/* Slots */}
<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>
{/* Break Time */}
<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>
{/* Slots and Break Times Lists */}
{/* (Content for slots and breaks remains the same as in the original implementation) */}
{/* Success Snackbar */}
<Snackbar
open={!!successMessage}
autoHideDuration={6000}
onClose={handleCloseSnackbar}
message={successMessage}
/>
</DialogContent> </DialogContent>
<DialogActions>
<Button onClick={onClose} color="primary">
Cancel
</Button>
<Button onClick={saveData} color="primary">
Save
</Button>
</DialogActions>
</Dialog> </Dialog>
); );
} };
export default AddSlotModal;

View file

@ -11,7 +11,7 @@ import { adminList, deleteAdmin } from "../../redux/slices/adminSlice";
import { vehicleList, deleteVehicle } from "../../redux/slices/VehicleSlice"; import { vehicleList, deleteVehicle } from "../../redux/slices/VehicleSlice";
import { deleteManager, managerList } from "../../redux/slices/managerSlice"; import { deleteManager, managerList } from "../../redux/slices/managerSlice";
import { useDispatch } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { import {
Box, Box,
Button, Button,
@ -24,7 +24,7 @@ import {
} from "@mui/material"; } from "@mui/material";
import MoreHorizRoundedIcon from "@mui/icons-material/MoreHorizRounded"; import MoreHorizRoundedIcon from "@mui/icons-material/MoreHorizRounded";
import DeleteModal from "../Modals/DeleteModal"; import DeleteModal from "../Modals/DeleteModal";
import { AppDispatch } from "../../redux/store/store"; import { AppDispatch, RootState } from "../../redux/store/store";
import ViewModal from "../Modals/ViewModal"; import ViewModal from "../Modals/ViewModal";
import VehicleViewModal from "../Modals/VehicleViewModal"; import VehicleViewModal from "../Modals/VehicleViewModal";
@ -36,7 +36,10 @@ import UserViewModal from "../Modals/UserViewModal/index.tsx";
import { deleteUser, userList } from "../../redux/slices/userSlice.ts"; 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 {
deleteSlot,
fetchAvailableSlots,
} from "../../redux/slices/slotSlice.ts";
import { bookingList } from "../../redux/slices/bookSlice.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 }) => ({
@ -105,14 +108,17 @@ const CustomTable: React.FC<CustomTableProps> = ({
const [searchQuery, setSearchQuery] = React.useState(""); const [searchQuery, setSearchQuery] = React.useState("");
const [currentPage, setCurrentPage] = React.useState(1); const [currentPage, setCurrentPage] = React.useState(1);
const usersPerPage = 10; const usersPerPage = 10;
const { user, isLoading } = useSelector(
(state: RootState) => state?.profileReducer
);
const open = Boolean(anchorEl); const open = Boolean(anchorEl);
console.log("Rows", rows);
const handleClick = (event: React.MouseEvent<HTMLElement>, row: Row) => { const handleClick = (event: React.MouseEvent<HTMLElement>, row: Row) => {
setAnchorEl(event.currentTarget); setAnchorEl(event.currentTarget);
setSelectedRow(row); setSelectedRow(row);
setRowData(row); setRowData(row);
}; };
const handleClose = () => { const handleClose = () => {
setAnchorEl(null); setAnchorEl(null);
}; };
@ -125,9 +131,13 @@ const CustomTable: React.FC<CustomTableProps> = ({
}; };
const handleDeleteButton = (id: string | undefined) => { const handleDeleteButton = (id: string | undefined) => {
if (!id) console.error("ID not found", id); if (!id) {
console.error("ID not found", id);
return;
}
switch (tableType) { switch (tableType) {
case "admin": case "admin":
dispatch(deleteAdmin(id || "")); dispatch(deleteAdmin(id || ""));
break; break;
@ -143,9 +153,9 @@ const CustomTable: React.FC<CustomTableProps> = ({
case "station": case "station":
dispatch(deleteStation(id || "")); dispatch(deleteStation(id || ""));
break; break;
// case "booking": case "slots":
// dispatch(deleteBooking(id || "")); dispatch(deleteSlot(id || ""));
// break; break;
default: default:
console.error("Unknown table type:", tableType); console.error("Unknown table type:", tableType);
return; return;
@ -172,6 +182,9 @@ const CustomTable: React.FC<CustomTableProps> = ({
case "booking": case "booking":
dispatch(bookingList()); dispatch(bookingList());
break; break;
case "slots":
dispatch(fetchAvailableSlots());
break;
default: default:
console.error("Unknown table type:", tableType); console.error("Unknown table type:", tableType);
return; return;
@ -240,6 +253,8 @@ const CustomTable: React.FC<CustomTableProps> = ({
? "Charging Station" ? "Charging Station"
: tableType === "booking" : tableType === "booking"
? "Booking" ? "Booking"
: tableType === "slots"
? "Slot"
: "List"} : "List"}
</Typography> </Typography>
@ -290,33 +305,37 @@ const CustomTable: React.FC<CustomTableProps> = ({
width: "100%", width: "100%",
}} }}
> >
<Button {!(user?.userType === "user" && tableType === "slots") && (
sx={{ <Button
backgroundColor: "#52ACDF", sx={{
color: "white", backgroundColor: "#52ACDF",
borderRadius: "8px", color: "white",
width: "184px", borderRadius: "8px",
"&:hover": { backgroundColor: "#439BC1" }, width: "184px",
}} "&:hover": { backgroundColor: "#439BC1" },
onClick={() => handleClickOpen()} }}
> onClick={() => handleClickOpen()}
Add{" "} >
{tableType === "admin" Add{" "}
? "Admin" {tableType === "admin"
: tableType === "role" ? "Admin"
? "Role" : tableType === "role"
: tableType === "user" ? "Role"
? "User" : tableType === "user"
: tableType === "manager" ? "User"
? "Manager" : tableType === "manager"
: tableType === "vehicle" ? "Manager"
? "Vehicle" : tableType === "vehicle"
: tableType === "station" ? "Vehicle"
? "Charging Station" : tableType === "station"
: tableType === "booking" ? "Charging Station"
? "Booking" : tableType === "booking"
: "Item"} ? "Booking"
</Button> : tableType === "slots"
? "Slot"
: "Item"}
</Button>
)}
</Box> </Box>
<IconButton <IconButton

View file

@ -60,16 +60,26 @@ 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" && { // userRole === "manager" && {
text: "Add Slots", // text: "Add Slots",
icon: <ManageAccountsOutlinedIcon />, // icon: <ManageAccountsOutlinedIcon />,
url: "/panel/EVslots", // Placeholder for now // url: "/panel/EVslots", // Placeholder for now
}, // },
userRole === "user" && { userRole === "user" && {
text: "Bookings", text: "Bookings",
icon: <ManageAccountsOutlinedIcon />, icon: <ManageAccountsOutlinedIcon />,
url: "/panel/booking-list", // Placeholder for now url: "/panel/booking-list", // Placeholder for now
}, },
userRole === "manager" && {
text: "Available Slots",
icon: <ManageAccountsOutlinedIcon />,
url: "/panel/slot-list", // Placeholder for now
},
userRole === "user" && {
text: "Available Slots",
icon: <ManageAccountsOutlinedIcon />,
url: "/panel/slot-list", // Placeholder for now
},
]; ];
const filteredMenuItems = baseMenuItems.filter(Boolean); const filteredMenuItems = baseMenuItems.filter(Boolean);

View file

@ -7,7 +7,7 @@ import { useForm } from "react-hook-form";
import { addBooking, bookingList } from "../../redux/slices/bookSlice"; import { addBooking, bookingList } from "../../redux/slices/bookSlice";
import AddBookingModal from "../../components/AddBookingModal"; import AddBookingModal from "../../components/AddBookingModal";
export default function ManagerList() { export default function BookingList() {
const [addModalOpen, setAddModalOpen] = useState<boolean>(false); const [addModalOpen, setAddModalOpen] = useState<boolean>(false);
const [editModalOpen, setEditModalOpen] = useState<boolean>(false); const [editModalOpen, setEditModalOpen] = useState<boolean>(false);
const [editRow, setEditRow] = useState<any>(null); const [editRow, setEditRow] = useState<any>(null);
@ -22,6 +22,7 @@ export default function ManagerList() {
); );
const users = useSelector((state: RootState) => state?.profileReducer.user); const users = useSelector((state: RootState) => state?.profileReducer.user);
useEffect(() => { useEffect(() => {
dispatch(bookingList()); dispatch(bookingList());
}, [dispatch]); }, [dispatch]);
@ -30,6 +31,7 @@ export default function ManagerList() {
setRowData(null); setRowData(null);
setAddModalOpen(true); setAddModalOpen(true);
}; };
const handleCloseModal = () => { const handleCloseModal = () => {
setAddModalOpen(false); setAddModalOpen(false);
setEditModalOpen(false); setEditModalOpen(false);
@ -37,44 +39,57 @@ export default function ManagerList() {
reset(); reset();
}; };
// Updated handleAddBooking function to handle the added fields
const handleAddBooking = async (data: { const handleAddBooking = async (data: {
stationId: string; stationId: string;
date: string; date: string;
startTime: string; startTime: string;
endTime: string; endTime: string;
carName: string;
carNumber: string;
carPort: string;
}) => { }) => {
try { try {
// Dispatch addBooking with new data
await dispatch(addBooking(data)); await dispatch(addBooking(data));
await dispatch(bookingList()); await dispatch(bookingList());
handleCloseModal(); // Close the modal after adding the booking
handleCloseModal(); // Close the modal
} catch (error) { } catch (error) {
console.error("Error adding manager", error); console.error("Error adding booking", error);
} }
}; };
// Table Columns for bookings
const categoryColumns: Column[] = [ const categoryColumns: Column[] = [
{ id: "srno", label: "Sr No" }, { id: "srno", label: "Sr No" },
{ id: "name", label: "User Name" }, { id: "name", label: "User Name" },
// { id: "stationName", label: "Station Name" },
// { id: "registeredAddress", label: "Station Location" },
{ id: "stationId", label: "Station Id" }, { id: "stationId", label: "Station Id" },
{ id: "stationName", label: "Station Name" },
{ id: "stationLocation", label: "Station Location" },
{ id: "date", label: "Date" }, { id: "date", label: "Date" },
{ id: "startTime", label: "Start Time" }, { id: "startTime", label: "Start Time" },
{ id: "endTime", label: "End Time" }, { id: "endTime", label: "End Time" },
{ id: "carName", label: "Vehicle Name" },
{ id: "carNumber", label: "Vehicle Number" },
{ id: "carPort", label: "Charging Port" },
{ id: "action", label: "Action", align: "center" }, { id: "action", label: "Action", align: "center" },
]; ];
// Table Rows for bookings
// Table Rows for bookings
const categoryRows = bookings?.length const categoryRows = bookings?.length
? bookings?.map(function ( ? bookings?.map(function (
booking: { booking: {
id: number; id: number;
name: string;
stationId: string; stationId: string;
stationName: string;
stationLocation: string;
date: string; date: string;
startTime: string; startTime: string;
endTime: string; endTime: string;
carName: string;
carNumber: string;
carPort: string;
}, },
index: number index: number
) { ) {
@ -83,13 +98,17 @@ export default function ManagerList() {
srno: index + 1, srno: index + 1,
name: users?.name ?? "NA", name: users?.name ?? "NA",
stationId: booking?.stationId ?? "NA", stationId: booking?.stationId ?? "NA",
stationName: booking?.stationName ?? "NA",
stationLocation: booking?.stationLocation ?? "NA",
date: booking?.date ?? "NA", date: booking?.date ?? "NA",
startTime: booking?.startTime ?? "NA", startTime: booking?.startTime ?? "NA",
endTime: booking?.endTime ?? "NA", endTime: booking?.endTime ?? "NA",
carName: booking?.carName ?? "NA", // Directly access carName
carNumber: booking?.carNumber ?? "NA", // Directly access carNumber
carPort: booking?.carPort ?? "NA",
}; };
}) })
: []; : [];
// console.log("Rowa", categoryRows);
return ( return (
<> <>
@ -105,11 +124,11 @@ export default function ManagerList() {
tableType="booking" tableType="booking"
handleClickOpen={handleClickOpen} handleClickOpen={handleClickOpen}
/> />
{/* Add Manager Modal */} {/* Add Booking Modal */}
<AddBookingModal <AddBookingModal
open={addModalOpen} open={addModalOpen}
handleClose={handleCloseModal} handleClose={handleCloseModal}
handleAddManager={handleAddBooking} handleAddBooking={handleAddBooking} // Passing the handleAddBooking function
/> />
</> </>
); );

View file

@ -4,12 +4,9 @@ import {
Tab, Tab,
Box, Box,
Card, Card,
CardContent,
Button,
Typography, Typography,
IconButton, IconButton,
Stack, Button,
Snackbar,
} from "@mui/material"; } from "@mui/material";
import { CustomTextField } from "../../components/AddEditUserModel/styled.css.tsx"; import { CustomTextField } from "../../components/AddEditUserModel/styled.css.tsx";
import AddCircleIcon from "@mui/icons-material/AddCircle"; import AddCircleIcon from "@mui/icons-material/AddCircle";
@ -38,23 +35,18 @@ const days = [
export default function EVSlotManagement() { export default function EVSlotManagement() {
const [selectedDay, setSelectedDay] = useState("Monday"); const [selectedDay, setSelectedDay] = useState("Monday");
const [openingTime, setOpeningTime] = useState("");
const [closingTime, setClosingTime] = useState("");
const [breakTime, setBreakTime] = useState<any>([]); const [breakTime, setBreakTime] = useState<any>([]);
const [error, setError] = useState<string>(""); const [error, setError] = useState<string>("");
const [successMessage, setSuccessMessage] = useState<string | null>(null);
const [selectedDate, setSelectedDate] = useState<dayjs.Dayjs | null>( const [selectedDate, setSelectedDate] = useState<dayjs.Dayjs | null>(
dayjs() dayjs()
); );
const navigate = useNavigate(); const navigate = useNavigate();
const dispatch = useDispatch(); const dispatch = useDispatch();
const { slots, availableSlots } = useSelector( const { availableSlots } = useSelector(
(state: RootState) => state?.slotReducer (state: RootState) => state?.slotReducer
); );
console.log("availableSlots", availableSlots);
useEffect(() => { useEffect(() => {
dispatch(fetchAvailableSlots()); dispatch(fetchAvailableSlots());
}, [dispatch]); }, [dispatch]);
@ -63,64 +55,37 @@ export default function EVSlotManagement() {
navigate("/panel/dashboard"); navigate("/panel/dashboard");
}; };
// Add Slot
const addSlot = (start: string, end: string) => { const addSlot = (start: string, end: string) => {
const selectedDateFormatted = selectedDate?.format("YYYY-MM-DD"); const selectedDateFormatted = selectedDate?.format("YYYY-MM-DD");
const startTime = start;
const endTime = end;
dispatch( dispatch(
createSlot({ createSlot({
date: selectedDateFormatted, date: selectedDateFormatted,
startHour: startTime, startHour: start,
endHour: endTime, endHour: end,
isAvailable: true, isAvailable: true,
}) })
); ).then(() => {
// dispatch(fetchAvailableSlots()); dispatch(fetchAvailableSlots());
});
}; };
// Delete slot function that dispatches deleteSlot // Delete slot
const addBreak = (start: string, end: string) => { const handleDeleteSlot = (slotId: string) => {
if (!start || !end) {
setError("Break Start and End times are required.");
return;
}
setBreakTime([...breakTime, { start, end }]);
setError("");
};
const handleDeleteSlot = (slotId: number) => {
dispatch(deleteSlot(slotId)); dispatch(deleteSlot(slotId));
dispatch(fetchAvailableSlots()); dispatch(fetchAvailableSlots());
}; };
// Delete break function // Filter the available slots for the selected date's day
const deleteBreak = (start: string, end: string) => { const filteredSlots = availableSlots.filter((slot: any) => {
const updatedBreaks = breakTime.filter( // Get the day of the week for each slot
(breakItem: any) => const slotDate = dayjs(slot.date);
!(breakItem.start === start && breakItem.end === end) const slotDay = slotDate.format("dddd"); // Get the day of the week (e.g., "Monday")
);
setBreakTime(updatedBreaks);
};
// Save function // Compare the selected day with the slot's day
const saveData = () => { return slotDay === selectedDay; // Only show slots that match the selected day
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
};
const handleCloseSnackbar = () => {
setSuccessMessage(null);
};
return ( return (
<Box sx={{ maxWidth: 600, mx: "auto", p: 2 }}> <Box sx={{ maxWidth: 600, mx: "auto", p: 2 }}>
@ -219,46 +184,13 @@ export default function EVSlotManagement() {
</Typography> </Typography>
)} )}
</Card> </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 }}> <Card sx={{ mt: 2, p: 2 }}>
<Typography variant="h6" gutterBottom> <Typography variant="h6" gutterBottom>
Slots for {selectedDay} Slots for {selectedDay}
</Typography> </Typography>
{Array.isArray(availableSlots) && availableSlots.length ? ( {filteredSlots.length ? (
availableSlots.map((slot: any, index: number) => ( filteredSlots.map((slot: any, index: number) => (
<Box <Box
key={index} key={index}
sx={{ sx={{
@ -284,75 +216,6 @@ export default function EVSlotManagement() {
<Typography>No slots added for {selectedDay}</Typography> <Typography>No slots added for {selectedDay}</Typography>
)} )}
</Card> </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>
<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> </Box>
); );
} }

View file

@ -0,0 +1,110 @@
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 { createSlot, fetchAvailableSlots } from "../../redux/slices/slotSlice";
import AddSlotModal from "../../components/AddSlotModal";
import dayjs from "dayjs";
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 availableSlots = useSelector(
(state: RootState) => state?.slotReducer.availableSlots
);
useEffect(() => {
dispatch(fetchAvailableSlots());
}, [dispatch]);
const handleClickOpen = () => {
setRowData(null);
setAddModalOpen(true);
};
const handleCloseModal = () => {
setAddModalOpen(false);
setEditModalOpen(false);
setRowData(null);
reset();
};
const handleAddSlot = async (data: {
date: string;
startTime: string;
endTime: string;
isAvailable: boolean;
}) => {
try {
await dispatch(createSlot(data));
await dispatch(fetchAvailableSlots());
handleCloseModal();
} catch (error) {
console.error("Error adding slot", error);
}
};
const slotColumns: Column[] = [
{ id: "srno", label: "Sr No" },
{ id: "name", label: "Station Name" },
{ id: "stationId", label: "Station Id" },
{ id: "date", label: "Date" },
{ id: "startTime", label: "Start Time" },
{ id: "endTime", label: "End Time" },
{ id: "isAvailable", label: "Is Available", align: "center" },
{ id: "action", label: "Action", align: "center" },
];
// Make sure dayjs is imported
const slotRows = availableSlots?.length
? availableSlots.map((slot, index) => {
const formattedDate = dayjs(slot?.date).format("YYYY-MM-DD");
const startTime = dayjs(slot?.startTime).format("HH:mm");
const endTime = dayjs(slot?.endTime).format("HH:mm");
console.log("first", startTime);
return {
srno: index + 1,
id: slot?.id ?? "NA",
stationId: slot?.stationId ?? "NA",
name: slot?.ChargingStation?.name ?? "NA",
date: formattedDate ?? "NA",
startTime: startTime ?? "NA",
endTime: endTime ?? "NA",
isAvailable: slot?.isAvailable ? "Yes" : "No",
};
})
: [];
console.log("Rows",slotRows)
return (
<>
<CustomTable
columns={slotColumns}
rows={slotRows}
setDeleteModal={setDeleteModal}
deleteModal={deleteModal}
setViewModal={setViewModal}
viewModal={viewModal}
setRowData={setRowData}
setModalOpen={() => setEditModalOpen(true)}
tableType="slots"
handleClickOpen={handleClickOpen}
/>
<AddSlotModal
open={addModalOpen}
handleClose={handleCloseModal}
handleAddSlot={handleAddSlot}
/>
</>
);
}

View file

@ -2,27 +2,75 @@ import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
import http from "../../lib/https"; import http from "../../lib/https";
import { toast } from "sonner"; import { toast } from "sonner";
// Define the structure for carDetails
interface CarDetails {
name: string;
number: string;
model: string;
}
interface Booking { interface Booking {
id: number; id: number;
name:string; slotId: number;
stationId: string; stationId: string;
stationName: string;
stationLocation: string;
date: string; date: string;
startTime: string; startTime: string;
endTime: string; endTime: string;
carDetails: CarDetails;
} }
interface BookingState { interface BookingState {
bookings: Booking[]; bookings: Booking[];
loading: boolean; loading: boolean;
error: string | null; error: string | null;
carNames: string[]; // For car names
carPorts: string[]; // For car ports
} }
const initialState: BookingState = { const initialState: BookingState = {
bookings: [], bookings: [],
loading: false, loading: false,
error: null, error: null,
carNames: [], // Initialize carNames
carPorts: [], // Initialize carPorts
}; };
// Redux slice for fetching car names and ports
export const getCarNames = createAsyncThunk<
string[],
void,
{ rejectValue: string }
>("fetchCarNames", async (_, { rejectWithValue }) => {
try {
const response = await http.get("/get-vehicle-dropdown");
return response.data.data; // Adjust based on actual API response
} catch (error: any) {
return rejectWithValue(
error?.response?.data?.message || "An error occurred"
);
}
});
export const getCarPorts = createAsyncThunk<
string[],
void,
{ rejectValue: string }
>("fetchCarPorts", async (_, { rejectWithValue }) => {
try {
const response = await http.get("/get-vehicle-port-dropdown");
return response.data.data; // Adjust based on actual API response
} catch (error: any) {
return rejectWithValue(
error?.response?.data?.message || "An error occurred"
);
}
});
// Fetch booking list
export const bookingList = createAsyncThunk< export const bookingList = createAsyncThunk<
Booking, Booking[],
void, void,
{ rejectValue: string } { rejectValue: string }
>("fetchBooking", async (_, { rejectWithValue }) => { >("fetchBooking", async (_, { rejectWithValue }) => {
@ -31,18 +79,20 @@ export const bookingList = createAsyncThunk<
if (!token) throw new Error("No token found"); if (!token) throw new Error("No token found");
const response = await http.get("/user-bookings"); const response = await http.get("/user-bookings");
console.log("API Response:", response);
if (!response.data?.data) throw new Error("Invalid API response"); if (!response.data?.data) throw new Error("Invalid API response");
return response.data.data; return response.data.data; // Updated to access data
} catch (error: any) { } catch (error: any) {
toast.error("Error Fetching User Booking List" + error); toast.error("Error Fetching User Booking List: " + error.message);
return rejectWithValue( return rejectWithValue(
error?.response?.data?.message || "An error occurred" error?.response?.data?.message || "An error occurred"
); );
} }
}); });
// Add a new booking
export const addBooking = createAsyncThunk< export const addBooking = createAsyncThunk<
Booking, Booking,
{ {
@ -50,13 +100,20 @@ export const addBooking = createAsyncThunk<
date: string; date: string;
startTime: string; startTime: string;
endTime: string; endTime: string;
carName: string;
carNumber: string;
carPort: string;
}, },
{ rejectValue: string } { rejectValue: string }
>("/AddBooking", async (data, { rejectWithValue }) => { >("/AddBooking", async (data, { rejectWithValue }) => {
try { try {
const response = await http.post("/book-slot", data); const response = await http.post("/book-slot", data);
return response.data; toast.success("EV Booked Successfully!");
return response.data.data; // Adjust to match the new API response structure
} catch (error: any) { } catch (error: any) {
toast.error(
"The requested time slot doesn't fall within any available slot "
);
return rejectWithValue( return rejectWithValue(
error.response?.data?.message || "An error occurred" error.response?.data?.message || "An error occurred"
); );
@ -82,23 +139,33 @@ const bookSlice = createSlice({
) )
.addCase(bookingList.rejected, (state, action) => { .addCase(bookingList.rejected, (state, action) => {
state.loading = false; state.loading = false;
state.error = action.error.message || "Failed to fetch users"; state.error =
action.error.message || "Failed to fetch bookings";
}) })
.addCase(addBooking.pending, (state) => { .addCase(addBooking.pending, (state) => {
state.loading = true; state.loading = true;
// state.error = null;
}) })
.addCase( .addCase(
addBooking.fulfilled, addBooking.fulfilled,
(state, action: PayloadAction<Booking>) => { (state, action: PayloadAction<Booking>) => {
state.loading = false; state.loading = false;
state.bookings.push(action.payload); state.bookings.push(action.payload); // Add new booking to state
}
)
.addCase(addBooking.rejected, (state) => {
state.loading = false;
})
// Add case reducers for fetching car names and ports
.addCase(
getCarNames.fulfilled,
(state, action: PayloadAction<string[]>) => {
state.carNames = action.payload;
} }
) )
.addCase( .addCase(
addBooking.rejected, getCarPorts.fulfilled,
(state, action: PayloadAction<string | undefined>) => { (state, action: PayloadAction<string[]>) => {
state.loading = false; state.carPorts = action.payload;
} }
); );
}, },

View file

@ -4,11 +4,14 @@ import { toast } from "sonner";
// Define TypeScript types // Define TypeScript types
interface Slot { interface Slot {
id: number; id: string;
stationId:string;
date: string; date: string;
startHour: Date; startTime: string;
endHour: Date; endTime: string;
isAvailable: boolean; isAvailable: boolean;
ChargingStation: { name: string };
} }
interface SlotState { interface SlotState {
@ -52,42 +55,39 @@ export const createSlot = createAsyncThunk<
Slot, Slot,
{ {
date: string; date: string;
startHour: string; startTime: string;
endHour: string; endTime: string;
isAvailable: boolean; isAvailable: boolean;
}, },
{ rejectValue: string } { rejectValue: string }
>( >("slots/createSlot", async (payload, { rejectWithValue }) => {
"slots/createSlot", try {
async (payload, { rejectWithValue }) => { // const payload = {
try { // date,
// const payload = { // startHour,
// date, // endHour,
// startHour, // isAvailable,
// endHour, // };
// isAvailable,
// };
const token = localStorage?.getItem("authToken"); const token = localStorage?.getItem("authToken");
if (!token) throw new Error("No token found"); if (!token) throw new Error("No token found");
const response = await http.post("/create-slot", payload); const response = await http.post("/create-slot", payload);
toast.success("Slot created successfully"); toast.success("Slot created successfully");
return response.data.data; return response.data.data;
} catch (error: any) { } catch (error: any) {
// Show error message // Show error message
toast.error("Error creating slot: " + error?.message); toast.error("Error creating slot: " + error?.message);
// Return a detailed error message if possible // Return a detailed error message if possible
return rejectWithValue( return rejectWithValue(
error.response?.data?.message || "An error occurred" error.response?.data?.message || "An error occurred"
); );
}
} }
); });
// Update Slot details // Update Slot details
export const updateSlot = createAsyncThunk< export const updateSlot = createAsyncThunk<
Slot, 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 }) => {
@ -104,8 +104,8 @@ export const updateSlot = createAsyncThunk<
}); });
export const deleteSlot = createAsyncThunk< export const deleteSlot = createAsyncThunk<
number, // Return type (id of deleted slot) string, // Return type (id of deleted slot)
number, string,
{ rejectValue: string } { rejectValue: string }
>("slots/deleteSlot", async (id, { rejectWithValue }) => { >("slots/deleteSlot", async (id, { rejectWithValue }) => {
try { try {
@ -181,12 +181,18 @@ const slotSlice = createSlice({
}) })
.addCase( .addCase(
deleteSlot.fulfilled, deleteSlot.fulfilled,
(state, action: PayloadAction<number>) => { (state, action: PayloadAction<string>) => {
state.loading = false; state.loading = false;
// Remove the deleted slot from the state // Ensure we're filtering the correct array (availableSlots)
state.slots = state.slots.filter( state.availableSlots = state.availableSlots.filter(
(slot) => slot.id !== action.payload (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) => { .addCase(deleteSlot.rejected, (state, action) => {

View file

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