dev-jaanvi #1
|
@ -1,4 +1,152 @@
|
||||||
import React, { useState } from "react";
|
// import React, { useState } from "react";
|
||||||
|
// import {
|
||||||
|
// Dialog,
|
||||||
|
// DialogActions,
|
||||||
|
// DialogContent,
|
||||||
|
// DialogTitle,
|
||||||
|
// Button,
|
||||||
|
// TextField,
|
||||||
|
// Typography,
|
||||||
|
// Box,
|
||||||
|
// } from "@mui/material";
|
||||||
|
// import { useForm } from "react-hook-form";
|
||||||
|
|
||||||
|
// const AddSlotModal = ({ open, handleClose, handleAddSlot }: any) => {
|
||||||
|
// const {
|
||||||
|
// register,
|
||||||
|
// handleSubmit,
|
||||||
|
// reset,
|
||||||
|
// formState: { errors },
|
||||||
|
// } = useForm();
|
||||||
|
// const [isAvailable, setIsAvailable] = useState<boolean>(true); // Default is available
|
||||||
|
|
||||||
|
// // Get today's date in the format yyyy-mm-dd
|
||||||
|
// const today = new Date().toISOString().split("T")[0];
|
||||||
|
|
||||||
|
// const onSubmit = (data: {
|
||||||
|
// date: string;
|
||||||
|
// startHour: string;
|
||||||
|
// endHour: string;
|
||||||
|
// }) => {
|
||||||
|
// handleAddSlot({ ...data, isAvailable });
|
||||||
|
// reset();
|
||||||
|
// handleClose();
|
||||||
|
// };
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <Dialog open={open} onClose={handleClose}>
|
||||||
|
// <DialogTitle>Add EV Slot</DialogTitle>
|
||||||
|
// <DialogContent>
|
||||||
|
// <form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
// <Typography variant="body2" fontWeight={500}>
|
||||||
|
// Date
|
||||||
|
// </Typography>
|
||||||
|
// <TextField
|
||||||
|
// {...register("date", {
|
||||||
|
// required: "Date is required",
|
||||||
|
// validate: (value) =>
|
||||||
|
// value >= today || "Date cannot be in the past",
|
||||||
|
// })}
|
||||||
|
// // label="Date"
|
||||||
|
// // sx={{ marginTop: 1 }}
|
||||||
|
// type="date"
|
||||||
|
// fullWidth
|
||||||
|
// margin="normal"
|
||||||
|
// slotProps={{
|
||||||
|
// inputLabel: {
|
||||||
|
// shrink: true,
|
||||||
|
// },
|
||||||
|
// }}
|
||||||
|
// error={!!errors.date}
|
||||||
|
// helperText={errors.date?.message}
|
||||||
|
// // Set the min value to today's date
|
||||||
|
// inputProps={{ min: today }}
|
||||||
|
// />
|
||||||
|
// <Typography variant="body2" fontWeight={500}>
|
||||||
|
// Start Hour
|
||||||
|
// </Typography>
|
||||||
|
// <TextField
|
||||||
|
// {...register("startHour", {
|
||||||
|
// required: "Start hour is required",
|
||||||
|
// })}
|
||||||
|
// // label="Start Hour"
|
||||||
|
// type="time"
|
||||||
|
// // sx={{ marginTop: 1 }}
|
||||||
|
// fullWidth
|
||||||
|
// margin="normal"
|
||||||
|
// slotProps={{
|
||||||
|
// inputLabel: {
|
||||||
|
// shrink: true,
|
||||||
|
// },
|
||||||
|
// }}
|
||||||
|
// error={!!errors.startHour}
|
||||||
|
// helperText={errors.startHour?.message}
|
||||||
|
// />
|
||||||
|
// <Typography variant="body2" fontWeight={500}>
|
||||||
|
// End Hour
|
||||||
|
// </Typography>
|
||||||
|
// <TextField
|
||||||
|
// {...register("endHour", {
|
||||||
|
// required: "End hour is required",
|
||||||
|
// })}
|
||||||
|
// // label="End Hour"
|
||||||
|
// type="time"
|
||||||
|
// // sx={{ marginTop: 1 }}
|
||||||
|
// fullWidth
|
||||||
|
// margin="normal"
|
||||||
|
// InputLabelProps={{
|
||||||
|
// shrink: true,
|
||||||
|
// }}
|
||||||
|
// error={!!errors.endHour}
|
||||||
|
// helperText={errors.endHour?.message}
|
||||||
|
// />
|
||||||
|
// {/* Availability Toggle */}
|
||||||
|
// <Box
|
||||||
|
// display="flex"
|
||||||
|
// alignItems="center"
|
||||||
|
// justifyContent="space-between"
|
||||||
|
// gap={2}
|
||||||
|
// >
|
||||||
|
|
||||||
|
// <Button
|
||||||
|
// onClick={() => setIsAvailable((prev) => !prev)}
|
||||||
|
// variant={isAvailable ? "contained" : "outlined"}
|
||||||
|
// color="primary"
|
||||||
|
// sx={{ marginTop: 1 }}
|
||||||
|
|
||||||
|
// >
|
||||||
|
// Check Availability
|
||||||
|
// </Button>
|
||||||
|
// <Typography>
|
||||||
|
// {isAvailable ? "Available" : "Not Available"}
|
||||||
|
// </Typography>
|
||||||
|
// </Box>
|
||||||
|
// <DialogActions>
|
||||||
|
// <Button onClick={handleClose} color="secondary">
|
||||||
|
// Cancel
|
||||||
|
// </Button>
|
||||||
|
|
||||||
|
// <Button
|
||||||
|
// type="submit"
|
||||||
|
// sx={{
|
||||||
|
// backgroundColor: "#52ACDF",
|
||||||
|
// color: "white",
|
||||||
|
// borderRadius: "8px",
|
||||||
|
// width: "100px",
|
||||||
|
// "&:hover": { backgroundColor: "#439BC1" },
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// Add Booking
|
||||||
|
// </Button>
|
||||||
|
// </DialogActions>
|
||||||
|
// </form>
|
||||||
|
// </DialogContent>
|
||||||
|
// </Dialog>
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export default AddSlotModal;
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
|
@ -8,6 +156,12 @@ import {
|
||||||
TextField,
|
TextField,
|
||||||
Typography,
|
Typography,
|
||||||
Box,
|
Box,
|
||||||
|
FormControlLabel,
|
||||||
|
Switch,
|
||||||
|
MenuItem,
|
||||||
|
Select,
|
||||||
|
FormControl,
|
||||||
|
InputLabel,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
|
||||||
|
@ -16,19 +170,77 @@ const AddSlotModal = ({ open, handleClose, handleAddSlot }: any) => {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
reset,
|
reset,
|
||||||
|
watch,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm();
|
} = useForm();
|
||||||
const [isAvailable, setIsAvailable] = useState<boolean>(true); // Default is available
|
const [isAvailable, setIsAvailable] = useState<boolean>(true);
|
||||||
|
const [isDateRange, setIsDateRange] = useState<boolean>(false);
|
||||||
|
const [durationUnit, setDurationUnit] = useState<string>("minutes");
|
||||||
|
const [minEndTime, setMinEndTime] = useState<string>("");
|
||||||
|
|
||||||
// Get today's date in the format yyyy-mm-dd
|
// Get today's date in the format yyyy-mm-dd
|
||||||
const today = new Date().toISOString().split("T")[0];
|
const today = new Date().toISOString().split("T")[0];
|
||||||
|
|
||||||
const onSubmit = (data: {
|
// Watch the start time value
|
||||||
date: string;
|
const startHour = watch("startHour");
|
||||||
startHour: string;
|
|
||||||
endHour: string;
|
useEffect(() => {
|
||||||
}) => {
|
if (startHour) {
|
||||||
handleAddSlot({ ...data, isAvailable });
|
setMinEndTime(startHour);
|
||||||
|
}
|
||||||
|
}, [startHour]);
|
||||||
|
|
||||||
|
const onSubmit = (data: any) => {
|
||||||
|
const { date, startDate, endDate, startHour, endHour, duration } = data;
|
||||||
|
const slots: { date: string; startHour: string; endHour: string; isAvailable: boolean; duration: number; }[] = [];
|
||||||
|
|
||||||
|
const generateSlotsForDate = (date: string) => {
|
||||||
|
const startTime = new Date(`1970-01-01T${startHour}:00`);
|
||||||
|
const endTime = new Date(`1970-01-01T${endHour}:00`);
|
||||||
|
let durationMinutes = parseInt(duration, 10);
|
||||||
|
|
||||||
|
if (durationUnit === "hours") {
|
||||||
|
durationMinutes *= 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (
|
||||||
|
let time = startTime;
|
||||||
|
time < endTime;
|
||||||
|
time.setMinutes(time.getMinutes() + durationMinutes)
|
||||||
|
) {
|
||||||
|
const slotEndTime = new Date(time);
|
||||||
|
slotEndTime.setMinutes(
|
||||||
|
slotEndTime.getMinutes() + durationMinutes
|
||||||
|
);
|
||||||
|
|
||||||
|
if (slotEndTime <= endTime) {
|
||||||
|
slots.push({
|
||||||
|
date,
|
||||||
|
startHour: time.toTimeString().slice(0, 5),
|
||||||
|
endHour: slotEndTime.toTimeString().slice(0, 5),
|
||||||
|
isAvailable,
|
||||||
|
duration: durationMinutes,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isDateRange) {
|
||||||
|
const start = new Date(startDate);
|
||||||
|
const end = new Date(endDate);
|
||||||
|
for (
|
||||||
|
let d = new Date(start);
|
||||||
|
d <= end;
|
||||||
|
d.setDate(d.getDate() + 1)
|
||||||
|
) {
|
||||||
|
const dateString = d.toISOString().split("T")[0];
|
||||||
|
generateSlotsForDate(dateString);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
generateSlotsForDate(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAddSlot(slots);
|
||||||
reset();
|
reset();
|
||||||
handleClose();
|
handleClose();
|
||||||
};
|
};
|
||||||
|
@ -38,6 +250,54 @@ const AddSlotModal = ({ open, handleClose, handleAddSlot }: any) => {
|
||||||
<DialogTitle>Add EV Slot</DialogTitle>
|
<DialogTitle>Add EV Slot</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
checked={isDateRange}
|
||||||
|
onChange={() => setIsDateRange(!isDateRange)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Select Date Range"
|
||||||
|
/>
|
||||||
|
{isDateRange ? (
|
||||||
|
<>
|
||||||
|
<Typography variant="body2" fontWeight={500}>
|
||||||
|
Start Date
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
{...register("startDate", {
|
||||||
|
required: "Start date is required",
|
||||||
|
validate: (value) =>
|
||||||
|
value >= today ||
|
||||||
|
"Start date cannot be in the past",
|
||||||
|
})}
|
||||||
|
type="date"
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
error={!!errors.startDate}
|
||||||
|
helperText={errors.startDate?.message}
|
||||||
|
inputProps={{ min: today }}
|
||||||
|
/>
|
||||||
|
<Typography variant="body2" fontWeight={500}>
|
||||||
|
End Date
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
{...register("endDate", {
|
||||||
|
required: "End date is required",
|
||||||
|
validate: (value) =>
|
||||||
|
value >= today ||
|
||||||
|
"End date cannot be in the past",
|
||||||
|
})}
|
||||||
|
type="date"
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
error={!!errors.endDate}
|
||||||
|
helperText={errors.endDate?.message}
|
||||||
|
inputProps={{ min: today }}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<Typography variant="body2" fontWeight={500}>
|
<Typography variant="body2" fontWeight={500}>
|
||||||
Date
|
Date
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@ -45,23 +305,18 @@ const AddSlotModal = ({ open, handleClose, handleAddSlot }: any) => {
|
||||||
{...register("date", {
|
{...register("date", {
|
||||||
required: "Date is required",
|
required: "Date is required",
|
||||||
validate: (value) =>
|
validate: (value) =>
|
||||||
value >= today || "Date cannot be in the past",
|
value >= today ||
|
||||||
|
"Date cannot be in the past",
|
||||||
})}
|
})}
|
||||||
// label="Date"
|
|
||||||
// sx={{ marginTop: 1 }}
|
|
||||||
type="date"
|
type="date"
|
||||||
fullWidth
|
fullWidth
|
||||||
margin="normal"
|
margin="normal"
|
||||||
slotProps={{
|
|
||||||
inputLabel: {
|
|
||||||
shrink: true,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
error={!!errors.date}
|
error={!!errors.date}
|
||||||
helperText={errors.date?.message}
|
helperText={errors.date?.message}
|
||||||
// Set the min value to today's date
|
|
||||||
inputProps={{ min: today }}
|
inputProps={{ min: today }}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<Typography variant="body2" fontWeight={500}>
|
<Typography variant="body2" fontWeight={500}>
|
||||||
Start Hour
|
Start Hour
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@ -69,16 +324,9 @@ const AddSlotModal = ({ open, handleClose, handleAddSlot }: any) => {
|
||||||
{...register("startHour", {
|
{...register("startHour", {
|
||||||
required: "Start hour is required",
|
required: "Start hour is required",
|
||||||
})}
|
})}
|
||||||
// label="Start Hour"
|
|
||||||
type="time"
|
type="time"
|
||||||
// sx={{ marginTop: 1 }}
|
|
||||||
fullWidth
|
fullWidth
|
||||||
margin="normal"
|
margin="normal"
|
||||||
slotProps={{
|
|
||||||
inputLabel: {
|
|
||||||
shrink: true,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
error={!!errors.startHour}
|
error={!!errors.startHour}
|
||||||
helperText={errors.startHour?.message}
|
helperText={errors.startHour?.message}
|
||||||
/>
|
/>
|
||||||
|
@ -88,32 +336,60 @@ const AddSlotModal = ({ open, handleClose, handleAddSlot }: any) => {
|
||||||
<TextField
|
<TextField
|
||||||
{...register("endHour", {
|
{...register("endHour", {
|
||||||
required: "End hour is required",
|
required: "End hour is required",
|
||||||
|
validate: (value) =>
|
||||||
|
value > startHour ||
|
||||||
|
"End hour must be after start hour",
|
||||||
})}
|
})}
|
||||||
// label="End Hour"
|
|
||||||
type="time"
|
type="time"
|
||||||
// sx={{ marginTop: 1 }}
|
|
||||||
fullWidth
|
fullWidth
|
||||||
margin="normal"
|
margin="normal"
|
||||||
InputLabelProps={{
|
|
||||||
shrink: true,
|
|
||||||
}}
|
|
||||||
error={!!errors.endHour}
|
error={!!errors.endHour}
|
||||||
helperText={errors.endHour?.message}
|
helperText={errors.endHour?.message}
|
||||||
|
inputProps={{ min: minEndTime }}
|
||||||
/>
|
/>
|
||||||
{/* Availability Toggle */}
|
<Typography variant="body2" fontWeight={500}>
|
||||||
|
Slot Duration
|
||||||
|
</Typography>
|
||||||
|
<Box display="flex" alignItems="center" gap={2}>
|
||||||
|
<TextField
|
||||||
|
{...register("duration", {
|
||||||
|
required: "Duration is required",
|
||||||
|
pattern: {
|
||||||
|
value: /^[0-9]+$/,
|
||||||
|
message: "Duration must be a number",
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
type="number"
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
error={!!errors.duration}
|
||||||
|
helperText={errors.duration?.message}
|
||||||
|
/>
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<InputLabel>Unit</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={durationUnit}
|
||||||
|
onChange={(e) =>
|
||||||
|
setDurationUnit(e.target.value)
|
||||||
|
}
|
||||||
|
label="Unit"
|
||||||
|
>
|
||||||
|
<MenuItem value="minutes">Minutes</MenuItem>
|
||||||
|
<MenuItem value="hours">Hours</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
display="flex"
|
display="flex"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
gap={2}
|
gap={2}
|
||||||
>
|
>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setIsAvailable((prev) => !prev)}
|
onClick={() => setIsAvailable((prev) => !prev)}
|
||||||
variant={isAvailable ? "contained" : "outlined"}
|
variant={isAvailable ? "contained" : "outlined"}
|
||||||
color="primary"
|
color="primary"
|
||||||
sx={{ marginTop: 1 }}
|
sx={{ marginTop: 1 }}
|
||||||
|
|
||||||
>
|
>
|
||||||
Check Availability
|
Check Availability
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -125,7 +401,6 @@ const AddSlotModal = ({ open, handleClose, handleAddSlot }: any) => {
|
||||||
<Button onClick={handleClose} color="secondary">
|
<Button onClick={handleClose} color="secondary">
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -146,3 +421,5 @@ const AddSlotModal = ({ open, handleClose, handleAddSlot }: any) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AddSlotModal;
|
export default AddSlotModal;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,27 +1,37 @@
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import {
|
import { Box, Button, Typography, Modal } from "@mui/material";
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Typography,
|
|
||||||
Modal,
|
|
||||||
} from "@mui/material";
|
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
import { CustomIconButton, CustomTextField } from "../AddUserModal/styled.css";
|
import { CustomIconButton, CustomTextField } from "../AddUserModal/styled.css";
|
||||||
export default function AddVehicleModal({
|
import { useDispatch } from "react-redux";
|
||||||
open,
|
import { addVehicle } from "../../redux/slices/VehicleSlice";
|
||||||
handleClose,
|
|
||||||
handleAddVehicle,
|
export default function AddVehicleModal({ open, handleClose }) {
|
||||||
}) {
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
reset,
|
reset,
|
||||||
} = useForm();
|
} = useForm();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const onSubmit = (data: any) => {
|
const onSubmit = (data: {
|
||||||
handleAddVehicle(data); // Add vehicle to table
|
name: string;
|
||||||
handleClose(); // Close modal after adding
|
company: string;
|
||||||
|
modelName: string;
|
||||||
|
chargeType: string;
|
||||||
|
imageFile: File;
|
||||||
|
}) => {
|
||||||
|
const { name, company, modelName, chargeType, imageFile } = data;
|
||||||
|
dispatch(
|
||||||
|
addVehicle({
|
||||||
|
name,
|
||||||
|
company,
|
||||||
|
modelName,
|
||||||
|
chargeType,
|
||||||
|
imageFile: imageFile[0],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
handleClose();
|
||||||
reset();
|
reset();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -32,7 +42,7 @@ export default function AddVehicleModal({
|
||||||
if (reason === "backdropClick") {
|
if (reason === "backdropClick") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
handleClose(); // Close modal when clicking cross or cancel
|
handleClose();
|
||||||
}}
|
}}
|
||||||
aria-labelledby="add-vehicle-modal"
|
aria-labelledby="add-vehicle-modal"
|
||||||
>
|
>
|
||||||
|
@ -95,7 +105,6 @@ export default function AddVehicleModal({
|
||||||
placeholder="Enter Company Name"
|
placeholder="Enter Company Name"
|
||||||
size="small"
|
size="small"
|
||||||
sx={{ marginTop: 1 }}
|
sx={{ marginTop: 1 }}
|
||||||
|
|
||||||
error={!!errors.company}
|
error={!!errors.company}
|
||||||
helperText={
|
helperText={
|
||||||
errors.company
|
errors.company
|
||||||
|
@ -127,7 +136,6 @@ export default function AddVehicleModal({
|
||||||
placeholder="Enter Vehicle Name"
|
placeholder="Enter Vehicle Name"
|
||||||
size="small"
|
size="small"
|
||||||
sx={{ marginTop: 1 }}
|
sx={{ marginTop: 1 }}
|
||||||
|
|
||||||
error={!!errors.name}
|
error={!!errors.name}
|
||||||
helperText={
|
helperText={
|
||||||
errors.name ? errors.name.message : ""
|
errors.name ? errors.name.message : ""
|
||||||
|
@ -171,7 +179,6 @@ export default function AddVehicleModal({
|
||||||
placeholder="Enter Model Name"
|
placeholder="Enter Model Name"
|
||||||
size="small"
|
size="small"
|
||||||
sx={{ marginTop: 1 }}
|
sx={{ marginTop: 1 }}
|
||||||
|
|
||||||
error={!!errors.modelName}
|
error={!!errors.modelName}
|
||||||
helperText={
|
helperText={
|
||||||
errors.modelName
|
errors.modelName
|
||||||
|
@ -199,7 +206,6 @@ export default function AddVehicleModal({
|
||||||
placeholder="Enter Charge Type"
|
placeholder="Enter Charge Type"
|
||||||
size="small"
|
size="small"
|
||||||
sx={{ marginTop: 1 }}
|
sx={{ marginTop: 1 }}
|
||||||
|
|
||||||
error={!!errors.chargeType}
|
error={!!errors.chargeType}
|
||||||
helperText={
|
helperText={
|
||||||
errors.chargeType
|
errors.chargeType
|
||||||
|
@ -213,36 +219,23 @@ export default function AddVehicleModal({
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Third Row - Image URL */}
|
{/* Image Upload */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
|
height:"100%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="body2" fontWeight={500}>
|
<Typography variant="body2" fontWeight={500}>
|
||||||
Image URL
|
Upload Image
|
||||||
</Typography>
|
</Typography>
|
||||||
<CustomTextField
|
<input
|
||||||
fullWidth
|
type="file"
|
||||||
placeholder="Enter Image URL"
|
accept="image/*"
|
||||||
size="small"
|
{...register("imageFile")}
|
||||||
sx={{ marginTop: 1 }}
|
style={{ marginTop: "8px"}}
|
||||||
|
|
||||||
error={!!errors.imageUrl}
|
|
||||||
helperText={
|
|
||||||
errors.imageUrl
|
|
||||||
? errors.imageUrl.message
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
{...register("imageUrl", {
|
|
||||||
required: "Image URL is required",
|
|
||||||
pattern: {
|
|
||||||
value: /^(https?:\/\/)?((([a-zA-Z\d]([a-zA-Z\d-]*[a-zA-Z\d])*)\.)+[a-zA-Z]{2,}|((\d{1,3}\.){3}\d{1,3}))(:\d+)?(\/[-a-zA-Z\d%_.~+]*)*(\?[;&a-zA-Z\d%_.~+=-]*)?(#[-a-zA-Z\d_]*)?$/,
|
|
||||||
message: "Please enter a valid URL",
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
236
src/components/BookingSlotsMangers/bookingslots.tsx
Normal file
236
src/components/BookingSlotsMangers/bookingslots.tsx
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
Button,
|
||||||
|
TextField,
|
||||||
|
Typography,
|
||||||
|
Box,
|
||||||
|
FormControlLabel,
|
||||||
|
Switch,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
|
||||||
|
const AddSlotModal = ({ open, handleClose, handleAddSlot }: any) => {
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm();
|
||||||
|
const [isAvailable, setIsAvailable] = useState<boolean>(true);
|
||||||
|
const [isDateRange, setIsDateRange] = useState<boolean>(false);
|
||||||
|
|
||||||
|
// Get today's date in the format yyyy-mm-dd
|
||||||
|
const today = new Date().toISOString().split("T")[0];
|
||||||
|
|
||||||
|
const onSubmit = (data: any) => {
|
||||||
|
const { date, startDate, endDate, startHour, endHour, duration } = data;
|
||||||
|
const slots = [];
|
||||||
|
|
||||||
|
const generateSlotsForDate = (date: string) => {
|
||||||
|
const startTime = new Date(`1970-01-01T${startHour}:00`);
|
||||||
|
const endTime = new Date(`1970-01-01T${endHour}:00`);
|
||||||
|
const durationMinutes = parseInt(duration, 10);
|
||||||
|
|
||||||
|
for (
|
||||||
|
let time = startTime;
|
||||||
|
time < endTime;
|
||||||
|
time.setMinutes(time.getMinutes() + durationMinutes)
|
||||||
|
) {
|
||||||
|
const slotEndTime = new Date(time);
|
||||||
|
slotEndTime.setMinutes(
|
||||||
|
slotEndTime.getMinutes() + durationMinutes
|
||||||
|
);
|
||||||
|
|
||||||
|
if (slotEndTime <= endTime) {
|
||||||
|
slots.push({
|
||||||
|
date,
|
||||||
|
startHour: time.toTimeString().slice(0, 5),
|
||||||
|
endHour: slotEndTime.toTimeString().slice(0, 5),
|
||||||
|
isAvailable,
|
||||||
|
duration,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isDateRange) {
|
||||||
|
const start = new Date(startDate);
|
||||||
|
const end = new Date(endDate);
|
||||||
|
for (
|
||||||
|
let d = new Date(start);
|
||||||
|
d <= end;
|
||||||
|
d.setDate(d.getDate() + 1)
|
||||||
|
) {
|
||||||
|
const dateString = d.toISOString().split("T")[0];
|
||||||
|
generateSlotsForDate(dateString);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
generateSlotsForDate(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAddSlot(slots);
|
||||||
|
reset();
|
||||||
|
handleClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onClose={handleClose}>
|
||||||
|
<DialogTitle>Add EV Slot</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
checked={isDateRange}
|
||||||
|
onChange={() => setIsDateRange(!isDateRange)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Select Date Range"
|
||||||
|
/>
|
||||||
|
{isDateRange ? (
|
||||||
|
<>
|
||||||
|
<Typography variant="body2" fontWeight={500}>
|
||||||
|
Start Date
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
{...register("startDate", {
|
||||||
|
required: "Start date is required",
|
||||||
|
validate: (value) =>
|
||||||
|
value >= today ||
|
||||||
|
"Start date cannot be in the past",
|
||||||
|
})}
|
||||||
|
type="date"
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
error={!!errors.startDate}
|
||||||
|
helperText={errors.startDate?.message}
|
||||||
|
inputProps={{ min: today }}
|
||||||
|
/>
|
||||||
|
<Typography variant="body2" fontWeight={500}>
|
||||||
|
End Date
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
{...register("endDate", {
|
||||||
|
required: "End date is required",
|
||||||
|
validate: (value) =>
|
||||||
|
value >= today ||
|
||||||
|
"End date cannot be in the past",
|
||||||
|
})}
|
||||||
|
type="date"
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
error={!!errors.endDate}
|
||||||
|
helperText={errors.endDate?.message}
|
||||||
|
inputProps={{ min: today }}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Typography variant="body2" fontWeight={500}>
|
||||||
|
Date
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
{...register("date", {
|
||||||
|
required: "Date is required",
|
||||||
|
validate: (value) =>
|
||||||
|
value >= today ||
|
||||||
|
"Date cannot be in the past",
|
||||||
|
})}
|
||||||
|
type="date"
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
error={!!errors.date}
|
||||||
|
helperText={errors.date?.message}
|
||||||
|
inputProps={{ min: today }}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Typography variant="body2" fontWeight={500}>
|
||||||
|
Start Hour
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
{...register("startHour", {
|
||||||
|
required: "Start hour is required",
|
||||||
|
})}
|
||||||
|
type="time"
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
error={!!errors.startHour}
|
||||||
|
helperText={errors.startHour?.message}
|
||||||
|
/>
|
||||||
|
<Typography variant="body2" fontWeight={500}>
|
||||||
|
End Hour
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
{...register("endHour", {
|
||||||
|
required: "End hour is required",
|
||||||
|
})}
|
||||||
|
type="time"
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
error={!!errors.endHour}
|
||||||
|
helperText={errors.endHour?.message}
|
||||||
|
/>
|
||||||
|
<Typography variant="body2" fontWeight={500}>
|
||||||
|
Slot Duration (minutes)
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
{...register("duration", {
|
||||||
|
required: "Duration is required",
|
||||||
|
pattern: {
|
||||||
|
value: /^[0-9]+$/,
|
||||||
|
message: "Duration must be a number",
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
type="number"
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
error={!!errors.duration}
|
||||||
|
helperText={errors.duration?.message}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="space-between"
|
||||||
|
gap={2}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() => setIsAvailable((prev) => !prev)}
|
||||||
|
variant={isAvailable ? "contained" : "outlined"}
|
||||||
|
color="primary"
|
||||||
|
sx={{ marginTop: 1 }}
|
||||||
|
>
|
||||||
|
Check Availability
|
||||||
|
</Button>
|
||||||
|
<Typography>
|
||||||
|
{isAvailable ? "Available" : "Not Available"}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={handleClose} color="secondary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "#52ACDF",
|
||||||
|
color: "white",
|
||||||
|
borderRadius: "8px",
|
||||||
|
width: "100px",
|
||||||
|
"&:hover": { backgroundColor: "#439BC1" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add Booking
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddSlotModal;
|
|
@ -1,27 +1,20 @@
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import {
|
import { Box, Button, Typography, Modal } from "@mui/material";
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Typography,
|
|
||||||
Modal,
|
|
||||||
} from "@mui/material";
|
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
import { useForm, Controller } from "react-hook-form";
|
import { useForm, Controller } from "react-hook-form";
|
||||||
import { updateVehicle } from "../../redux/slices/VehicleSlice";
|
import { updateVehicle } from "../../redux/slices/VehicleSlice";
|
||||||
import {
|
import { CustomIconButton, CustomTextField } from "../AddUserModal/styled.css";
|
||||||
CustomIconButton,
|
|
||||||
CustomTextField,
|
|
||||||
} from "../AddUserModal/styled.css";
|
|
||||||
interface EditVehicleModalProps {
|
interface EditVehicleModalProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
handleClose: () => void;
|
handleClose: () => void;
|
||||||
handleUpdate: (
|
handleUpdate: (
|
||||||
id: string,
|
id: string,
|
||||||
name: string,
|
name: string,
|
||||||
email: string,
|
company: string,
|
||||||
phone: string,
|
modelName: string,
|
||||||
registeredAddress: string,
|
chargeType: string,
|
||||||
imageUrl: string
|
imageUrl: File | null
|
||||||
) => void;
|
) => void;
|
||||||
editRow: any;
|
editRow: any;
|
||||||
}
|
}
|
||||||
|
@ -31,7 +24,7 @@ interface FormData {
|
||||||
company: string;
|
company: string;
|
||||||
modelName: string;
|
modelName: string;
|
||||||
chargeType: string;
|
chargeType: string;
|
||||||
imageUrl: string;
|
imageUrl: File | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EditVehicleModal: React.FC<EditVehicleModalProps> = ({
|
const EditVehicleModal: React.FC<EditVehicleModalProps> = ({
|
||||||
|
@ -52,38 +45,58 @@ const EditVehicleModal: React.FC<EditVehicleModalProps> = ({
|
||||||
company: "",
|
company: "",
|
||||||
modelName: "",
|
modelName: "",
|
||||||
chargeType: "",
|
chargeType: "",
|
||||||
imageUrl: "",
|
imageUrl: null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set values if editRow is provided
|
// Set values if editRow is provided
|
||||||
|
const [imagePreview, setImagePreview] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editRow) {
|
if (editRow) {
|
||||||
|
// Construct full image URL
|
||||||
|
const imageUrl = `${process.env.REACT_APP_BACKEND_URL}/image/${editRow.imageUrl}`;
|
||||||
|
setImagePreview(imageUrl); // Set the image URL to display the preview
|
||||||
setValue("name", editRow.name);
|
setValue("name", editRow.name);
|
||||||
setValue("company", editRow.company);
|
setValue("company", editRow.company);
|
||||||
setValue("modelName", editRow.modelName);
|
setValue("modelName", editRow.modelName);
|
||||||
setValue("chargeType", editRow.chargeType);
|
setValue("chargeType", editRow.chargeType);
|
||||||
setValue("imageUrl", editRow.imageUrl);
|
|
||||||
} else {
|
} else {
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
}, [editRow, setValue, reset]);
|
}, [editRow, setValue, reset]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = e.target.files?.[0];
|
||||||
|
if (file) {
|
||||||
|
setImagePreview(URL.createObjectURL(file)); // Preview the image
|
||||||
|
setValue("imageUrl", file); // Set the file in the form
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const onSubmit = (data: FormData) => {
|
const onSubmit = (data: FormData) => {
|
||||||
if (editRow) {
|
// Check if a new image was selected and set its URL or filename
|
||||||
|
const imageUrl = data.imageUrl
|
||||||
|
? `${process.env.REACT_APP_BACKEND_URL}/image/${data.imageUrl.name}` // Assuming image is a file object
|
||||||
|
: editRow?.imageUrl; // Keep existing image if not changed
|
||||||
|
|
||||||
handleUpdate(
|
handleUpdate(
|
||||||
editRow.id,
|
editRow.id,
|
||||||
data.name,
|
data.name,
|
||||||
data.company,
|
data.company,
|
||||||
data.modelName,
|
data.modelName,
|
||||||
data.chargeType,
|
data.chargeType,
|
||||||
data.imageUrl
|
imageUrl // Send the updated image URL or filename to backend
|
||||||
);
|
);
|
||||||
}
|
|
||||||
handleClose(); // Close the modal
|
handleClose();
|
||||||
reset(); // Reset the form fields
|
reset();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
open={open}
|
open={open}
|
||||||
|
@ -177,7 +190,6 @@ const EditVehicleModal: React.FC<EditVehicleModalProps> = ({
|
||||||
sx={{ marginTop: 1 }}
|
sx={{ marginTop: 1 }}
|
||||||
error={!!errors.name}
|
error={!!errors.name}
|
||||||
helperText={errors.name?.message}
|
helperText={errors.name?.message}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -283,7 +295,7 @@ const EditVehicleModal: React.FC<EditVehicleModalProps> = ({
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Third Row - Image URL Input */}
|
{/* Image Upload */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
@ -292,24 +304,43 @@ const EditVehicleModal: React.FC<EditVehicleModalProps> = ({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="body2" fontWeight={500} mb={0.5}>
|
<Typography variant="body2" fontWeight={500} mb={0.5}>
|
||||||
Image URL
|
Upload Image
|
||||||
</Typography>
|
</Typography>
|
||||||
<Controller
|
<Button
|
||||||
name="imageUrl"
|
variant="contained"
|
||||||
control={control}
|
component="label"
|
||||||
rules={{ required: "Image URL is required" }}
|
sx={{
|
||||||
render={({ field }) => (
|
backgroundColor: "#52ACDF",
|
||||||
<CustomTextField
|
color: "white",
|
||||||
{...field}
|
borderRadius: "8px",
|
||||||
fullWidth
|
width: "100%",
|
||||||
placeholder="Enter Image URL"
|
"&:hover": { backgroundColor: "#439BC1" },
|
||||||
size="small"
|
}}
|
||||||
sx={{ marginTop: 1 }}
|
>
|
||||||
error={!!errors.imageUrl}
|
Upload Image
|
||||||
helperText={errors.imageUrl?.message}
|
<input
|
||||||
|
type="file"
|
||||||
|
hidden
|
||||||
|
accept="image/*"
|
||||||
|
onChange={handleImageChange}
|
||||||
/>
|
/>
|
||||||
|
</Button>
|
||||||
|
{imagePreview && (
|
||||||
|
<Box sx={{ marginTop: 2 }}>
|
||||||
|
<Typography variant="body2" mb={1}>
|
||||||
|
Preview:
|
||||||
|
</Typography>
|
||||||
|
<img
|
||||||
|
src={imagePreview}
|
||||||
|
alt="image preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: "100%",
|
||||||
|
height: "auto",
|
||||||
|
borderRadius: "8px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ export default function Header() {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Search Bar */}
|
{/* Search Bar */}
|
||||||
<Box
|
{/* <Box
|
||||||
sx={{
|
sx={{
|
||||||
// width: { xs: "100%", sm: "360px" },
|
// width: { xs: "100%", sm: "360px" },
|
||||||
height: "40px",
|
height: "40px",
|
||||||
|
@ -82,7 +82,7 @@ export default function Header() {
|
||||||
fontSize: { xs: "12px", sm: "14px" },
|
fontSize: { xs: "12px", sm: "14px" },
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box> */}
|
||||||
|
|
||||||
{/* Notification and Profile Section */}
|
{/* Notification and Profile Section */}
|
||||||
<Stack
|
<Stack
|
||||||
|
@ -94,10 +94,10 @@ export default function Header() {
|
||||||
display: { xs: "none", sm: "flex" }, // Hide on mobile, show on larger screens
|
display: { xs: "none", sm: "flex" }, // Hide on mobile, show on larger screens
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<NotificationsNoneIcon
|
{/* <NotificationsNoneIcon
|
||||||
sx={{ cursor: "pointer" }}
|
sx={{ cursor: "pointer" }}
|
||||||
onClick={toggleNotifications}
|
onClick={toggleNotifications}
|
||||||
/>
|
/> */}
|
||||||
<Avatar
|
<Avatar
|
||||||
alt="User Avatar"
|
alt="User Avatar"
|
||||||
src="/avatar.png"
|
src="/avatar.png"
|
||||||
|
|
|
@ -45,11 +45,11 @@ export default function MenuContent({ hidden }: PropType) {
|
||||||
icon: <PeopleOutlinedIcon />,
|
icon: <PeopleOutlinedIcon />,
|
||||||
url: "/panel/user-list",
|
url: "/panel/user-list",
|
||||||
},
|
},
|
||||||
userRole === "superadmin" && {
|
// userRole === "superadmin" && {
|
||||||
text: "Roles",
|
// text: "Roles",
|
||||||
icon: <AnalyticsOutlinedIcon />,
|
// icon: <AnalyticsOutlinedIcon />,
|
||||||
url: "/panel/role-list",
|
// url: "/panel/role-list",
|
||||||
},
|
// },
|
||||||
userRole === "admin" && {
|
userRole === "admin" && {
|
||||||
text: "Users",
|
text: "Users",
|
||||||
icon: <PeopleOutlinedIcon />,
|
icon: <PeopleOutlinedIcon />,
|
||||||
|
@ -86,6 +86,7 @@ export default function MenuContent({ hidden }: PropType) {
|
||||||
icon: <ChecklistSharpIcon />,
|
icon: <ChecklistSharpIcon />,
|
||||||
url: "/panel/slot-list", // Placeholder for now
|
url: "/panel/slot-list", // Placeholder for now
|
||||||
},
|
},
|
||||||
|
|
||||||
userRole === "user" && {
|
userRole === "user" && {
|
||||||
text: "Available Slots",
|
text: "Available Slots",
|
||||||
icon: <ChecklistSharpIcon />,
|
icon: <ChecklistSharpIcon />,
|
||||||
|
|
|
@ -23,6 +23,7 @@ import {
|
||||||
deleteSlot,
|
deleteSlot,
|
||||||
} from "../../redux/slices/slotSlice.ts";
|
} from "../../redux/slices/slotSlice.ts";
|
||||||
import { RootState } from "../../redux/reducers.ts";
|
import { RootState } from "../../redux/reducers.ts";
|
||||||
|
import BookingSlots from "../../components/BookingSlotsMangers/bookingslots.tsx";
|
||||||
|
|
||||||
const days = [
|
const days = [
|
||||||
"Monday",
|
"Monday",
|
||||||
|
@ -122,7 +123,7 @@ export default function EVSlotManagement() {
|
||||||
|
|
||||||
<Tabs
|
<Tabs
|
||||||
value={selectedDay}
|
value={selectedDay}
|
||||||
onChange={(event, newValue) => setSelectedDay(newValue)}
|
onChange={(_event, newValue) => setSelectedDay(newValue)}
|
||||||
variant="scrollable"
|
variant="scrollable"
|
||||||
scrollButtons="auto"
|
scrollButtons="auto"
|
||||||
sx={{ mt: 3 }}
|
sx={{ mt: 3 }}
|
||||||
|
@ -216,6 +217,9 @@ export default function EVSlotManagement() {
|
||||||
<Typography>No slots added for {selectedDay}</Typography>
|
<Typography>No slots added for {selectedDay}</Typography>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,12 +46,13 @@ export default function VehicleList() {
|
||||||
company: string;
|
company: string;
|
||||||
modelName: string;
|
modelName: string;
|
||||||
chargeType: string;
|
chargeType: string;
|
||||||
imageUrl: string;
|
imageFile: File;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
await dispatch(addVehicle(data)); // Dispatch action to add vehicle
|
const response = await dispatch(addVehicle(data));
|
||||||
await dispatch(vehicleList()); // Fetch the updated list
|
console.log("Added vehicle response: ", response); // Check if the image URL is included in the response
|
||||||
handleCloseModal(); // Close the modal
|
await dispatch(vehicleList());
|
||||||
|
handleCloseModal();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error adding vehicle", error);
|
console.error("Error adding vehicle", error);
|
||||||
}
|
}
|
||||||
|
@ -92,8 +93,9 @@ export default function VehicleList() {
|
||||||
{ id: "imageUrl", label: "Image" },
|
{ id: "imageUrl", label: "Image" },
|
||||||
{ id: "action", label: "Action", align: "center" },
|
{ id: "action", label: "Action", align: "center" },
|
||||||
];
|
];
|
||||||
|
console.log(
|
||||||
|
`${process.env.REACT_APP_BACKEND_URL}/image/${vehicles[0]?.imageUrl}`
|
||||||
|
);
|
||||||
|
|
||||||
const categoryRows = vehicles?.length
|
const categoryRows = vehicles?.length
|
||||||
? vehicles?.map(
|
? vehicles?.map(
|
||||||
|
@ -114,11 +116,10 @@ export default function VehicleList() {
|
||||||
company: vehicle?.company,
|
company: vehicle?.company,
|
||||||
modelName: vehicle?.modelName,
|
modelName: vehicle?.modelName,
|
||||||
chargeType: vehicle?.chargeType,
|
chargeType: vehicle?.chargeType,
|
||||||
imageUrl: vehicle?.imageUrl,
|
imageUrl: `${process.env.REACT_APP_BACKEND_URL}/image/${vehicle?.imageUrl}`,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CustomTable
|
<CustomTable
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
|
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";
|
||||||
|
|
||||||
interface VehicleBrand {
|
interface VehicleBrand {
|
||||||
name: string;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -16,18 +17,21 @@ interface Vehicle {
|
||||||
chargeType: string;
|
chargeType: string;
|
||||||
imageUrl: string;
|
imageUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface VehicleState {
|
interface VehicleState {
|
||||||
vehicleBrands: VehicleBrand[];
|
vehicleBrands: VehicleBrand[];
|
||||||
vehicles: Vehicle[];
|
vehicles: Vehicle[];
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: VehicleState = {
|
const initialState: VehicleState = {
|
||||||
vehicleBrands: [],
|
vehicleBrands: [],
|
||||||
vehicles: [],
|
vehicles: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null,
|
error: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchVehicleBrands = createAsyncThunk<
|
export const fetchVehicleBrands = createAsyncThunk<
|
||||||
VehicleBrand[],
|
VehicleBrand[],
|
||||||
void,
|
void,
|
||||||
|
@ -60,6 +64,7 @@ export const vehicleList = createAsyncThunk<
|
||||||
if (!token) throw new Error("No token found");
|
if (!token) throw new Error("No token found");
|
||||||
|
|
||||||
const response = await http.get("/get-vehicles");
|
const response = await http.get("/get-vehicles");
|
||||||
|
// console.log("---------",response?.data?.data);
|
||||||
|
|
||||||
if (!response.data?.data) throw new Error("Invalid API response");
|
if (!response.data?.data) throw new Error("Invalid API response");
|
||||||
|
|
||||||
|
@ -80,12 +85,23 @@ export const addVehicle = createAsyncThunk<
|
||||||
company: string;
|
company: string;
|
||||||
modelName: string;
|
modelName: string;
|
||||||
chargeType: string;
|
chargeType: string;
|
||||||
imageUrl: string;
|
imageFile: File;
|
||||||
},
|
},
|
||||||
{ rejectValue: string }
|
{ rejectValue: string }
|
||||||
>("/AddVehicle", async (data, { rejectWithValue }) => {
|
>("addVehicle", async ({ name, company, modelName, chargeType, imageFile }, { rejectWithValue }) => {
|
||||||
try {
|
try {
|
||||||
const response = await http.post("create-vehicle", data);
|
const formData = new FormData();
|
||||||
|
formData.append("name", name);
|
||||||
|
formData.append("company", company);
|
||||||
|
formData.append("modelName", modelName);
|
||||||
|
formData.append("chargeType", chargeType);
|
||||||
|
formData.append("image", imageFile);
|
||||||
|
|
||||||
|
const response = await http.post("create-vehicle", formData, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "multipart/form-data",
|
||||||
|
},
|
||||||
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(
|
||||||
|
@ -103,7 +119,7 @@ export const updateVehicle = createAsyncThunk(
|
||||||
`/update-vehicle/${id}`,
|
`/update-vehicle/${id}`,
|
||||||
vehicleData
|
vehicleData
|
||||||
);
|
);
|
||||||
toast.success("Vehicle Deatils updated successfully");
|
toast.success("Vehicle Details updated successfully");
|
||||||
return response?.data;
|
return response?.data;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
toast.error("Error updating the user: " + error);
|
toast.error("Error updating the user: " + error);
|
||||||
|
@ -113,6 +129,7 @@ export const updateVehicle = createAsyncThunk(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const deleteVehicle = createAsyncThunk<
|
export const deleteVehicle = createAsyncThunk<
|
||||||
string,
|
string,
|
||||||
string,
|
string,
|
||||||
|
@ -130,6 +147,7 @@ export const deleteVehicle = createAsyncThunk<
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const vehicleSlice = createSlice({
|
const vehicleSlice = createSlice({
|
||||||
name: "vehicle",
|
name: "vehicle",
|
||||||
initialState,
|
initialState,
|
||||||
|
@ -144,9 +162,10 @@ const vehicleSlice = createSlice({
|
||||||
vehicleList.fulfilled,
|
vehicleList.fulfilled,
|
||||||
(state, action: PayloadAction<Vehicle[]>) => {
|
(state, action: PayloadAction<Vehicle[]>) => {
|
||||||
state.loading = false;
|
state.loading = false;
|
||||||
state.vehicles = action.payload;
|
state.vehicles = action.payload; // Replaces vehicle list in state
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
.addCase(vehicleList.rejected, (state, action) => {
|
.addCase(vehicleList.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 users";
|
||||||
|
@ -161,15 +180,12 @@ const vehicleSlice = createSlice({
|
||||||
state.vehicleBrands = action.payload;
|
state.vehicleBrands = action.payload;
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
.addCase(fetchVehicleBrands.rejected, (state, action) => {
|
.addCase(fetchVehicleBrands.rejected, (state, action) => {
|
||||||
state.loading = false;
|
state.loading = false;
|
||||||
state.error =
|
state.error = action.payload || "Failed to fetch vehicle brands";
|
||||||
action.payload || "Failed to fetch vehicle brands";
|
|
||||||
})
|
})
|
||||||
.addCase(addVehicle.pending, (state) => {
|
.addCase(addVehicle.pending, (state) => {
|
||||||
state.loading = true;
|
state.loading = true;
|
||||||
// state.error = null;
|
|
||||||
})
|
})
|
||||||
.addCase(
|
.addCase(
|
||||||
addVehicle.fulfilled,
|
addVehicle.fulfilled,
|
||||||
|
@ -178,6 +194,7 @@ const vehicleSlice = createSlice({
|
||||||
state.vehicles.push(action.payload);
|
state.vehicles.push(action.payload);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
.addCase(addVehicle.rejected, (state) => {
|
.addCase(addVehicle.rejected, (state) => {
|
||||||
state.loading = false;
|
state.loading = false;
|
||||||
})
|
})
|
||||||
|
@ -186,8 +203,13 @@ const vehicleSlice = createSlice({
|
||||||
})
|
})
|
||||||
.addCase(updateVehicle.fulfilled, (state, action) => {
|
.addCase(updateVehicle.fulfilled, (state, action) => {
|
||||||
state.loading = false;
|
state.loading = false;
|
||||||
state.error = action.payload;
|
const updatedVehicle = action.payload;
|
||||||
|
state.vehicles = state.vehicles.map((vehicle) =>
|
||||||
|
vehicle.id === updatedVehicle.id ? updatedVehicle : vehicle
|
||||||
|
);
|
||||||
|
toast.success("Vehicle Details updated successfully");
|
||||||
})
|
})
|
||||||
|
|
||||||
.addCase(updateVehicle.rejected, (state) => {
|
.addCase(updateVehicle.rejected, (state) => {
|
||||||
state.loading = false;
|
state.loading = false;
|
||||||
})
|
})
|
||||||
|
|
|
@ -28,6 +28,8 @@ const ExternalStationList = lazy(
|
||||||
);
|
);
|
||||||
const AllManagersList = lazy(() => import("./pages/AllMangersList"));
|
const AllManagersList = lazy(() => import("./pages/AllMangersList"));
|
||||||
const AvailableSlotsList = lazy(() => import("./pages/AvailableSlotsList"));
|
const AvailableSlotsList = lazy(() => import("./pages/AvailableSlotsList"));
|
||||||
|
|
||||||
|
|
||||||
interface ProtectedRouteProps {
|
interface ProtectedRouteProps {
|
||||||
// caps: string[];
|
// caps: string[];
|
||||||
component: React.ReactNode;
|
component: React.ReactNode;
|
||||||
|
@ -134,6 +136,7 @@ export default function AppRouter() {
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
{/* Catch-all Route */}
|
{/* Catch-all Route */}
|
||||||
|
|
Loading…
Reference in a new issue