dev-jaanvi #1

Open
jaanvi wants to merge 155 commits from dev-jaanvi into main
10 changed files with 907 additions and 339 deletions
Showing only changes of commit 36f6df429a - Show all commits

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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