addslot and images in vehicle added
This commit is contained in:
parent
769239b349
commit
36f6df429a
|
@ -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 {
|
||||
Dialog,
|
||||
DialogActions,
|
||||
|
@ -8,6 +156,12 @@ import {
|
|||
TextField,
|
||||
Typography,
|
||||
Box,
|
||||
FormControlLabel,
|
||||
Switch,
|
||||
MenuItem,
|
||||
Select,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
} from "@mui/material";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
|
@ -16,19 +170,77 @@ const AddSlotModal = ({ open, handleClose, handleAddSlot }: any) => {
|
|||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
watch,
|
||||
formState: { errors },
|
||||
} = 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
|
||||
const today = new Date().toISOString().split("T")[0];
|
||||
|
||||
const onSubmit = (data: {
|
||||
date: string;
|
||||
startHour: string;
|
||||
endHour: string;
|
||||
}) => {
|
||||
handleAddSlot({ ...data, isAvailable });
|
||||
// Watch the start time value
|
||||
const startHour = watch("startHour");
|
||||
|
||||
useEffect(() => {
|
||||
if (startHour) {
|
||||
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();
|
||||
handleClose();
|
||||
};
|
||||
|
@ -38,30 +250,73 @@ const AddSlotModal = ({ open, handleClose, handleAddSlot }: any) => {
|
|||
<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 }}
|
||||
<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>
|
||||
|
@ -69,16 +324,9 @@ const AddSlotModal = ({ open, handleClose, handleAddSlot }: any) => {
|
|||
{...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}
|
||||
/>
|
||||
|
@ -88,32 +336,60 @@ const AddSlotModal = ({ open, handleClose, handleAddSlot }: any) => {
|
|||
<TextField
|
||||
{...register("endHour", {
|
||||
required: "End hour is required",
|
||||
validate: (value) =>
|
||||
value > startHour ||
|
||||
"End hour must be after start hour",
|
||||
})}
|
||||
// label="End Hour"
|
||||
type="time"
|
||||
// sx={{ marginTop: 1 }}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
InputLabelProps={{
|
||||
shrink: true,
|
||||
}}
|
||||
error={!!errors.endHour}
|
||||
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
|
||||
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>
|
||||
|
@ -125,7 +401,6 @@ const AddSlotModal = ({ open, handleClose, handleAddSlot }: any) => {
|
|||
<Button onClick={handleClose} color="secondary">
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
sx={{
|
||||
|
@ -146,3 +421,5 @@ const AddSlotModal = ({ open, handleClose, handleAddSlot }: any) => {
|
|||
};
|
||||
|
||||
export default AddSlotModal;
|
||||
|
||||
|
||||
|
|
|
@ -1,27 +1,37 @@
|
|||
import { useForm } from "react-hook-form";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Typography,
|
||||
Modal,
|
||||
} from "@mui/material";
|
||||
import { Box, Button, Typography, Modal } from "@mui/material";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { CustomIconButton, CustomTextField } from "../AddUserModal/styled.css";
|
||||
export default function AddVehicleModal({
|
||||
open,
|
||||
handleClose,
|
||||
handleAddVehicle,
|
||||
}) {
|
||||
import { useDispatch } from "react-redux";
|
||||
import { addVehicle } from "../../redux/slices/VehicleSlice";
|
||||
|
||||
export default function AddVehicleModal({ open, handleClose }) {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
reset,
|
||||
} = useForm();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onSubmit = (data: any) => {
|
||||
handleAddVehicle(data); // Add vehicle to table
|
||||
handleClose(); // Close modal after adding
|
||||
const onSubmit = (data: {
|
||||
name: string;
|
||||
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();
|
||||
};
|
||||
|
||||
|
@ -32,7 +42,7 @@ export default function AddVehicleModal({
|
|||
if (reason === "backdropClick") {
|
||||
return;
|
||||
}
|
||||
handleClose(); // Close modal when clicking cross or cancel
|
||||
handleClose();
|
||||
}}
|
||||
aria-labelledby="add-vehicle-modal"
|
||||
>
|
||||
|
@ -95,7 +105,6 @@ export default function AddVehicleModal({
|
|||
placeholder="Enter Company Name"
|
||||
size="small"
|
||||
sx={{ marginTop: 1 }}
|
||||
|
||||
error={!!errors.company}
|
||||
helperText={
|
||||
errors.company
|
||||
|
@ -127,7 +136,6 @@ export default function AddVehicleModal({
|
|||
placeholder="Enter Vehicle Name"
|
||||
size="small"
|
||||
sx={{ marginTop: 1 }}
|
||||
|
||||
error={!!errors.name}
|
||||
helperText={
|
||||
errors.name ? errors.name.message : ""
|
||||
|
@ -171,7 +179,6 @@ export default function AddVehicleModal({
|
|||
placeholder="Enter Model Name"
|
||||
size="small"
|
||||
sx={{ marginTop: 1 }}
|
||||
|
||||
error={!!errors.modelName}
|
||||
helperText={
|
||||
errors.modelName
|
||||
|
@ -199,7 +206,6 @@ export default function AddVehicleModal({
|
|||
placeholder="Enter Charge Type"
|
||||
size="small"
|
||||
sx={{ marginTop: 1 }}
|
||||
|
||||
error={!!errors.chargeType}
|
||||
helperText={
|
||||
errors.chargeType
|
||||
|
@ -213,36 +219,23 @@ export default function AddVehicleModal({
|
|||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Third Row - Image URL */}
|
||||
{/* Image Upload */}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
height:"100%"
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" fontWeight={500}>
|
||||
Image URL
|
||||
Upload Image
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
placeholder="Enter Image URL"
|
||||
size="small"
|
||||
sx={{ marginTop: 1 }}
|
||||
|
||||
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",
|
||||
},
|
||||
})}
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
{...register("imageFile")}
|
||||
style={{ marginTop: "8px"}}
|
||||
/>
|
||||
</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 {
|
||||
Box,
|
||||
Button,
|
||||
Typography,
|
||||
Modal,
|
||||
} from "@mui/material";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Box, Button, Typography, Modal } from "@mui/material";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import { updateVehicle } from "../../redux/slices/VehicleSlice";
|
||||
import {
|
||||
CustomIconButton,
|
||||
CustomTextField,
|
||||
} from "../AddUserModal/styled.css";
|
||||
import { CustomIconButton, CustomTextField } from "../AddUserModal/styled.css";
|
||||
|
||||
interface EditVehicleModalProps {
|
||||
open: boolean;
|
||||
handleClose: () => void;
|
||||
handleUpdate: (
|
||||
id: string,
|
||||
name: string,
|
||||
email: string,
|
||||
phone: string,
|
||||
registeredAddress: string,
|
||||
imageUrl: string
|
||||
company: string,
|
||||
modelName: string,
|
||||
chargeType: string,
|
||||
imageUrl: File | null
|
||||
) => void;
|
||||
editRow: any;
|
||||
}
|
||||
|
@ -31,7 +24,7 @@ interface FormData {
|
|||
company: string;
|
||||
modelName: string;
|
||||
chargeType: string;
|
||||
imageUrl: string;
|
||||
imageUrl: File | null;
|
||||
}
|
||||
|
||||
const EditVehicleModal: React.FC<EditVehicleModalProps> = ({
|
||||
|
@ -52,37 +45,57 @@ const EditVehicleModal: React.FC<EditVehicleModalProps> = ({
|
|||
company: "",
|
||||
modelName: "",
|
||||
chargeType: "",
|
||||
imageUrl: "",
|
||||
imageUrl: null,
|
||||
},
|
||||
});
|
||||
|
||||
// Set values if editRow is provided
|
||||
const [imagePreview, setImagePreview] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
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("company", editRow.company);
|
||||
setValue("modelName", editRow.modelName);
|
||||
setValue("chargeType", editRow.chargeType);
|
||||
setValue("imageUrl", editRow.imageUrl);
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
}, [editRow, setValue, reset]);
|
||||
|
||||
const onSubmit = (data: FormData) => {
|
||||
if (editRow) {
|
||||
handleUpdate(
|
||||
editRow.id,
|
||||
data.name,
|
||||
data.company,
|
||||
data.modelName,
|
||||
data.chargeType,
|
||||
data.imageUrl
|
||||
);
|
||||
}
|
||||
handleClose(); // Close the modal
|
||||
reset(); // Reset the form fields
|
||||
};
|
||||
|
||||
|
||||
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) => {
|
||||
// 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(
|
||||
editRow.id,
|
||||
data.name,
|
||||
data.company,
|
||||
data.modelName,
|
||||
data.chargeType,
|
||||
imageUrl // Send the updated image URL or filename to backend
|
||||
);
|
||||
|
||||
handleClose();
|
||||
reset();
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
@ -151,23 +164,23 @@ const EditVehicleModal: React.FC<EditVehicleModalProps> = ({
|
|||
name="name"
|
||||
control={control}
|
||||
rules={{
|
||||
required: "Vehicle Name is required",
|
||||
minLength: {
|
||||
value: 3,
|
||||
message:
|
||||
"Minimum 3 characters required",
|
||||
},
|
||||
maxLength: {
|
||||
value: 30,
|
||||
message:
|
||||
"Maximum 30 characters allowed",
|
||||
},
|
||||
pattern: {
|
||||
value: /^[A-Za-z\s]+$/, // Only letters and spaces are allowed
|
||||
message:
|
||||
"Vehicle Name must only contain letters and spaces",
|
||||
},
|
||||
}}
|
||||
required: "Vehicle Name is required",
|
||||
minLength: {
|
||||
value: 3,
|
||||
message:
|
||||
"Minimum 3 characters required",
|
||||
},
|
||||
maxLength: {
|
||||
value: 30,
|
||||
message:
|
||||
"Maximum 30 characters allowed",
|
||||
},
|
||||
pattern: {
|
||||
value: /^[A-Za-z\s]+$/, // Only letters and spaces are allowed
|
||||
message:
|
||||
"Vehicle Name must only contain letters and spaces",
|
||||
},
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<CustomTextField
|
||||
{...field}
|
||||
|
@ -177,7 +190,6 @@ const EditVehicleModal: React.FC<EditVehicleModalProps> = ({
|
|||
sx={{ marginTop: 1 }}
|
||||
error={!!errors.name}
|
||||
helperText={errors.name?.message}
|
||||
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
@ -283,7 +295,7 @@ const EditVehicleModal: React.FC<EditVehicleModalProps> = ({
|
|||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Third Row - Image URL Input */}
|
||||
{/* Image Upload */}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
|
@ -292,24 +304,43 @@ const EditVehicleModal: React.FC<EditVehicleModalProps> = ({
|
|||
}}
|
||||
>
|
||||
<Typography variant="body2" fontWeight={500} mb={0.5}>
|
||||
Image URL
|
||||
Upload Image
|
||||
</Typography>
|
||||
<Controller
|
||||
name="imageUrl"
|
||||
control={control}
|
||||
rules={{ required: "Image URL is required" }}
|
||||
render={({ field }) => (
|
||||
<CustomTextField
|
||||
{...field}
|
||||
fullWidth
|
||||
placeholder="Enter Image URL"
|
||||
size="small"
|
||||
sx={{ marginTop: 1 }}
|
||||
error={!!errors.imageUrl}
|
||||
helperText={errors.imageUrl?.message}
|
||||
<Button
|
||||
variant="contained"
|
||||
component="label"
|
||||
sx={{
|
||||
backgroundColor: "#52ACDF",
|
||||
color: "white",
|
||||
borderRadius: "8px",
|
||||
width: "100%",
|
||||
"&:hover": { backgroundColor: "#439BC1" },
|
||||
}}
|
||||
>
|
||||
Upload Image
|
||||
<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>
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ export default function Header() {
|
|||
}}
|
||||
>
|
||||
{/* Search Bar */}
|
||||
<Box
|
||||
{/* <Box
|
||||
sx={{
|
||||
// width: { xs: "100%", sm: "360px" },
|
||||
height: "40px",
|
||||
|
@ -82,7 +82,7 @@ export default function Header() {
|
|||
fontSize: { xs: "12px", sm: "14px" },
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box> */}
|
||||
|
||||
{/* Notification and Profile Section */}
|
||||
<Stack
|
||||
|
@ -94,10 +94,10 @@ export default function Header() {
|
|||
display: { xs: "none", sm: "flex" }, // Hide on mobile, show on larger screens
|
||||
}}
|
||||
>
|
||||
<NotificationsNoneIcon
|
||||
{/* <NotificationsNoneIcon
|
||||
sx={{ cursor: "pointer" }}
|
||||
onClick={toggleNotifications}
|
||||
/>
|
||||
/> */}
|
||||
<Avatar
|
||||
alt="User Avatar"
|
||||
src="/avatar.png"
|
||||
|
|
|
@ -45,11 +45,11 @@ export default function MenuContent({ hidden }: PropType) {
|
|||
icon: <PeopleOutlinedIcon />,
|
||||
url: "/panel/user-list",
|
||||
},
|
||||
userRole === "superadmin" && {
|
||||
text: "Roles",
|
||||
icon: <AnalyticsOutlinedIcon />,
|
||||
url: "/panel/role-list",
|
||||
},
|
||||
// userRole === "superadmin" && {
|
||||
// text: "Roles",
|
||||
// icon: <AnalyticsOutlinedIcon />,
|
||||
// url: "/panel/role-list",
|
||||
// },
|
||||
userRole === "admin" && {
|
||||
text: "Users",
|
||||
icon: <PeopleOutlinedIcon />,
|
||||
|
@ -86,6 +86,7 @@ export default function MenuContent({ hidden }: PropType) {
|
|||
icon: <ChecklistSharpIcon />,
|
||||
url: "/panel/slot-list", // Placeholder for now
|
||||
},
|
||||
|
||||
userRole === "user" && {
|
||||
text: "Available Slots",
|
||||
icon: <ChecklistSharpIcon />,
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
deleteSlot,
|
||||
} from "../../redux/slices/slotSlice.ts";
|
||||
import { RootState } from "../../redux/reducers.ts";
|
||||
import BookingSlots from "../../components/BookingSlotsMangers/bookingslots.tsx";
|
||||
|
||||
const days = [
|
||||
"Monday",
|
||||
|
@ -122,7 +123,7 @@ export default function EVSlotManagement() {
|
|||
|
||||
<Tabs
|
||||
value={selectedDay}
|
||||
onChange={(event, newValue) => setSelectedDay(newValue)}
|
||||
onChange={(_event, newValue) => setSelectedDay(newValue)}
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
sx={{ mt: 3 }}
|
||||
|
@ -216,6 +217,9 @@ export default function EVSlotManagement() {
|
|||
<Typography>No slots added for {selectedDay}</Typography>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
|
||||
</Box>
|
||||
|
||||
);
|
||||
}
|
||||
|
|
|
@ -46,12 +46,13 @@ export default function VehicleList() {
|
|||
company: string;
|
||||
modelName: string;
|
||||
chargeType: string;
|
||||
imageUrl: string;
|
||||
imageFile: File;
|
||||
}) => {
|
||||
try {
|
||||
await dispatch(addVehicle(data)); // Dispatch action to add vehicle
|
||||
await dispatch(vehicleList()); // Fetch the updated list
|
||||
handleCloseModal(); // Close the modal
|
||||
const response = await dispatch(addVehicle(data));
|
||||
console.log("Added vehicle response: ", response); // Check if the image URL is included in the response
|
||||
await dispatch(vehicleList());
|
||||
handleCloseModal();
|
||||
} catch (error) {
|
||||
console.error("Error adding vehicle", error);
|
||||
}
|
||||
|
@ -92,8 +93,9 @@ export default function VehicleList() {
|
|||
{ id: "imageUrl", label: "Image" },
|
||||
{ id: "action", label: "Action", align: "center" },
|
||||
];
|
||||
|
||||
|
||||
console.log(
|
||||
`${process.env.REACT_APP_BACKEND_URL}/image/${vehicles[0]?.imageUrl}`
|
||||
);
|
||||
|
||||
const categoryRows = vehicles?.length
|
||||
? vehicles?.map(
|
||||
|
@ -114,11 +116,10 @@ export default function VehicleList() {
|
|||
company: vehicle?.company,
|
||||
modelName: vehicle?.modelName,
|
||||
chargeType: vehicle?.chargeType,
|
||||
imageUrl: vehicle?.imageUrl,
|
||||
imageUrl: `${process.env.REACT_APP_BACKEND_URL}/image/${vehicle?.imageUrl}`,
|
||||
})
|
||||
)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomTable
|
||||
|
|
|
@ -1,209 +1,231 @@
|
|||
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
|
||||
import http from "../../lib/https";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface VehicleBrand {
|
||||
name: string;
|
||||
id: string;
|
||||
company: string;
|
||||
name: string;
|
||||
id: string;
|
||||
company: string;
|
||||
}
|
||||
|
||||
interface Vehicle {
|
||||
brandId?: string;
|
||||
id: number;
|
||||
name: string;
|
||||
company: string;
|
||||
modelName: string;
|
||||
chargeType: string;
|
||||
imageUrl: string;
|
||||
brandId?: string;
|
||||
id: number;
|
||||
name: string;
|
||||
company: string;
|
||||
modelName: string;
|
||||
chargeType: string;
|
||||
imageUrl: string;
|
||||
}
|
||||
|
||||
interface VehicleState {
|
||||
vehicleBrands: VehicleBrand[];
|
||||
vehicles: Vehicle[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
vehicleBrands: VehicleBrand[];
|
||||
vehicles: Vehicle[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
const initialState: VehicleState = {
|
||||
vehicleBrands: [],
|
||||
vehicles: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
vehicleBrands: [],
|
||||
vehicles: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
};
|
||||
|
||||
export const fetchVehicleBrands = createAsyncThunk<
|
||||
VehicleBrand[],
|
||||
void,
|
||||
{ rejectValue: string }
|
||||
VehicleBrand[],
|
||||
void,
|
||||
{ rejectValue: string }
|
||||
>("vehicle/fetchVehicleBrands", async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await http.get("/get-vehicle-brand");
|
||||
try {
|
||||
const response = await http.get("/get-vehicle-brand");
|
||||
|
||||
if (!response.data || !Array.isArray(response.data.data)) {
|
||||
throw new Error("Expected array of vehicle brands");
|
||||
}
|
||||
if (!response.data || !Array.isArray(response.data.data)) {
|
||||
throw new Error("Expected array of vehicle brands");
|
||||
}
|
||||
|
||||
// Map the brand names (strings) to objects with 'id' and 'name' properties
|
||||
return response.data.data.map((brand: string) => ({
|
||||
id: brand, // Use brand name as the ID
|
||||
name: brand, // Use brand name as the label
|
||||
}));
|
||||
} catch (error: any) {
|
||||
return rejectWithValue("Failed to fetch vehicle brands");
|
||||
}
|
||||
// Map the brand names (strings) to objects with 'id' and 'name' properties
|
||||
return response.data.data.map((brand: string) => ({
|
||||
id: brand, // Use brand name as the ID
|
||||
name: brand, // Use brand name as the label
|
||||
}));
|
||||
} catch (error: any) {
|
||||
return rejectWithValue("Failed to fetch vehicle brands");
|
||||
}
|
||||
});
|
||||
|
||||
export const vehicleList = createAsyncThunk<
|
||||
Vehicle[],
|
||||
void,
|
||||
{ rejectValue: string }
|
||||
Vehicle[],
|
||||
void,
|
||||
{ rejectValue: string }
|
||||
>("fetchVehicles", async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
const token = localStorage?.getItem("authToken");
|
||||
if (!token) throw new Error("No token found");
|
||||
try {
|
||||
const token = localStorage?.getItem("authToken");
|
||||
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");
|
||||
|
||||
return response.data.data;
|
||||
} catch (error: any) {
|
||||
toast.error("Error Fetching Profile" + error);
|
||||
return rejectWithValue(
|
||||
error?.response?.data?.message || "An error occurred"
|
||||
);
|
||||
}
|
||||
return response.data.data;
|
||||
} catch (error: any) {
|
||||
toast.error("Error Fetching Profile" + error);
|
||||
return rejectWithValue(
|
||||
error?.response?.data?.message || "An error occurred"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
//Add Vehicle
|
||||
// Add Vehicle
|
||||
export const addVehicle = createAsyncThunk<
|
||||
Vehicle,
|
||||
{
|
||||
name: string;
|
||||
company: string;
|
||||
modelName: string;
|
||||
chargeType: string;
|
||||
imageUrl: string;
|
||||
},
|
||||
{ rejectValue: string }
|
||||
>("/AddVehicle", async (data, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await http.post("create-vehicle", data);
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(
|
||||
error.response?.data?.message || "An error occurred"
|
||||
);
|
||||
}
|
||||
Vehicle,
|
||||
{
|
||||
name: string;
|
||||
company: string;
|
||||
modelName: string;
|
||||
chargeType: string;
|
||||
imageFile: File;
|
||||
},
|
||||
{ rejectValue: string }
|
||||
>("addVehicle", async ({ name, company, modelName, chargeType, imageFile }, { rejectWithValue }) => {
|
||||
try {
|
||||
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;
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(
|
||||
error.response?.data?.message || "An error occurred"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Update Vehicle details
|
||||
export const updateVehicle = createAsyncThunk(
|
||||
"updateVehicle",
|
||||
async ({ id, ...vehicleData }: Vehicle, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await http.patch(
|
||||
`/update-vehicle/${id}`,
|
||||
vehicleData
|
||||
);
|
||||
toast.success("Vehicle Deatils updated successfully");
|
||||
return response?.data;
|
||||
} catch (error: any) {
|
||||
toast.error("Error updating the user: " + error);
|
||||
return rejectWithValue(
|
||||
error.response?.data?.message || "An error occurred"
|
||||
);
|
||||
}
|
||||
}
|
||||
"updateVehicle",
|
||||
async ({ id, ...vehicleData }: Vehicle, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await http.patch(
|
||||
`/update-vehicle/${id}`,
|
||||
vehicleData
|
||||
);
|
||||
toast.success("Vehicle Details updated successfully");
|
||||
return response?.data;
|
||||
} catch (error: any) {
|
||||
toast.error("Error updating the user: " + error);
|
||||
return rejectWithValue(
|
||||
error.response?.data?.message || "An error occurred"
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const deleteVehicle = createAsyncThunk<
|
||||
string,
|
||||
string,
|
||||
{ rejectValue: string }
|
||||
string,
|
||||
string,
|
||||
{ rejectValue: string }
|
||||
>("deleteVehicle", async (id, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await http.delete(`/delete-vehicle/${id}`);
|
||||
toast.success(response.data?.message);
|
||||
return id;
|
||||
} catch (error: any) {
|
||||
toast.error("Error deleting the vehicle" + error);
|
||||
try {
|
||||
const response = await http.delete(`/delete-vehicle/${id}`);
|
||||
toast.success(response.data?.message);
|
||||
return id;
|
||||
} catch (error: any) {
|
||||
toast.error("Error deleting the vehicle" + error);
|
||||
|
||||
return rejectWithValue(
|
||||
error.response?.data?.message || "An error occurred"
|
||||
);
|
||||
}
|
||||
return rejectWithValue(
|
||||
error.response?.data?.message || "An error occurred"
|
||||
);
|
||||
}
|
||||
});
|
||||
const vehicleSlice = createSlice({
|
||||
name: "vehicle",
|
||||
initialState,
|
||||
reducers: {},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(vehicleList.pending, (state) => {
|
||||
state.loading = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(
|
||||
vehicleList.fulfilled,
|
||||
(state, action: PayloadAction<Vehicle[]>) => {
|
||||
state.loading = false;
|
||||
state.vehicles = action.payload;
|
||||
}
|
||||
)
|
||||
.addCase(vehicleList.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.error.message || "Failed to fetch users";
|
||||
})
|
||||
.addCase(fetchVehicleBrands.pending, (state) => {
|
||||
state.loading = true;
|
||||
})
|
||||
.addCase(
|
||||
fetchVehicleBrands.fulfilled,
|
||||
(state, action: PayloadAction<VehicleBrand[]>) => {
|
||||
state.loading = false;
|
||||
state.vehicleBrands = action.payload;
|
||||
}
|
||||
)
|
||||
|
||||
.addCase(fetchVehicleBrands.rejected, (state, action) => {
|
||||
const vehicleSlice = createSlice({
|
||||
name: "vehicle",
|
||||
initialState,
|
||||
reducers: {},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(vehicleList.pending, (state) => {
|
||||
state.loading = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(
|
||||
vehicleList.fulfilled,
|
||||
(state, action: PayloadAction<Vehicle[]>) => {
|
||||
state.loading = false;
|
||||
state.error =
|
||||
action.payload || "Failed to fetch vehicle brands";
|
||||
})
|
||||
.addCase(addVehicle.pending, (state) => {
|
||||
state.loading = true;
|
||||
// state.error = null;
|
||||
})
|
||||
.addCase(
|
||||
addVehicle.fulfilled,
|
||||
(state, action: PayloadAction<Vehicle>) => {
|
||||
state.loading = false;
|
||||
state.vehicles.push(action.payload);
|
||||
}
|
||||
)
|
||||
.addCase(addVehicle.rejected, (state) => {
|
||||
state.vehicles = action.payload; // Replaces vehicle list in state
|
||||
}
|
||||
)
|
||||
|
||||
.addCase(vehicleList.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.error.message || "Failed to fetch users";
|
||||
})
|
||||
.addCase(fetchVehicleBrands.pending, (state) => {
|
||||
state.loading = true;
|
||||
})
|
||||
.addCase(
|
||||
fetchVehicleBrands.fulfilled,
|
||||
(state, action: PayloadAction<VehicleBrand[]>) => {
|
||||
state.loading = false;
|
||||
})
|
||||
.addCase(updateVehicle.pending, (state) => {
|
||||
state.loading = true;
|
||||
})
|
||||
.addCase(updateVehicle.fulfilled, (state, action) => {
|
||||
state.vehicleBrands = action.payload;
|
||||
}
|
||||
)
|
||||
.addCase(fetchVehicleBrands.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload || "Failed to fetch vehicle brands";
|
||||
})
|
||||
.addCase(addVehicle.pending, (state) => {
|
||||
state.loading = true;
|
||||
})
|
||||
.addCase(
|
||||
addVehicle.fulfilled,
|
||||
(state, action: PayloadAction<Vehicle>) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload;
|
||||
})
|
||||
.addCase(updateVehicle.rejected, (state) => {
|
||||
state.loading = false;
|
||||
})
|
||||
.addCase(deleteVehicle.pending, (state) => {
|
||||
state.loading = true;
|
||||
})
|
||||
.addCase(deleteVehicle.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
state.vehicles = state.vehicles.filter(
|
||||
(vehicle) => String(vehicle.id) !== String(action.payload)
|
||||
);
|
||||
})
|
||||
.addCase(deleteVehicle.rejected, (state) => {
|
||||
state.loading = false;
|
||||
});
|
||||
},
|
||||
state.vehicles.push(action.payload);
|
||||
}
|
||||
)
|
||||
|
||||
.addCase(addVehicle.rejected, (state) => {
|
||||
state.loading = false;
|
||||
})
|
||||
.addCase(updateVehicle.pending, (state) => {
|
||||
state.loading = true;
|
||||
})
|
||||
.addCase(updateVehicle.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
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) => {
|
||||
state.loading = false;
|
||||
})
|
||||
.addCase(deleteVehicle.pending, (state) => {
|
||||
state.loading = true;
|
||||
})
|
||||
.addCase(deleteVehicle.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
state.vehicles = state.vehicles.filter(
|
||||
(vehicle) => String(vehicle.id) !== String(action.payload)
|
||||
);
|
||||
})
|
||||
.addCase(deleteVehicle.rejected, (state) => {
|
||||
state.loading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default vehicleSlice.reducer;
|
||||
|
|
|
@ -28,6 +28,8 @@ const ExternalStationList = lazy(
|
|||
);
|
||||
const AllManagersList = lazy(() => import("./pages/AllMangersList"));
|
||||
const AvailableSlotsList = lazy(() => import("./pages/AvailableSlotsList"));
|
||||
|
||||
|
||||
interface ProtectedRouteProps {
|
||||
// caps: string[];
|
||||
component: React.ReactNode;
|
||||
|
@ -134,6 +136,7 @@ export default function AppRouter() {
|
|||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
</Route>
|
||||
|
||||
{/* Catch-all Route */}
|
||||
|
|
Loading…
Reference in a new issue