addslot and images in vehicle added

This commit is contained in:
jaanvi 2025-04-10 18:36:07 +05:30
parent 769239b349
commit 36f6df429a
10 changed files with 907 additions and 339 deletions

View file

@ -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;

View file

@ -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>

View 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;

View file

@ -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>

View file

@ -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"

View file

@ -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 />,

View file

@ -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>
);
}

View file

@ -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

View file

@ -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;

View file

@ -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 */}