UserBooking and slots api implementation
This commit is contained in:
parent
61963d6fe2
commit
bbb980c042
|
@ -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
|
||||||
|
|
|
@ -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.");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
110
src/pages/EvSlotList/index.tsx
Normal file
110
src/pages/EvSlotList/index.tsx
Normal 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}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue