Merge pull request 'New Updates' (#24) from frontend/apiIntegration into develop
Reviewed-on: DigiMantra/digiev_frontend#24
This commit is contained in:
commit
6d2708e0f4
4
public/Group 14.svg
Normal file
4
public/Group 14.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="535" height="226" viewBox="0 0 535 226" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M43.561 68.1C55.753 68.1 65.913 72.3757 74.041 80.927C82.2537 89.4783 86.36 100.019 86.36 112.55C86.36 125.081 82.2537 135.622 74.041 144.173C65.913 152.724 55.753 157 43.561 157H8.255V68.1H43.561ZM43.561 140.236C51.2657 140.236 57.531 137.696 62.357 132.616C67.183 127.451 69.596 120.763 69.596 112.55C69.596 104.337 67.183 97.691 62.357 92.611C57.531 87.4463 51.2657 84.864 43.561 84.864H25.781V140.236H43.561ZM98.7921 68.1H116.318V157H98.7921V68.1ZM220.024 109.248V116.106C220.024 128.806 215.96 139.093 207.832 146.967C199.704 154.841 189.205 158.778 176.336 158.778C162.62 158.778 151.232 154.333 142.173 145.443C133.198 136.468 128.711 125.546 128.711 112.677C128.711 99.723 133.156 88.7587 142.046 79.784C151.02 70.8093 162.112 66.322 175.32 66.322C183.617 66.322 191.195 68.227 198.053 72.037C204.911 75.7623 210.245 80.7577 214.055 87.023L199.069 95.659C196.952 92.0183 193.735 89.055 189.417 86.769C185.183 84.483 180.442 83.34 175.193 83.34C166.726 83.34 159.741 86.134 154.238 91.722C148.819 97.2253 146.11 104.21 146.11 112.677C146.11 121.059 148.861 128.002 154.365 133.505C159.953 138.924 167.319 141.633 176.463 141.633C183.236 141.633 188.824 140.151 193.227 137.188C197.714 134.14 200.762 129.991 202.371 124.742H175.447V109.248H220.024ZM232.489 68.1H250.015V157H232.489V68.1ZM410.998 140.236H448.463V157H393.472V68.1H447.828V84.864H410.998V103.787H444.653V120.297H410.998V140.236ZM482.788 157L452.943 68.1H471.993L493.71 136.426L515.3 68.1H534.477L504.505 157H482.788Z" fill="white"/>
|
||||
<path d="M374.268 108.459L324.874 94.3623L347.782 14.0969L264.907 117.311L314.302 131.408L291.394 211.673L374.268 108.459Z" fill="#52ACDF"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
BIN
public/evLogo.png
Normal file
BIN
public/evLogo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.9 KiB |
|
@ -13,19 +13,22 @@ import {
|
|||
TextField,
|
||||
} from "@mui/material";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
addBooking,
|
||||
bookingList,
|
||||
getCarNames,
|
||||
getCarPorts,
|
||||
} from "../../redux/slices/bookSlice";
|
||||
import { AppDispatch } from "../../redux/store/store";
|
||||
import { AppDispatch, RootState } from "../../redux/store/store";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
CustomIconButton,
|
||||
CustomTextField,
|
||||
} from "../AddEditUserModel/styled.css.tsx";
|
||||
import { getAllStations, stationList } from "../../redux/slices/stationSlice.ts";
|
||||
import { fetchAvailableSlots } from "../../redux/slices/slotSlice.ts";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export default function AddBookingModal({
|
||||
open,
|
||||
|
@ -44,6 +47,17 @@ export default function AddBookingModal({
|
|||
} = useForm();
|
||||
const [carNames, setCarNames] = useState<any[]>([]); // To hold the car names
|
||||
const [carPorts, setCarPorts] = useState<any[]>([]); // To hold the car ports
|
||||
const stations = useSelector(
|
||||
(state: RootState) => state?.stationReducer.stations
|
||||
);
|
||||
const availableSlots = useSelector(
|
||||
(state: RootState) => state.slotReducer.availableSlots
|
||||
);
|
||||
console.log("first", availableSlots);
|
||||
useEffect(() => {
|
||||
dispatch(fetchAvailableSlots());
|
||||
dispatch(getAllStations());
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch car names and car ports
|
||||
|
@ -60,13 +74,13 @@ export default function AddBookingModal({
|
|||
// Fetch the bookings after this
|
||||
dispatch(bookingList());
|
||||
}, [dispatch]);
|
||||
|
||||
console.log("Car Ports: ", carPorts);
|
||||
// Get today's date in yyyy-mm-dd format
|
||||
const today = new Date().toISOString().split("T")[0];
|
||||
|
||||
const onSubmit = async (data: any) => {
|
||||
const bookingData = {
|
||||
stationId: data.stationId,
|
||||
stationId: data.stationId, // Using stationId here for the backend
|
||||
date: data.date,
|
||||
startTime: data.startTime,
|
||||
endTime: data.endTime,
|
||||
|
@ -130,23 +144,34 @@ export default function AddBookingModal({
|
|||
{/* Station ID */}
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="body2" fontWeight={500}>
|
||||
Station ID
|
||||
Select Station
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
placeholder="Enter Station Id"
|
||||
size="small"
|
||||
error={!!errors.stationId}
|
||||
helperText={
|
||||
errors.stationId
|
||||
? errors.stationId.message
|
||||
: ""
|
||||
}
|
||||
{...register("stationId", {
|
||||
required: "Station ID is required",
|
||||
})}
|
||||
/>
|
||||
<FormControl fullWidth error={!!errors.stationName}>
|
||||
<InputLabel>Select Station</InputLabel>
|
||||
<Select
|
||||
{...register("stationId", {
|
||||
required: "Station Name is required", // Change to stationId
|
||||
})}
|
||||
defaultValue=""
|
||||
size="small"
|
||||
>
|
||||
{stations.map((station) => (
|
||||
<MenuItem
|
||||
key={station.id}
|
||||
value={station.id}
|
||||
>
|
||||
{station.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.stationName && (
|
||||
<Typography color="error" variant="body2">
|
||||
{errors.stationName.message}
|
||||
</Typography>
|
||||
)}
|
||||
</FormControl>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="body2" fontWeight={500}>
|
||||
Date
|
||||
|
@ -190,7 +215,6 @@ export default function AddBookingModal({
|
|||
size="small"
|
||||
error={!!errors.carName}
|
||||
>
|
||||
<InputLabel>Car Name</InputLabel>
|
||||
<Select
|
||||
{...field}
|
||||
label="Car Name"
|
||||
|
@ -242,9 +266,9 @@ export default function AddBookingModal({
|
|||
{carPorts.map((port, index) => (
|
||||
<MenuItem
|
||||
key={index}
|
||||
value={port.chargeType}
|
||||
value={port}
|
||||
>
|
||||
{port.chargeType}
|
||||
{port}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
|
@ -266,7 +290,6 @@ export default function AddBookingModal({
|
|||
|
||||
{/* Start Time and End Time */}
|
||||
<Box sx={{ display: "flex", gap: 2, mb: 2 }}>
|
||||
{/* Start Time */}
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="body2" fontWeight={500}>
|
||||
Start Time
|
||||
|
@ -287,7 +310,6 @@ export default function AddBookingModal({
|
|||
/>
|
||||
</Box>
|
||||
|
||||
{/* End Time */}
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="body2" fontWeight={500}>
|
||||
End Time
|
||||
|
@ -328,6 +350,114 @@ export default function AddBookingModal({
|
|||
})}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* <Box sx={{ flex: 1 }}>
|
||||
<Typography variant="body2" fontWeight={500}>
|
||||
{" Time Slot "}
|
||||
</Typography>
|
||||
<Controller
|
||||
control={control}
|
||||
name="timeSlot"
|
||||
render={({ field }) => (
|
||||
<FormControl
|
||||
fullWidth
|
||||
size="small"
|
||||
error={!!errors.timeSlot}
|
||||
>
|
||||
<InputLabel>
|
||||
Select Time Slot
|
||||
</InputLabel>
|
||||
<Select
|
||||
{...field}
|
||||
label="Time Slot"
|
||||
multiple // Allow multiple selections
|
||||
value={field.value || []} // Ensure the value is an array, even if it's empty
|
||||
onChange={(e) =>
|
||||
field.onChange(e.target.value)
|
||||
} // Ensure the selected value is updated properly
|
||||
>
|
||||
{availableSlots.map(
|
||||
(slot, index) => {
|
||||
const start = dayjs(
|
||||
slot.startTime
|
||||
);
|
||||
const end = dayjs(
|
||||
slot.endTime
|
||||
);
|
||||
|
||||
// Function to generate half-hour time slots between start and end time
|
||||
const generateHalfHourSlots =
|
||||
(
|
||||
startTime: dayjs.Dayjs,
|
||||
endTime: dayjs.Dayjs
|
||||
) => {
|
||||
const slots = [];
|
||||
let currentTime =
|
||||
startTime;
|
||||
|
||||
while (
|
||||
currentTime.isBefore(
|
||||
endTime
|
||||
)
|
||||
) {
|
||||
const nextTime =
|
||||
currentTime.add(
|
||||
30,
|
||||
"minute"
|
||||
);
|
||||
slots.push({
|
||||
id: `${currentTime.format(
|
||||
"HH:mm"
|
||||
)}-${nextTime.format(
|
||||
"HH:mm"
|
||||
)}`,
|
||||
label: `${currentTime.format(
|
||||
"hh:mm A"
|
||||
)} - ${nextTime.format(
|
||||
"hh:mm A"
|
||||
)}`,
|
||||
});
|
||||
currentTime =
|
||||
nextTime;
|
||||
}
|
||||
|
||||
return slots;
|
||||
};
|
||||
|
||||
// Generate half-hour slots for the current available slot
|
||||
const halfHourSlots =
|
||||
generateHalfHourSlots(
|
||||
start,
|
||||
end
|
||||
);
|
||||
|
||||
return halfHourSlots.map(
|
||||
(slot, slotIndex) => (
|
||||
<MenuItem
|
||||
key={`${index}-${slotIndex}`}
|
||||
value={slot.id}
|
||||
>
|
||||
{slot.label}
|
||||
</MenuItem>
|
||||
)
|
||||
);
|
||||
}
|
||||
)}
|
||||
</Select>
|
||||
{errors.timeSlot && (
|
||||
<Typography
|
||||
color="error"
|
||||
variant="body2"
|
||||
sx={{ mt: 1 }}
|
||||
>
|
||||
{errors.timeSlot.message}
|
||||
</Typography>
|
||||
)}
|
||||
</FormControl>
|
||||
)}
|
||||
rules={{ required: "Time Slot is required" }}
|
||||
/>
|
||||
</Box> */}
|
||||
</Box>
|
||||
|
||||
{/* Submit Button */}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { IconButton, TextField } from "@mui/material";
|
|||
export const CustomIconButton = styled(IconButton)({
|
||||
backgroundColor: "transparent",
|
||||
"&:hover": {
|
||||
backgroundColor: "#272727",
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
"*:where([data-mui-color-scheme='dark']) &": {
|
||||
backgroundColor: "transparent",
|
||||
|
@ -20,7 +20,4 @@ export const CustomTextField = styled(TextField)({
|
|||
"& .MuiInputBase-root.Mui-focused .MuiInputBase-input::placeholder": {
|
||||
color: "darkgray",
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -6,21 +6,28 @@ import {
|
|||
Modal,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
Select,
|
||||
MenuItem,
|
||||
InputLabel,
|
||||
FormControl,
|
||||
} from "@mui/material";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { Visibility, VisibilityOff } from "@mui/icons-material";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { addManager, managerList } from "../../redux/slices/managerSlice";
|
||||
import {
|
||||
CustomIconButton,
|
||||
CustomTextField,
|
||||
} from "../AddEditUserModel/styled.css.tsx";
|
||||
import React, { useState, useRef } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { RootState } from "../../redux/reducers.ts";
|
||||
import { stationList } from "../../redux/slices/stationSlice.ts";
|
||||
|
||||
export default function AddManagerModal({
|
||||
open,
|
||||
handleClose,
|
||||
handleAddManager,
|
||||
// Assume the stations prop contains a list of station objects with 'id' and 'name'
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
const {
|
||||
|
@ -31,16 +38,26 @@ export default function AddManagerModal({
|
|||
reset,
|
||||
} = useForm();
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const stations = useSelector(
|
||||
(state: RootState) => state?.stationReducer.stations
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(stationList());
|
||||
}, [dispatch]);
|
||||
|
||||
// Handle form submission
|
||||
const onSubmit = async (data: any) => {
|
||||
// Retrieve the stationId based on the stationName selected
|
||||
const selectedStation = stations.find(
|
||||
(station) => station.name === data.stationName
|
||||
);
|
||||
const managerData = {
|
||||
name: data.name,
|
||||
email: data.email,
|
||||
phone: data.phone,
|
||||
password: data.password,
|
||||
stationId: data.stationId, // Send stationId here
|
||||
|
||||
stationId: selectedStation?.id, // Send stationId here
|
||||
};
|
||||
|
||||
try {
|
||||
|
@ -131,33 +148,55 @@ export default function AddManagerModal({
|
|||
"Maximum 30 characters allowed",
|
||||
},
|
||||
pattern: {
|
||||
value: /^[A-Za-z\s]+$/, // Only letters and spaces are allowed
|
||||
value: /^[A-Za-z\s]+$/,
|
||||
message:
|
||||
"Manager Name must only contain letters and spaces",
|
||||
},
|
||||
})}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Station Dropdown */}
|
||||
<Box sx={{ display: "flex", gap: 2, mb: 2 }}>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="body2" fontWeight={500}>
|
||||
Station Id
|
||||
Select Station
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
placeholder="Enter Station Id"
|
||||
size="small"
|
||||
error={!!errors.stationId}
|
||||
helperText={
|
||||
errors.stationId
|
||||
? errors.stationId.message
|
||||
: ""
|
||||
}
|
||||
{...register("stationId", {
|
||||
required: "Station Id is required",
|
||||
})}
|
||||
/>
|
||||
<FormControl fullWidth error={!!errors.stationName}>
|
||||
<InputLabel>Select Station</InputLabel>
|
||||
<Select
|
||||
{...register("stationName", {
|
||||
required: "Station Name is required",
|
||||
})}
|
||||
defaultValue=""
|
||||
size="small"
|
||||
>
|
||||
{Array.isArray(stations) &&
|
||||
stations.length > 0 ? (
|
||||
stations.map((station) => (
|
||||
<MenuItem
|
||||
key={station.id}
|
||||
value={station.name}
|
||||
>
|
||||
{station.name}
|
||||
</MenuItem>
|
||||
))
|
||||
) : (
|
||||
<MenuItem disabled>
|
||||
No stations available
|
||||
</MenuItem>
|
||||
)}
|
||||
</Select>
|
||||
{errors.stationName && (
|
||||
<Typography color="error" variant="body2">
|
||||
{errors.stationName.message}
|
||||
</Typography>
|
||||
)}
|
||||
</FormControl>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Email and Password */}
|
||||
<Box sx={{ display: "flex", gap: 2, mb: 2 }}>
|
||||
{/* Email */}
|
||||
|
@ -219,7 +258,6 @@ export default function AddManagerModal({
|
|||
: "primary"
|
||||
}
|
||||
size="small"
|
||||
// onMouseDown={togglePasswordVisibility}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
|
@ -247,9 +285,8 @@ export default function AddManagerModal({
|
|||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Phone and Registered Address */}
|
||||
{/* Phone Number */}
|
||||
<Box sx={{ display: "flex", gap: 2, mb: 2 }}>
|
||||
{/* Phone */}
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="body2" fontWeight={500}>
|
||||
Phone Number
|
||||
|
@ -272,7 +309,6 @@ export default function AddManagerModal({
|
|||
})}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
|
||||
{/* Submit Button */}
|
||||
|
|
|
@ -1,6 +1,25 @@
|
|||
import { useForm } from "react-hook-form";
|
||||
import { Box, Button, Typography, Modal } from "@mui/material";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Typography,
|
||||
Modal,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
Select,
|
||||
MenuItem,
|
||||
Checkbox,
|
||||
ListItemText,
|
||||
InputLabel,
|
||||
} from "@mui/material";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { useEffect, useState } from "react";
|
||||
import { RootState } from "../../redux/reducers.ts";
|
||||
import {
|
||||
fetchVehicleBrands,
|
||||
vehicleList,
|
||||
} from "../../redux/slices/VehicleSlice"; // Adjust this import path accordingly
|
||||
import {
|
||||
CustomIconButton,
|
||||
CustomTextField,
|
||||
|
@ -18,10 +37,68 @@ export default function AddStationModal({
|
|||
reset,
|
||||
} = useForm();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// Get vehicle names from the Redux store
|
||||
const vehicles = useSelector(
|
||||
(state: RootState) => state.vehicleReducer.vehicles
|
||||
); // Adjust according to your actual state structure
|
||||
const vehicleBrands = useSelector(
|
||||
(state: RootState) => state.vehicleReducer.vehicleBrands
|
||||
);
|
||||
|
||||
// State for selected vehicle brand and vehicles
|
||||
const [selectedBrands, setSelectedBrands] = useState<string[]>([]);
|
||||
const [selectedVehicles, setSelectedVehicles] = useState<string[]>([]);
|
||||
|
||||
// Fetch vehicle brands and vehicle list when the component mounts
|
||||
useEffect(() => {
|
||||
dispatch(fetchVehicleBrands());
|
||||
dispatch(vehicleList());
|
||||
}, [dispatch]);
|
||||
|
||||
// Filter vehicles based on selected vehicle brands
|
||||
const filteredVehicles = vehicles.filter((vehicle) =>
|
||||
selectedBrands.includes(vehicle.company)
|
||||
);
|
||||
|
||||
// Handle changes in selected vehicle brands (checkboxes)
|
||||
const handleBrandChange = (
|
||||
event: React.ChangeEvent<{ value: unknown }>
|
||||
) => {
|
||||
const value = event.target.value as string[];
|
||||
setSelectedBrands(value); // Update the selected vehicle brands
|
||||
};
|
||||
|
||||
// Handle changes in selected vehicles (checkboxes)
|
||||
const handleVehicleChange = (
|
||||
event: React.ChangeEvent<{ value: unknown }>
|
||||
) => {
|
||||
setSelectedVehicles(event.target.value as string[]);
|
||||
};
|
||||
|
||||
// Function to map selected vehicle names to corresponding vehicle ids
|
||||
const getVehicleIds = () => {
|
||||
return vehicles
|
||||
.filter((vehicle) => selectedVehicles.includes(vehicle.name))
|
||||
.map((vehicle) => vehicle.id);
|
||||
};
|
||||
|
||||
const onSubmit = (data: any) => {
|
||||
handleAddStation(data); // Add station to the list
|
||||
const vehicleIds = getVehicleIds(); // Get the ids of the selected vehicles
|
||||
|
||||
const payload = {
|
||||
...data,
|
||||
status: 1, // Default status
|
||||
allowedCarIds: vehicleIds, // Pass the vehicle ids to the backend
|
||||
totalSlots: Number(data.totalSlots), // Ensure this is a number
|
||||
};
|
||||
|
||||
handleAddStation(payload);
|
||||
handleClose(); // Close modal after adding
|
||||
reset();
|
||||
setSelectedVehicles([]);
|
||||
setSelectedBrands([]); // Reset selected brands after submission
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -77,7 +154,6 @@ export default function AddStationModal({
|
|||
gap: 2,
|
||||
}}
|
||||
>
|
||||
{/* First Row - Two Inputs */}
|
||||
<Box sx={{ display: "flex", gap: 2 }}>
|
||||
<Box
|
||||
sx={{
|
||||
|
@ -175,6 +251,114 @@ export default function AddStationModal({
|
|||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Vehicle Brand Dropdown with Checkboxes */}
|
||||
<Box sx={{ display: "flex", gap: 2 }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" fontWeight={500}>
|
||||
Select Vehicle Brands
|
||||
</Typography>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Choose Brands</InputLabel>
|
||||
<Select
|
||||
multiple
|
||||
value={selectedBrands}
|
||||
onChange={handleBrandChange}
|
||||
renderValue={(selected) =>
|
||||
(selected as string[]).join(", ")
|
||||
}
|
||||
label="Choose Brands"
|
||||
>
|
||||
{vehicleBrands.length > 0 ? (
|
||||
vehicleBrands.map((brand) => (
|
||||
<MenuItem
|
||||
key={brand.id}
|
||||
value={brand.id}
|
||||
>
|
||||
<Checkbox
|
||||
checked={selectedBrands.includes(
|
||||
brand.id
|
||||
)}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={brand.name}
|
||||
/>
|
||||
</MenuItem>
|
||||
))
|
||||
) : (
|
||||
<MenuItem disabled>
|
||||
No vehicle brands available
|
||||
</MenuItem>
|
||||
)}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
{errors.vehicleBrand
|
||||
? errors.vehicleBrand.message
|
||||
: ""}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Box>
|
||||
|
||||
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" fontWeight={500}>
|
||||
Vehicle Name
|
||||
</Typography>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Choose Vehicles</InputLabel>
|
||||
<Select
|
||||
multiple
|
||||
value={selectedVehicles}
|
||||
onChange={handleVehicleChange}
|
||||
renderValue={(selected) =>
|
||||
(selected as string[]).join(", ")
|
||||
}
|
||||
>
|
||||
{filteredVehicles.length > 0 ? (
|
||||
filteredVehicles.map((vehicle) => (
|
||||
<MenuItem
|
||||
key={vehicle.id}
|
||||
value={vehicle.name}
|
||||
>
|
||||
<Checkbox
|
||||
checked={selectedVehicles.includes(
|
||||
vehicle.name
|
||||
)}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={vehicle.name}
|
||||
/>
|
||||
</MenuItem>
|
||||
))
|
||||
) : (
|
||||
<MenuItem disabled>
|
||||
No vehicles available for the
|
||||
selected brands
|
||||
</MenuItem>
|
||||
)}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
{errors.vehicleName
|
||||
? errors.vehicleName.message
|
||||
: ""}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Submit Button */}
|
||||
|
|
|
@ -83,6 +83,36 @@ export default function AddVehicleModal({
|
|||
>
|
||||
{/* First Row - Two Inputs */}
|
||||
<Box sx={{ display: "flex", gap: 2 }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" fontWeight={500}>
|
||||
Company Name
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
placeholder="Enter Company Name"
|
||||
size="small"
|
||||
error={!!errors.company}
|
||||
helperText={
|
||||
errors.company
|
||||
? errors.company.message
|
||||
: ""
|
||||
}
|
||||
{...register("company", {
|
||||
required: "Company is required",
|
||||
minLength: {
|
||||
value: 3,
|
||||
message:
|
||||
"Company must be at least 5 characters long",
|
||||
},
|
||||
})}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
|
@ -121,37 +151,6 @@ export default function AddVehicleModal({
|
|||
})}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" fontWeight={500}>
|
||||
Company
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
placeholder="Enter Company Name"
|
||||
size="small"
|
||||
error={!!errors.company}
|
||||
helperText={
|
||||
errors.company
|
||||
? errors.company.message
|
||||
: ""
|
||||
}
|
||||
{...register("company", {
|
||||
required: "Company is required",
|
||||
minLength: {
|
||||
value: 5,
|
||||
message:
|
||||
"Company must be at least 5 characters long",
|
||||
},
|
||||
})}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Second Row - Two Inputs */}
|
||||
|
|
108
src/components/AvailableSlotModal/index.tsx
Normal file
108
src/components/AvailableSlotModal/index.tsx
Normal file
|
@ -0,0 +1,108 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
Box,
|
||||
CircularProgress,
|
||||
} from "@mui/material";
|
||||
import { styled } from "@mui/system";
|
||||
import {
|
||||
fetchAvailableSlots,
|
||||
availableSlots,
|
||||
} from "../../redux/slices/slotSlice"; // Update with the correct import path
|
||||
|
||||
const SlotButton = styled(Button)<{ selected: boolean }>(({ selected }) => ({
|
||||
backgroundColor: selected ? "#1976d2" : "#333",
|
||||
color: "#fff",
|
||||
border: "1px solid #555",
|
||||
width: "120px",
|
||||
height: "40px",
|
||||
margin: "5px",
|
||||
"&:hover": {
|
||||
backgroundColor: selected ? "#1565c0" : "#444",
|
||||
},
|
||||
}));
|
||||
|
||||
const AvailableSlotsModal = ({
|
||||
open,
|
||||
onClose,
|
||||
}: {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// Fetch slots from the Redux store
|
||||
const { availableSlots, loading, error } = useSelector(
|
||||
(state: any) => state.slotReducer.availableSlots
|
||||
); // Adjust selector path
|
||||
const [selectedSlot, setSelectedSlot] = useState<availableSlots | null>(
|
||||
null
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
// Fetch available slots when the modal opens
|
||||
dispatch(fetchAvailableSlots());
|
||||
}
|
||||
}, [open, dispatch]);
|
||||
|
||||
const handleSlotSelect = (slot: availableSlots) => {
|
||||
setSelectedSlot(slot); // Update the selected slot
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
// Save the selected slot (you can dispatch an action here if needed)
|
||||
console.log("Selected Slot: ", selectedSlot);
|
||||
onClose(); // Close the modal after saving
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose}>
|
||||
<DialogTitle sx={{ color: "#fff", background: "#222" }}>
|
||||
Available Slots
|
||||
</DialogTitle>
|
||||
<Box
|
||||
sx={{
|
||||
background: "#111",
|
||||
padding: "20px",
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
<CircularProgress color="primary" />
|
||||
) : error ? (
|
||||
<div style={{ color: "red" }}>Error: {error}</div>
|
||||
) : Array.isArray(availableSlots) &&
|
||||
availableSlots.length > 0 ? (
|
||||
availableSlots.map((slot: availableSlots) => (
|
||||
<SlotButton
|
||||
key={slot?.id}
|
||||
onClick={() => handleSlotSelect(slot)}
|
||||
selected={selectedSlot?.id === slot?.id}
|
||||
>
|
||||
{`${slot?.startTime} - ${slot?.endTime}`}
|
||||
</SlotButton>
|
||||
))
|
||||
) : (
|
||||
<div>No available slots</div>
|
||||
)}
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
sx={{ marginTop: "10px" }}
|
||||
onClick={handleSave}
|
||||
disabled={!selectedSlot}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Box>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default AvailableSlotsModal;
|
|
@ -34,13 +34,13 @@ import { CustomIconButton } from "../AddEditUserModel/styled.css.tsx";
|
|||
import ManagerViewModal from "../Modals/ViewManagerModal";
|
||||
import UserViewModal from "../Modals/UserViewModal/index.tsx";
|
||||
import { deleteUser, userList } from "../../redux/slices/userSlice.ts";
|
||||
import { deleteStation } from "../../redux/slices/stationSlice.ts";
|
||||
import { deleteStation, stationList } from "../../redux/slices/stationSlice.ts";
|
||||
import StationViewModal from "../Modals/StationViewModal/index.tsx";
|
||||
import {
|
||||
deleteSlot,
|
||||
fetchAvailableSlots,
|
||||
} from "../../redux/slices/slotSlice.ts";
|
||||
import { bookingList } from "../../redux/slices/bookSlice.ts";
|
||||
import { bookingList, deleteBooking } from "../../redux/slices/bookSlice.ts";
|
||||
// Styled components for customization
|
||||
const StyledTableCell = styled(TableCell)(({ theme }) => ({
|
||||
[`&.${tableCellClasses.head}`]: {
|
||||
|
@ -83,10 +83,9 @@ interface CustomTableProps {
|
|||
viewModal: boolean;
|
||||
setViewModal: Function;
|
||||
deleteModal: boolean;
|
||||
handleStatusToggle: (id: string, currentStatus: number) => void;
|
||||
handleStatusToggle?: (id: string, currentStatus: number) => void;
|
||||
tableType: string; // Adding tableType prop to change header text dynamically
|
||||
handleClickOpen: () => void;
|
||||
//handleDeleteButton: (id: string | number | undefined) => void;
|
||||
}
|
||||
|
||||
const CustomTable: React.FC<CustomTableProps> = ({
|
||||
|
@ -108,11 +107,8 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
|||
const [searchQuery, setSearchQuery] = React.useState("");
|
||||
const [currentPage, setCurrentPage] = React.useState(1);
|
||||
const usersPerPage = 10;
|
||||
const { user, isLoading } = useSelector(
|
||||
(state: RootState) => state?.profileReducer
|
||||
);
|
||||
const { user } = useSelector((state: RootState) => state?.profileReducer);
|
||||
const open = Boolean(anchorEl);
|
||||
console.log("Rows", rows);
|
||||
const handleClick = (event: React.MouseEvent<HTMLElement>, row: Row) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
setSelectedRow(row);
|
||||
|
@ -137,7 +133,6 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
|||
}
|
||||
|
||||
switch (tableType) {
|
||||
|
||||
case "admin":
|
||||
dispatch(deleteAdmin(id || ""));
|
||||
break;
|
||||
|
@ -156,6 +151,9 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
|||
case "slots":
|
||||
dispatch(deleteSlot(id || ""));
|
||||
break;
|
||||
case "booking":
|
||||
dispatch(deleteBooking(id || ""));
|
||||
break;
|
||||
default:
|
||||
console.error("Unknown table type:", tableType);
|
||||
return;
|
||||
|
@ -185,6 +183,9 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
|||
case "slots":
|
||||
dispatch(fetchAvailableSlots());
|
||||
break;
|
||||
case "station":
|
||||
dispatch(stationList());
|
||||
break;
|
||||
default:
|
||||
console.error("Unknown table type:", tableType);
|
||||
return;
|
||||
|
@ -197,7 +198,7 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
|||
if (selectedRow) {
|
||||
// Toggle the opposite of current status
|
||||
const newStatus = selectedRow.statusValue === 1 ? 0 : 1;
|
||||
handleStatusToggle(selectedRow.id, newStatus);
|
||||
handleStatusToggle?.(selectedRow.id, newStatus);
|
||||
}
|
||||
handleClose();
|
||||
};
|
||||
|
@ -214,7 +215,7 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
|||
const currentRows = filteredRows.slice(indexOfFirstRow, indexOfLastRow);
|
||||
|
||||
const handlePageChange = (
|
||||
event: React.ChangeEvent<unknown>,
|
||||
_event: React.ChangeEvent<unknown>,
|
||||
value: number
|
||||
) => {
|
||||
setCurrentPage(value);
|
||||
|
@ -239,30 +240,35 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
|||
}}
|
||||
>
|
||||
{/* Dynamic title based on the page type */}
|
||||
{tableType === "admin"
|
||||
? "Admin"
|
||||
: tableType === "role"
|
||||
? "Roles"
|
||||
: tableType === "user"
|
||||
? "Users"
|
||||
: tableType === "manager"
|
||||
? "Managers"
|
||||
: tableType === "vehicle"
|
||||
? "Vehicles"
|
||||
: tableType === "station"
|
||||
? "Charging Station"
|
||||
: tableType === "booking"
|
||||
? "Booking"
|
||||
: tableType === "slots"
|
||||
? "Slot"
|
||||
: "List"}
|
||||
{(() => {
|
||||
switch (tableType) {
|
||||
case "admin":
|
||||
return "Admin";
|
||||
case "role":
|
||||
return "Roles";
|
||||
case "user":
|
||||
return "Users";
|
||||
case "manager":
|
||||
return "Managers";
|
||||
case "vehicle":
|
||||
return "Vehicles";
|
||||
case "station":
|
||||
return "Charging Station";
|
||||
case "booking":
|
||||
return "Booking";
|
||||
case "slots":
|
||||
return "Slot";
|
||||
default:
|
||||
return "List";
|
||||
}
|
||||
})()}
|
||||
</Typography>
|
||||
|
||||
{/* Search & Buttons Section */}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "16px",
|
||||
|
||||
marginTop: "16px",
|
||||
alignItems: "center",
|
||||
}}
|
||||
|
@ -277,6 +283,11 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
|||
backgroundColor: "#272727",
|
||||
"& .MuiOutlinedInput-root": {
|
||||
borderRadius: "12px",
|
||||
width: "422px",
|
||||
height: "44px",
|
||||
borderWidth: "1px",
|
||||
padding: "14px 12px 14px 12px",
|
||||
gap: "16px",
|
||||
"& fieldset": { borderColor: "#FFFFFF" },
|
||||
"&:hover fieldset": { borderColor: "#FFFFFF" },
|
||||
"&.Mui-focused fieldset": {
|
||||
|
@ -312,28 +323,34 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
|||
color: "white",
|
||||
borderRadius: "8px",
|
||||
width: "184px",
|
||||
marginRight: "16px",
|
||||
"&:hover": { backgroundColor: "#439BC1" },
|
||||
}}
|
||||
onClick={() => handleClickOpen()}
|
||||
>
|
||||
Add{" "}
|
||||
{tableType === "admin"
|
||||
? "Admin"
|
||||
: tableType === "role"
|
||||
? "Role"
|
||||
: tableType === "user"
|
||||
? "User"
|
||||
: tableType === "manager"
|
||||
? "Manager"
|
||||
: tableType === "vehicle"
|
||||
? "Vehicle"
|
||||
: tableType === "station"
|
||||
? "Charging Station"
|
||||
: tableType === "booking"
|
||||
? "Booking"
|
||||
: tableType === "slots"
|
||||
? "Slot"
|
||||
: "Item"}
|
||||
{(() => {
|
||||
switch (tableType) {
|
||||
case "admin":
|
||||
return "Admin";
|
||||
case "role":
|
||||
return "Role";
|
||||
case "user":
|
||||
return "User";
|
||||
case "manager":
|
||||
return "Manager";
|
||||
case "vehicle":
|
||||
return "Vehicle";
|
||||
case "station":
|
||||
return "Charging Station";
|
||||
case "booking":
|
||||
return "Booking";
|
||||
case "slots":
|
||||
return "Slot";
|
||||
default:
|
||||
return "Item";
|
||||
}
|
||||
})()}
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
|
@ -343,15 +360,17 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
|||
width: "44px",
|
||||
height: "44px",
|
||||
borderRadius: "8px",
|
||||
backgroundColor: "#272727",
|
||||
backgroundColor: "#1C1C1C",
|
||||
color: "#52ACDF",
|
||||
"&:hover": { backgroundColor: "#333333" },
|
||||
"*:where([data-mui-color-scheme='dark']) &": {
|
||||
backgroundColor: "#1C1C1C",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<TuneIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
||||
{/* Table Section */}
|
||||
<TableContainer
|
||||
component={Paper}
|
||||
|
@ -367,6 +386,10 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
|||
sx={{
|
||||
backgroundColor: "#272727",
|
||||
borderBottom: "none",
|
||||
".css-1ex4ubw-MuiTableCell-root.MuiTableCell-head ":
|
||||
{
|
||||
backgroundColor: "#272727",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{" "}
|
||||
|
@ -384,7 +407,13 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
|||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<TableBody
|
||||
sx={{
|
||||
".MuiTableCell-root": {
|
||||
backgroundColor: "#1C1C1C",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{currentRows.map((row, rowIndex) => (
|
||||
<StyledTableRow key={rowIndex}>
|
||||
{columns.map((column) => (
|
||||
|
@ -433,7 +462,6 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
|||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
{/* Pagination */}
|
||||
<Box
|
||||
sx={{
|
||||
|
@ -467,7 +495,6 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
|||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Menu Actions */}
|
||||
{open && (
|
||||
<Menu
|
||||
|
@ -476,8 +503,14 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
|||
open={open}
|
||||
onClose={handleClose}
|
||||
onClick={handleClose}
|
||||
transformOrigin={{ horizontal: "right", vertical: "top" }}
|
||||
anchorOrigin={{ horizontal: "right", vertical: "bottom" }}
|
||||
transformOrigin={{
|
||||
horizontal: "right",
|
||||
vertical: "top",
|
||||
}}
|
||||
anchorOrigin={{
|
||||
horizontal: "right",
|
||||
vertical: "bottom",
|
||||
}}
|
||||
sx={{
|
||||
[`& .${paperClasses.root}`]: {
|
||||
padding: 0,
|
||||
|
@ -631,7 +664,6 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
|||
</Box>
|
||||
</Menu>
|
||||
)}
|
||||
|
||||
{/* Modals */}
|
||||
{deleteModal && (
|
||||
<DeleteModal
|
||||
|
|
|
@ -14,11 +14,19 @@ import {
|
|||
CustomIconButton,
|
||||
CustomTextField,
|
||||
} from "../AddEditUserModel/styled.css.tsx"; // Custom styled components
|
||||
import { AppDispatch } from "../../redux/store/store.ts";
|
||||
|
||||
interface EditManagerModalProps {
|
||||
open: boolean;
|
||||
handleClose: () => void;
|
||||
editRow: any; // Manager data including id
|
||||
handleUpdate: (
|
||||
id: string,
|
||||
name: string,
|
||||
email: string,
|
||||
phone: string,
|
||||
stationId: string
|
||||
) => Promise<void>;
|
||||
editRow: any;
|
||||
}
|
||||
|
||||
interface FormData {
|
||||
|
@ -33,7 +41,7 @@ const EditManagerModal: React.FC<EditManagerModalProps> = ({
|
|||
handleClose,
|
||||
editRow,
|
||||
}) => {
|
||||
const dispatch = useDispatch(); // Use dispatch to send Redux actions
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
|
|
267
src/components/EditSlotModal/index.tsx
Normal file
267
src/components/EditSlotModal/index.tsx
Normal file
|
@ -0,0 +1,267 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Typography,
|
||||
Modal,
|
||||
CircularProgress,
|
||||
} from "@mui/material";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { updateSlot, fetchAvailableSlots } from "../../redux/slices/slotSlice"; // Update with correct action
|
||||
import {
|
||||
CustomIconButton,
|
||||
CustomTextField,
|
||||
} from "../AddEditUserModel/styled.css.tsx"; // Custom styled components
|
||||
import { AppDispatch } from "../../redux/store/store.ts";
|
||||
|
||||
interface EditSlotModalProps {
|
||||
open: boolean;
|
||||
handleClose: () => void;
|
||||
handleUpdate: (
|
||||
id: string,
|
||||
startTime: string,
|
||||
endTime: string,
|
||||
isAvailable: boolean
|
||||
) => Promise<void>;
|
||||
editRow: any; // Slot data including id
|
||||
}
|
||||
|
||||
interface FormData {
|
||||
date: string;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
isAvailable: boolean;
|
||||
}
|
||||
|
||||
const EditSlotModal: React.FC<EditSlotModalProps> = ({
|
||||
open,
|
||||
handleClose,
|
||||
editRow,
|
||||
}) => {
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
setValue,
|
||||
reset,
|
||||
} = useForm<FormData>({
|
||||
defaultValues: {
|
||||
date: "",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
isAvailable: false,
|
||||
},
|
||||
});
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isAvailable, setIsAvailable] = useState<boolean>(
|
||||
editRow?.isAvailable || false
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (editRow) {
|
||||
setValue("startTime", editRow.startTime);
|
||||
setValue("endTime", editRow.endTime);
|
||||
setIsAvailable(editRow.isAvailable);
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
}, [editRow, setValue, reset]);
|
||||
|
||||
const onSubmit = async (data: FormData) => {
|
||||
if (editRow) {
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
|
||||
const availabilityStatus = isAvailable ? true : false;
|
||||
|
||||
await dispatch(
|
||||
updateSlot({
|
||||
id: editRow.id, // Slot ID// date: data.date,
|
||||
startTime: data.startTime,
|
||||
endTime: data.endTime,
|
||||
isAvailable: availabilityStatus,
|
||||
})
|
||||
).unwrap();
|
||||
dispatch(fetchAvailableSlots());
|
||||
handleClose(); // Close modal on success
|
||||
reset(); // Reset form fields after submit
|
||||
} catch (error) {
|
||||
console.error("Error updating slot:", error);
|
||||
// Handle the error or show a toast message
|
||||
} finally {
|
||||
setLoading(false); // Stop loading state
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={(e, reason) => {
|
||||
if (reason === "backdropClick") {
|
||||
return;
|
||||
}
|
||||
handleClose(); // Close modal when clicking cross or cancel
|
||||
}}
|
||||
aria-labelledby="edit-slot-modal"
|
||||
>
|
||||
<Box
|
||||
component="form"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
width: 400,
|
||||
bgcolor: "background.paper",
|
||||
boxShadow: 24,
|
||||
p: 3,
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" fontWeight={600}>
|
||||
Edit Slot
|
||||
</Typography>
|
||||
<CustomIconButton onClick={handleClose}>
|
||||
<CloseIcon />
|
||||
</CustomIconButton>
|
||||
</Box>
|
||||
|
||||
{/* Horizontal Line */}
|
||||
<Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
|
||||
|
||||
{/* Input Fields */}
|
||||
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 2 }}>
|
||||
{/* Date */}
|
||||
{/* <Box sx={{ flex: "1 1 100%" }}>
|
||||
<Typography variant="body2" fontWeight={500} mb={0.5}>
|
||||
Date
|
||||
</Typography>
|
||||
<Controller
|
||||
name="date"
|
||||
control={control}
|
||||
rules={{ required: "Date is required" }}
|
||||
render={({ field }) => (
|
||||
<CustomTextField
|
||||
{...field}
|
||||
type="date"
|
||||
fullWidth
|
||||
size="small"
|
||||
error={!!errors.date}
|
||||
helperText={errors.date?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box> */}
|
||||
|
||||
{/* Start Time */}
|
||||
<Box sx={{ flex: "1 1 48%" }}>
|
||||
<Typography variant="body2" fontWeight={500} mb={0.5}>
|
||||
Start Time
|
||||
</Typography>
|
||||
<Controller
|
||||
name="startTime"
|
||||
control={control}
|
||||
rules={{ required: "Start Time is required" }}
|
||||
render={({ field }) => (
|
||||
<CustomTextField
|
||||
{...field}
|
||||
type="time"
|
||||
fullWidth
|
||||
size="small"
|
||||
error={!!errors.startTime}
|
||||
helperText={errors.startTime?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* End Time */}
|
||||
<Box sx={{ flex: "1 1 48%" }}>
|
||||
<Typography variant="body2" fontWeight={500} mb={0.5}>
|
||||
End Time
|
||||
</Typography>
|
||||
<Controller
|
||||
name="endTime"
|
||||
control={control}
|
||||
rules={{ required: "End Time is required" }}
|
||||
render={({ field }) => (
|
||||
<CustomTextField
|
||||
{...field}
|
||||
type="time"
|
||||
fullWidth
|
||||
size="small"
|
||||
error={!!errors.endTime}
|
||||
helperText={errors.endTime?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Availability Toggle */}
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
gap={2}
|
||||
sx={{ flex: "1 1 100%" }}
|
||||
>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const newAvailability = !isAvailable;
|
||||
setIsAvailable(newAvailability); // Update local state
|
||||
setValue("isAvailable", newAvailability); // Update form value for isAvailable
|
||||
}}
|
||||
variant={isAvailable ? "contained" : "outlined"}
|
||||
color="primary"
|
||||
>
|
||||
{isAvailable ? "Available" : "Not Available"}
|
||||
</Button>
|
||||
<Typography>
|
||||
{isAvailable ? "Available" : "Not Available"}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Submit Button */}
|
||||
<Box
|
||||
sx={{ display: "flex", justifyContent: "flex-end", mt: 3 }}
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
sx={{
|
||||
backgroundColor: "#52ACDF",
|
||||
color: "white",
|
||||
borderRadius: "8px",
|
||||
width: "117px",
|
||||
"&:hover": { backgroundColor: "#439BC1" },
|
||||
}}
|
||||
disabled={loading} // Disable the button during loading state
|
||||
>
|
||||
{loading ? (
|
||||
<CircularProgress size={24} color="inherit" />
|
||||
) : (
|
||||
"Update Slot"
|
||||
)}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditSlotModal;
|
|
@ -1,11 +1,37 @@
|
|||
import React, { useEffect } from "react";
|
||||
import { Box, Button, Typography, Modal } from "@mui/material";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Typography,
|
||||
Modal,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem,
|
||||
Checkbox,
|
||||
ListItemText,
|
||||
FormHelperText,
|
||||
} from "@mui/material";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { RootState } from "../../redux/reducers.ts";
|
||||
import {
|
||||
fetchVehicleBrands,
|
||||
vehicleList,
|
||||
} from "../../redux/slices/VehicleSlice";
|
||||
import {
|
||||
CustomIconButton,
|
||||
CustomTextField,
|
||||
} from "../AddEditUserModel/styled.css.tsx";
|
||||
} from "../AddEditUserModel/styled.css.tsx"; // Assuming custom styled components
|
||||
|
||||
interface FormData {
|
||||
name: string;
|
||||
registeredAddress: string;
|
||||
totalSlots: number;
|
||||
allowedCarIds: string[];
|
||||
status: number;
|
||||
}
|
||||
|
||||
interface EditStationModalProps {
|
||||
open: boolean;
|
||||
|
@ -15,16 +41,9 @@ interface EditStationModalProps {
|
|||
name: string,
|
||||
registeredAddress: string,
|
||||
totalSlots: number,
|
||||
status: number
|
||||
allowedCarIds: number[]
|
||||
) => void;
|
||||
editRow: any;
|
||||
}
|
||||
|
||||
interface FormData {
|
||||
name: string;
|
||||
registeredAddress: string;
|
||||
totalSlots: number;
|
||||
status: number;
|
||||
editRow?: any;
|
||||
}
|
||||
|
||||
const EditStationModal: React.FC<EditStationModalProps> = ({
|
||||
|
@ -36,42 +55,85 @@ const EditStationModal: React.FC<EditStationModalProps> = ({
|
|||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
setValue,
|
||||
reset,
|
||||
formState: { errors },
|
||||
} = useForm<FormData>({
|
||||
defaultValues: {
|
||||
name: "",
|
||||
registeredAddress: "",
|
||||
totalSlots: 0,
|
||||
status: 1,
|
||||
allowedCarIds: [],
|
||||
},
|
||||
});
|
||||
|
||||
// Set values if editRow is provided
|
||||
const dispatch = useDispatch();
|
||||
const vehicles = useSelector(
|
||||
(state: RootState) => state.vehicleReducer.vehicles
|
||||
);
|
||||
const vehicleBrands = useSelector(
|
||||
(state: RootState) => state.vehicleReducer.vehicleBrands
|
||||
);
|
||||
|
||||
const [selectedBrands, setSelectedBrands] = useState<string[]>([]);
|
||||
const [selectedVehicles, setSelectedVehicles] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchVehicleBrands());
|
||||
dispatch(vehicleList()); // Fetch vehicles when the component mounts
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (editRow) {
|
||||
setValue("name", editRow.name);
|
||||
setValue("registeredAddress", editRow.registeredAddress);
|
||||
setValue("totalSlots", editRow.totalSlots);
|
||||
setValue("status", editRow.number);
|
||||
setValue("status", editRow.status);
|
||||
setValue("allowedCarIds", editRow.allowedCarIds || []);
|
||||
setSelectedVehicles(editRow.allowedCarIds || []);
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
}, [editRow, setValue, reset]);
|
||||
|
||||
// Filter vehicles based on selected brands
|
||||
const filteredVehicles = vehicles.filter((vehicle) =>
|
||||
selectedBrands.includes(vehicle.company)
|
||||
);
|
||||
|
||||
// Handle changes in vehicle brand selection
|
||||
const handleBrandChange = (
|
||||
event: React.ChangeEvent<{ value: unknown }>
|
||||
) => {
|
||||
setSelectedBrands(event.target.value as string[]);
|
||||
};
|
||||
|
||||
// Handle changes in vehicle selection
|
||||
const handleCheckboxChange = (
|
||||
event: React.ChangeEvent<{ value: unknown }>
|
||||
) => {
|
||||
const value = event.target.value as string[];
|
||||
setSelectedVehicles(value);
|
||||
setValue("allowedCarIds", value); // Update allowedCarIds in form state
|
||||
};
|
||||
|
||||
const onSubmit = (data: FormData) => {
|
||||
if (editRow) {
|
||||
handleUpdate(
|
||||
editRow.id,
|
||||
data.name,
|
||||
data.registeredAddress,
|
||||
data.totalSlots,
|
||||
data.status
|
||||
);
|
||||
}
|
||||
handleClose(); // Close the modal
|
||||
reset(); // Reset the form fields
|
||||
const vehicleIds = vehicles
|
||||
.filter((vehicle) => selectedVehicles.includes(vehicle.name))
|
||||
.map((vehicle) => vehicle.id);
|
||||
|
||||
handleUpdate(
|
||||
editRow.id,
|
||||
data.name,
|
||||
data.registeredAddress,
|
||||
data.totalSlots,
|
||||
vehicleIds
|
||||
);
|
||||
handleClose();
|
||||
reset();
|
||||
//setSelectedBrands([]); // Reset brands after submit
|
||||
setSelectedVehicles([]); // Reset selected vehicles
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -81,7 +143,7 @@ const EditStationModal: React.FC<EditStationModalProps> = ({
|
|||
if (reason === "backdropClick") {
|
||||
return;
|
||||
}
|
||||
handleClose(); // Close modal when clicking cross or cancel
|
||||
handleClose();
|
||||
}}
|
||||
aria-labelledby="edit-station-modal"
|
||||
>
|
||||
|
@ -119,9 +181,9 @@ const EditStationModal: React.FC<EditStationModalProps> = ({
|
|||
{/* Horizontal Line */}
|
||||
<Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
|
||||
|
||||
{/* Input Fields */}
|
||||
{/* Form */}
|
||||
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
|
||||
{/* First Row - Two Inputs */}
|
||||
{/* Station Name and Address */}
|
||||
<Box sx={{ display: "flex", gap: 2 }}>
|
||||
<Box
|
||||
sx={{
|
||||
|
@ -140,19 +202,6 @@ const EditStationModal: React.FC<EditStationModalProps> = ({
|
|||
<Controller
|
||||
name="name"
|
||||
control={control}
|
||||
rules={{
|
||||
required: "Station Name is required",
|
||||
minLength: {
|
||||
value: 3,
|
||||
message:
|
||||
"Minimum 3 characters required",
|
||||
},
|
||||
maxLength: {
|
||||
value: 30,
|
||||
message:
|
||||
"Maximum 30 characters allowed",
|
||||
},
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<CustomTextField
|
||||
{...field}
|
||||
|
@ -160,12 +209,15 @@ const EditStationModal: React.FC<EditStationModalProps> = ({
|
|||
placeholder="Enter Station Name"
|
||||
size="small"
|
||||
error={!!errors.name}
|
||||
helperText={errors.name?.message}
|
||||
helperText={
|
||||
errors.name
|
||||
? errors.name.message
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
|
@ -183,9 +235,6 @@ const EditStationModal: React.FC<EditStationModalProps> = ({
|
|||
<Controller
|
||||
name="registeredAddress"
|
||||
control={control}
|
||||
rules={{
|
||||
required: "Registered Address is required",
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<CustomTextField
|
||||
{...field}
|
||||
|
@ -194,7 +243,10 @@ const EditStationModal: React.FC<EditStationModalProps> = ({
|
|||
size="small"
|
||||
error={!!errors.registeredAddress}
|
||||
helperText={
|
||||
errors.registeredAddress?.message
|
||||
errors.registeredAddress
|
||||
? errors.registeredAddress
|
||||
.message
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
@ -202,7 +254,7 @@ const EditStationModal: React.FC<EditStationModalProps> = ({
|
|||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Second Row - Total Slots */}
|
||||
{/* Total Slots */}
|
||||
<Box sx={{ display: "flex", gap: 2 }}>
|
||||
<Box
|
||||
sx={{
|
||||
|
@ -221,13 +273,6 @@ const EditStationModal: React.FC<EditStationModalProps> = ({
|
|||
<Controller
|
||||
name="totalSlots"
|
||||
control={control}
|
||||
rules={{
|
||||
required: "Total Slots are required",
|
||||
min: {
|
||||
value: 1,
|
||||
message: "At least 1 slot is required",
|
||||
},
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<CustomTextField
|
||||
{...field}
|
||||
|
@ -236,15 +281,115 @@ const EditStationModal: React.FC<EditStationModalProps> = ({
|
|||
size="small"
|
||||
type="number"
|
||||
error={!!errors.totalSlots}
|
||||
helperText={errors.totalSlots?.message}
|
||||
helperText={
|
||||
errors.totalSlots
|
||||
? errors.totalSlots.message
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Vehicle Brand Selection with Checkboxes */}
|
||||
<Box sx={{ display: "flex", gap: 2 }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" fontWeight={500}>
|
||||
Select Vehicle Brands
|
||||
</Typography>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Choose Brand</InputLabel>
|
||||
<Select
|
||||
multiple
|
||||
value={selectedBrands}
|
||||
onChange={handleBrandChange}
|
||||
label="Choose Brands"
|
||||
>
|
||||
{vehicleBrands.length > 0 ? (
|
||||
vehicleBrands.map((brand) => (
|
||||
<MenuItem
|
||||
key={brand.id}
|
||||
value={brand.id}
|
||||
>
|
||||
<Checkbox
|
||||
checked={selectedBrands.includes(
|
||||
brand.id
|
||||
)}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={brand.name}
|
||||
/>
|
||||
</MenuItem>
|
||||
))
|
||||
) : (
|
||||
<MenuItem disabled>
|
||||
No vehicle brands available
|
||||
</MenuItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" fontWeight={500}>
|
||||
Vehicle Name
|
||||
</Typography>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Choose Vehicles</InputLabel>
|
||||
<Select
|
||||
multiple
|
||||
value={selectedVehicles}
|
||||
onChange={handleCheckboxChange}
|
||||
renderValue={(selected) =>
|
||||
(selected as string[]).join(", ")
|
||||
}
|
||||
>
|
||||
{filteredVehicles.length > 0 ? (
|
||||
filteredVehicles.map((vehicle) => (
|
||||
<MenuItem
|
||||
key={vehicle.id}
|
||||
value={vehicle.name}
|
||||
>
|
||||
<Checkbox
|
||||
checked={selectedVehicles.includes(
|
||||
vehicle.name
|
||||
)}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={vehicle.name}
|
||||
/>
|
||||
</MenuItem>
|
||||
))
|
||||
) : (
|
||||
<MenuItem disabled>
|
||||
No vehicles available for this brand
|
||||
</MenuItem>
|
||||
)}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
{errors.allowedCarIds
|
||||
? errors.allowedCarIds.message
|
||||
: ""}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
|
||||
|
||||
{/* Submit Button */}
|
||||
<Box
|
||||
sx={{ display: "flex", justifyContent: "flex-end", mt: 3 }}
|
||||
|
@ -259,7 +404,7 @@ const EditStationModal: React.FC<EditStationModalProps> = ({
|
|||
"&:hover": { backgroundColor: "#439BC1" },
|
||||
}}
|
||||
>
|
||||
Update Charging Station
|
||||
Update Station
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
|
|
@ -15,22 +15,21 @@ import { AppDispatch, RootState } from "../../redux/store/store";
|
|||
import { fetchAdminProfile } from "../../redux/slices/profileSlice";
|
||||
import OptionsMenu from "../OptionsMenu";
|
||||
import NotificationsNoneIcon from "@mui/icons-material/NotificationsNone";
|
||||
import ColorModeIconDropdown from "../../shared-theme/ColorModeIconDropdown";
|
||||
|
||||
export default function Header() {
|
||||
const [showNotifications, setShowNotifications] = React.useState(false);
|
||||
const toggleNotifications = () => {
|
||||
setShowNotifications((prev) => !prev);
|
||||
};
|
||||
const [open, setOpen] = React.useState(true);
|
||||
const [open, setOpen] = React.useState(true);
|
||||
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
const { user } = useSelector(
|
||||
(state: RootState) => state?.profileReducer
|
||||
);
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
const { user } = useSelector((state: RootState) => state?.profileReducer);
|
||||
|
||||
React.useEffect(() => {
|
||||
dispatch(fetchAdminProfile());
|
||||
}, [dispatch]);
|
||||
React.useEffect(() => {
|
||||
dispatch(fetchAdminProfile());
|
||||
}, [dispatch]);
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
|
@ -89,7 +88,6 @@ export default function Header() {
|
|||
display: { xs: "none", sm: "flex" }, // Hide on mobile, show on larger screens
|
||||
}}
|
||||
>
|
||||
|
||||
<NotificationsNoneIcon onClick={toggleNotifications} />
|
||||
<Avatar
|
||||
alt="User Avatar"
|
||||
|
|
|
@ -7,7 +7,7 @@ import Typography from "@mui/material/Typography";
|
|||
import Stack from "@mui/material/Stack";
|
||||
import { LineChart } from "@mui/x-charts/LineChart";
|
||||
import { Box } from "@mui/material";
|
||||
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
function AreaGradient({ color, id }: { color: string; id: string }) {
|
||||
return (
|
||||
<defs>
|
||||
|
@ -43,7 +43,14 @@ export default function LineChartCard() {
|
|||
return (
|
||||
<Card
|
||||
variant="outlined"
|
||||
sx={{ width: "100%", height: "100%", backgroundColor: "#202020" }}
|
||||
sx={{
|
||||
width: "553px",
|
||||
height: "444px",
|
||||
borderRadius: "16px",
|
||||
"*:where([data-mui-color-scheme='dark']) &": {
|
||||
backgroundColor: "#202020",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<div
|
||||
|
@ -74,29 +81,39 @@ export default function LineChartCard() {
|
|||
marginLeft: "auto",
|
||||
marginRight: "16px",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
p: 1.5,
|
||||
borderRadius: "8px",
|
||||
border: "1px solid #454545",
|
||||
|
||||
padding: "4px 8px",
|
||||
color: "#F2F2F2",
|
||||
|
||||
width: "129px",
|
||||
height: "44px",
|
||||
padding: "12px 16px",
|
||||
gap: "8px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
width={"69px"}
|
||||
height={"16px"}
|
||||
sx={{
|
||||
fontFamily: "Gilroy",
|
||||
fontWeight: 500,
|
||||
fontSize: "14px",
|
||||
lineHeight: "24px",
|
||||
color: "#F2F2F2",
|
||||
p: "4px",
|
||||
color: "#D9D8D8",
|
||||
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
Weekly
|
||||
</Typography>
|
||||
<ArrowDropDownIcon sx={{ color: "#F2F2F2" }} />
|
||||
<ExpandMoreIcon
|
||||
width="20px"
|
||||
height="20px"
|
||||
|
||||
sx={{ color: "#F2F2F2" }}
|
||||
/>
|
||||
</Box>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ export default function MainGrid() {
|
|||
</Typography>
|
||||
<Grid
|
||||
container
|
||||
spacing={2}
|
||||
spacing={3}
|
||||
columns={12}
|
||||
|
||||
// sx={{ mb: (theme) => theme.spacing(2) }}
|
||||
|
|
|
@ -12,7 +12,12 @@ import { useSelector } from "react-redux";
|
|||
import { RootState } from "../../redux/store/store";
|
||||
import DashboardOutlinedIcon from "@mui/icons-material/DashboardOutlined";
|
||||
import ManageAccountsOutlinedIcon from "@mui/icons-material/ManageAccountsOutlined";
|
||||
|
||||
import EvStationOutlinedIcon from "@mui/icons-material/EvStationOutlined";
|
||||
import EvStationIcon from "@mui/icons-material/EvStation";
|
||||
import BookOnlineOutlinedIcon from "@mui/icons-material/BookOnlineOutlined";
|
||||
import ChecklistSharpIcon from "@mui/icons-material/ChecklistSharp";
|
||||
import AnalyticsOutlinedIcon from "@mui/icons-material/AnalyticsOutlined";
|
||||
import PeopleOutlinedIcon from "@mui/icons-material/PeopleOutlined";
|
||||
type PropType = {
|
||||
hidden: boolean;
|
||||
};
|
||||
|
@ -31,53 +36,53 @@ export default function MenuContent({ hidden }: PropType) {
|
|||
},
|
||||
userRole === "superadmin" && {
|
||||
text: "Admins",
|
||||
icon: <AnalyticsRoundedIcon />,
|
||||
icon: <ManageAccountsOutlinedIcon />,
|
||||
url: "/panel/admin-list",
|
||||
},
|
||||
userRole === "superadmin" && {
|
||||
text: "Roles",
|
||||
icon: <AnalyticsRoundedIcon />,
|
||||
icon: <AnalyticsOutlinedIcon />,
|
||||
url: "/panel/role-list",
|
||||
},
|
||||
userRole === "admin" && {
|
||||
text: "Users",
|
||||
icon: <AnalyticsRoundedIcon />,
|
||||
icon: <PeopleOutlinedIcon />,
|
||||
url: "/panel/user-list",
|
||||
},
|
||||
userRole === "admin" && {
|
||||
text: "Charging Stations",
|
||||
icon: <ManageAccountsOutlinedIcon />,
|
||||
icon: <EvStationIcon />,
|
||||
url: "/panel/station-list", // Placeholder for now
|
||||
},
|
||||
userRole === "admin" && {
|
||||
text: "Managers",
|
||||
icon: <ManageAccountsOutlinedIcon />,
|
||||
icon: <PeopleOutlinedIcon />,
|
||||
url: "/panel/manager-list", // Placeholder for now
|
||||
},
|
||||
|
||||
userRole === "admin" && {
|
||||
text: "Vehicles",
|
||||
icon: <ManageAccountsOutlinedIcon />,
|
||||
icon: <AnalyticsOutlinedIcon />,
|
||||
url: "/panel/vehicle-list", // Placeholder for now
|
||||
},
|
||||
// userRole === "manager" && {
|
||||
// text: "Add Slots",
|
||||
// icon: <ManageAccountsOutlinedIcon />,
|
||||
// url: "/panel/EVslots", // Placeholder for now
|
||||
// text: "Add Slots",
|
||||
// icon: <ManageAccountsOutlinedIcon />,
|
||||
// url: "/panel/EVslots", // Placeholder for now
|
||||
// },
|
||||
userRole === "user" && {
|
||||
text: "Bookings",
|
||||
icon: <ManageAccountsOutlinedIcon />,
|
||||
icon: <BookOnlineOutlinedIcon />,
|
||||
url: "/panel/booking-list", // Placeholder for now
|
||||
},
|
||||
userRole === "manager" && {
|
||||
text: "Available Slots",
|
||||
icon: <ManageAccountsOutlinedIcon />,
|
||||
icon: <ChecklistSharpIcon />,
|
||||
url: "/panel/slot-list", // Placeholder for now
|
||||
},
|
||||
userRole === "user" && {
|
||||
text: "Available Slots",
|
||||
icon: <ManageAccountsOutlinedIcon />,
|
||||
icon: <ChecklistSharpIcon />,
|
||||
url: "/panel/slot-list", // Placeholder for now
|
||||
},
|
||||
];
|
||||
|
@ -85,25 +90,33 @@ export default function MenuContent({ hidden }: PropType) {
|
|||
const filteredMenuItems = baseMenuItems.filter(Boolean);
|
||||
|
||||
return (
|
||||
<Stack sx={{ flexGrow: 1, p: 1, justifyContent: "space-between" }}>
|
||||
<Stack
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
p: 1,
|
||||
justifyContent: "space-between",
|
||||
backgroundColor: "#202020",
|
||||
}}
|
||||
>
|
||||
<List dense>
|
||||
{filteredMenuItems.map((item, index) => (
|
||||
<ListItem
|
||||
key={index}
|
||||
disablePadding
|
||||
sx={{ display: "block", py: 1 }}
|
||||
sx={{ display: "block", py: 1, px: 0.9 }}
|
||||
>
|
||||
<ListItemButton
|
||||
component={Link}
|
||||
to={item.url}
|
||||
selected={item.url === location.pathname}
|
||||
sx={{ alignItems: "center", columnGap: 1 }}
|
||||
sx={{ alignItems: "center", columnGap: 0.5 }}
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: "fit-content",
|
||||
".MuiSvgIcon-root": {
|
||||
fontSize: 24,
|
||||
color: "#FFFFFF",
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
@ -113,8 +126,14 @@ export default function MenuContent({ hidden }: PropType) {
|
|||
sx={{
|
||||
display: !hidden ? "none" : "",
|
||||
transition: "all 0.5s ease",
|
||||
|
||||
".MuiListItemText-primary": {
|
||||
width: "118px",
|
||||
height: "19px",
|
||||
fontSize: "16px",
|
||||
letterSpacing: "0%",
|
||||
lineHeight: "100%",
|
||||
color: "#D9D8D8",
|
||||
},
|
||||
}}
|
||||
primary={item.text}
|
||||
|
|
|
@ -8,7 +8,7 @@ type Props = {
|
|||
open: boolean;
|
||||
setViewModal: Function;
|
||||
handleView: (id: string | undefined) => void;
|
||||
id?: number | undefined;
|
||||
id?: string | undefined;
|
||||
};
|
||||
|
||||
const style = {
|
||||
|
|
|
@ -110,7 +110,8 @@ export default function ManagerViewModal({ open, setViewModal, id }: Props) {
|
|||
<Typography variant="body1">
|
||||
<strong>Station Location:</strong>
|
||||
<Typography variant="body2">
|
||||
{selectedManager.registeredAddress}
|
||||
{selectedManager.chargingStation
|
||||
?.registeredAddress || "Not Available"}
|
||||
</Typography>
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
@ -118,7 +119,8 @@ export default function ManagerViewModal({ open, setViewModal, id }: Props) {
|
|||
<Typography variant="body1">
|
||||
<strong>Station Name:</strong>
|
||||
<Typography variant="body2">
|
||||
{selectedManager.stationName}
|
||||
{selectedManager.chargingStation?.name ||
|
||||
"Not Available"}
|
||||
</Typography>
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
|
|
@ -7,6 +7,7 @@ import CloseIcon from "@mui/icons-material/Close";
|
|||
type Props = {
|
||||
open: boolean;
|
||||
setViewModal: Function;
|
||||
handleView: (id: string | undefined) => void;
|
||||
id?: string;
|
||||
};
|
||||
|
||||
|
|
|
@ -28,11 +28,15 @@ export default function ResourcePieChart() {
|
|||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "8px",
|
||||
gap: "12px",
|
||||
flexGrow: 1,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundColor: "#202020",
|
||||
width: "553px",
|
||||
height: "324px",
|
||||
padding: "16px",
|
||||
borderRadius: "16px",
|
||||
"*:where([data-mui-color-scheme='dark']) &": {
|
||||
backgroundColor: "#202020",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
|
@ -40,11 +44,13 @@ export default function ResourcePieChart() {
|
|||
component="h2"
|
||||
variant="subtitle2"
|
||||
color="#F2F2F2"
|
||||
width="84px"
|
||||
height="24px"
|
||||
sx={{
|
||||
fontFamily: "Gilroy",
|
||||
fontWeight: 500,
|
||||
fontSize: "18px",
|
||||
lineHeight: "24px",
|
||||
color: "#FFFFFF",
|
||||
}}
|
||||
>
|
||||
Resources
|
||||
|
@ -93,7 +99,12 @@ export default function ResourcePieChart() {
|
|||
borderRadius: "50%",
|
||||
}}
|
||||
/>
|
||||
<Typography variant="body2" color="#F2F2F2">
|
||||
<Typography
|
||||
variant="body2"
|
||||
width="100px"
|
||||
height="16px"
|
||||
color="#FFFFFF"
|
||||
>
|
||||
{entry.title}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
|
|
@ -5,15 +5,21 @@ import Typography from "@mui/material/Typography";
|
|||
import Box from "@mui/material/Box";
|
||||
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
|
||||
|
||||
|
||||
export default function SessionsChart() {
|
||||
return (
|
||||
<Card
|
||||
variant="outlined"
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundColor: "#202020",
|
||||
p: 2,
|
||||
width: "553px",
|
||||
height: "324px",
|
||||
gap: "16px",
|
||||
borderRadius: "16px",
|
||||
padding: "20px",
|
||||
"*:where([data-mui-color-scheme='dark']) &": {
|
||||
backgroundColor: "#202020",
|
||||
},
|
||||
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
|
@ -21,11 +27,16 @@ export default function SessionsChart() {
|
|||
variant="h6"
|
||||
align="left"
|
||||
color="#F2F2F2"
|
||||
width="132px"
|
||||
height="24px"
|
||||
gap={"12px"}
|
||||
sx={{
|
||||
fontFamily: "Gilroy",
|
||||
fontWeight: 500,
|
||||
fontSize: "18px",
|
||||
lineHeight: "24px",
|
||||
letterSpacing: "0%",
|
||||
color: "#FAFAFA",
|
||||
}}
|
||||
>
|
||||
Charging prices
|
||||
|
@ -50,13 +61,15 @@ export default function SessionsChart() {
|
|||
}}
|
||||
>
|
||||
<Typography
|
||||
width="134px"
|
||||
height="24px"
|
||||
sx={{
|
||||
fontFamily: "Gilroy",
|
||||
fontWeight: 500,
|
||||
fontSize: "14px",
|
||||
lineHeight: "24px",
|
||||
color: "#F2F2F2",
|
||||
p: "4px",
|
||||
color: "#D9D8D8",
|
||||
|
||||
}}
|
||||
>
|
||||
Delhi NCR EV Station
|
||||
|
@ -78,7 +91,6 @@ export default function SessionsChart() {
|
|||
mx: "auto",
|
||||
}}
|
||||
>
|
||||
{/* You can map over your data here; for simplicity, we’re using static boxes */}
|
||||
{[1, 2, 3, 4].map((item) => (
|
||||
<Box
|
||||
key={item}
|
||||
|
@ -87,23 +99,47 @@ export default function SessionsChart() {
|
|||
borderRadius: "8px",
|
||||
p: "12px 16px",
|
||||
backgroundColor: "#272727",
|
||||
color: "#F2F2F2",
|
||||
color: "#D9D8D8",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
component="h1"
|
||||
variant="body2"
|
||||
width="98px"
|
||||
height="24px"
|
||||
fontWeight={400}
|
||||
fontSize={"14px"}
|
||||
lineHeight={"24px"}
|
||||
gutterBottom
|
||||
>
|
||||
Basic Charging
|
||||
</Typography>
|
||||
<Typography
|
||||
component="h1"
|
||||
variant="subtitle2"
|
||||
gutterBottom
|
||||
>
|
||||
16.83 cents/kWh
|
||||
</Typography>
|
||||
<Box display={"flex"} gap={1}>
|
||||
<Typography
|
||||
component="h1"
|
||||
variant="subtitle2"
|
||||
color="#FFFFFF"
|
||||
width="40px"
|
||||
height={"24px"}
|
||||
fontWeight={500}
|
||||
fontSize="18px"
|
||||
lineHeight="24px"
|
||||
gutterBottom
|
||||
>
|
||||
16.83
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
width="71px"
|
||||
height="24px"
|
||||
gap="2px"
|
||||
fontWeight={400}
|
||||
fontSize={"14px"}
|
||||
lineHeight={"24px"}
|
||||
>
|
||||
cents/kWh
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
|
|
|
@ -64,26 +64,34 @@ export default function SideMenu() {
|
|||
pl: 2,
|
||||
}}
|
||||
>
|
||||
|
||||
<Avatar
|
||||
<img
|
||||
src="/evLogo.png"
|
||||
alt="Logo"
|
||||
src="/Digilogo.png"
|
||||
sx={{
|
||||
width: "50px",
|
||||
height: "50px",
|
||||
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
width: open ? "200px" : "60px", // Adjust width depending on open state
|
||||
height: "auto",
|
||||
transition: "width 0.5s ease", // Smooth transition for width change
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
{/* <Avatar
|
||||
alt="Logo"
|
||||
src="/evLogo.png"
|
||||
sx={{
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
/> */}
|
||||
{/* <Box
|
||||
sx={{
|
||||
display: open ? "flex" : "none",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
pt: 2,
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
{/* Digi EV Text Section */}
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="#D9D8D8"
|
||||
|
@ -94,11 +102,10 @@ export default function SideMenu() {
|
|||
>
|
||||
Digi EV
|
||||
</Typography>
|
||||
<Typography variant="subtitle2" color="#D9D8D8" >
|
||||
<Typography variant="subtitle2" color="#D9D8D8">
|
||||
{user?.userType || "N/A"}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
</Box> */}
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
|
|
|
@ -18,13 +18,28 @@ export default function StatCard({ title, value }: StatCardProps) {
|
|||
return (
|
||||
<Card
|
||||
variant="outlined"
|
||||
sx={{ height: "100%", backgroundColor: "#202020" }}
|
||||
sx={{
|
||||
width: "264.5px",
|
||||
height: "90px",
|
||||
padding: "16px",
|
||||
borderRadius: "12px",
|
||||
gap: "24px",
|
||||
"*:where([data-mui-color-scheme='dark']) &": {
|
||||
backgroundColor: "#202020",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="subtitle2"
|
||||
color="#F2F2F2"
|
||||
width={"118px"}
|
||||
height={"14px"}
|
||||
fontWeight={400}
|
||||
fontSize={"12px"}
|
||||
lineHeight={"14px"}
|
||||
letterSpacing={"0%"}
|
||||
gutterBottom
|
||||
>
|
||||
{title}
|
||||
|
@ -33,8 +48,12 @@ export default function StatCard({ title, value }: StatCardProps) {
|
|||
component="h1"
|
||||
variant="body1"
|
||||
color="#F2F2F2"
|
||||
fontSize={30}
|
||||
fontWeight={700}
|
||||
width={"36px"}
|
||||
height={"36px"}
|
||||
fontSize={"32px"}
|
||||
fontWeight={600}
|
||||
lineHeight={"36px"}
|
||||
letterSpacing={"0%"}
|
||||
gutterBottom
|
||||
>
|
||||
{value}
|
||||
|
|
|
@ -1,20 +1,8 @@
|
|||
import * as React from "react";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import {
|
||||
BarChart,
|
||||
Bar,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
} from "recharts";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
Typography,
|
||||
Box,
|
||||
|
||||
} from "@mui/material";
|
||||
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
|
||||
import { BarChart, Bar, XAxis, YAxis, CartesianGrid } from "recharts";
|
||||
import { Card, CardContent, Typography, Box } from "@mui/material";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
const data = [
|
||||
{ name: "Jan", v1: 40 },
|
||||
{ name: "Feb", v1: 50 },
|
||||
|
@ -30,7 +18,14 @@ export default function RoundedBarChart() {
|
|||
return (
|
||||
<Card
|
||||
variant="outlined"
|
||||
sx={{ width: "100%", height: "100%", backgroundColor: "#202020" }}
|
||||
sx={{
|
||||
width: "553px",
|
||||
height: "444px",
|
||||
borderRadius: "16px",
|
||||
"*:where([data-mui-color-scheme='dark']) &": {
|
||||
backgroundColor: "#202020",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<div
|
||||
|
@ -61,29 +56,41 @@ export default function RoundedBarChart() {
|
|||
marginLeft: "auto",
|
||||
marginRight: "16px",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
p: 1.5,
|
||||
borderRadius: "8px",
|
||||
border: "1px solid #454545",
|
||||
|
||||
padding: "4px 8px",
|
||||
// padding: "4px 8px",
|
||||
color: "#F2F2F2",
|
||||
width: "129px",
|
||||
height: "44px",
|
||||
padding: "12px 16px",
|
||||
gap: "8px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
width={"69px"}
|
||||
height={"16px"}
|
||||
sx={{
|
||||
fontFamily: "Gilroy",
|
||||
|
||||
fontWeight: 500,
|
||||
fontSize: "14px",
|
||||
lineHeight: "24px",
|
||||
color: "#F2F2F2",
|
||||
p: "4px",
|
||||
color: "#D9D8D8",
|
||||
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
Monthly
|
||||
</Typography>
|
||||
<ArrowDropDownIcon sx={{ color: "#F2F2F2" }} />
|
||||
<ExpandMoreIcon
|
||||
width="20px"
|
||||
height="20px"
|
||||
|
||||
sx={{ color: "#F2F2F2" ,textAlign: "center", }}
|
||||
/>
|
||||
</Box>
|
||||
</div>
|
||||
<BarChart
|
||||
|
|
|
@ -50,12 +50,8 @@ const DashboardLayout: React.FC<LayoutProps> = ({ customStyles }) => {
|
|||
...customStyles,
|
||||
mt: { xs: 8, md: 0 },
|
||||
padding: 0,
|
||||
|
||||
})}
|
||||
>
|
||||
|
||||
|
||||
|
||||
<Stack
|
||||
spacing={2}
|
||||
sx={{
|
||||
|
|
|
@ -1,25 +1,35 @@
|
|||
import axios from "axios";
|
||||
// import { useHistory } from "react-router-dom";
|
||||
|
||||
const http = axios.create({
|
||||
baseURL: process.env.REACT_APP_BACKEND_URL,
|
||||
});
|
||||
|
||||
http.interceptors.request.use((config) => {
|
||||
const authToken = localStorage.getItem("authToken");
|
||||
if (authToken) {
|
||||
config.headers.Authorization = `Bearer ${authToken}`;
|
||||
}
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
http.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
if (error.response && error.response.status === 401) {
|
||||
if (error.response) {
|
||||
const status = error.response.status;
|
||||
const requestUrl = error.config.url; // Get the API route
|
||||
|
||||
window.location.href = "/login";
|
||||
// Handle token expiration (401) but NOT for login failures
|
||||
if (status === 401 && !requestUrl.includes("/login")) {
|
||||
localStorage.removeItem("authToken");
|
||||
window.location.href = "/login";
|
||||
}
|
||||
|
||||
// const history = useHistory();
|
||||
// history.push("/login");
|
||||
// Handle forbidden access
|
||||
if (status === 403) {
|
||||
localStorage.removeItem("authToken");
|
||||
window.location.href = "/login";
|
||||
}
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
|
|
@ -83,6 +83,8 @@ export default function AdminList() {
|
|||
{ id: "email", label: "Email" },
|
||||
{ id: "phone", label: "Phone" },
|
||||
{ id: "registeredAddress", label: "Address" },
|
||||
{ id: "userType", label: "Role" },
|
||||
|
||||
{ id: "action", label: "Action", align: "center" },
|
||||
];
|
||||
|
||||
|
@ -93,7 +95,8 @@ export default function AdminList() {
|
|||
name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
Admins: { registeredAddress: string }[]; // Adjusted to support array of Admins
|
||||
Admins: { registeredAddress: string }[];
|
||||
userType:string;
|
||||
},
|
||||
index: number
|
||||
) => ({
|
||||
|
@ -103,6 +106,7 @@ export default function AdminList() {
|
|||
email: admin?.email,
|
||||
phone: admin?.phone,
|
||||
registeredAddress: admin?.Admins?.[0]?.registeredAddress || "NA",
|
||||
userType:admin?.userType||"NA",
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import { useNavigate } from "react-router-dom";
|
|||
import { Visibility, VisibilityOff } from "@mui/icons-material";
|
||||
import { Card, SignInContainer } from "./styled.css.tsx";
|
||||
import { CustomIconButton } from "../../../components/AddEditUserModel/styled.css.tsx";
|
||||
import { AppDispatch } from "../../../redux/store/store.ts";
|
||||
interface ILoginForm {
|
||||
email: string;
|
||||
password: string;
|
||||
|
@ -37,7 +38,7 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
|
|||
handleSubmit,
|
||||
formState: { errors, isValid },
|
||||
} = useForm<ILoginForm>({ mode: "onChange" });
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
const router = useNavigate();
|
||||
|
||||
const handleClickOpen = () => {
|
||||
|
@ -104,11 +105,11 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
|
|||
}}
|
||||
>
|
||||
<img
|
||||
src="/DigiEVLogo.png"
|
||||
src="/evLogo.png"
|
||||
alt="Logo"
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
width: "250px",
|
||||
width: "180px",
|
||||
height: "auto",
|
||||
}}
|
||||
/>
|
||||
|
@ -329,9 +330,7 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
|
|||
? "error"
|
||||
: "primary"
|
||||
}
|
||||
onMouseDown={
|
||||
togglePasswordVisibility
|
||||
}
|
||||
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Box, Button, TextField, Typography } from "@mui/material";
|
||||
import { useEffect, useState } from "react";
|
||||
import CustomTable, { Column } from "../../components/CustomTable";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { RootState, AppDispatch } from "../../redux/store/store";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { createSlot, fetchAvailableSlots } from "../../redux/slices/slotSlice";
|
||||
import {
|
||||
createSlot,
|
||||
fetchAvailableSlots,
|
||||
updateSlot,
|
||||
} from "../../redux/slices/slotSlice";
|
||||
import AddSlotModal from "../../components/AddSlotModal";
|
||||
import dayjs from "dayjs";
|
||||
import EditSlotModal from "../../components/EditSlotModal";
|
||||
export default function EVSlotList() {
|
||||
const [addModalOpen, setAddModalOpen] = useState<boolean>(false);
|
||||
const [editModalOpen, setEditModalOpen] = useState<boolean>(false);
|
||||
const [editRow, setEditRow] = useState<any>(null);
|
||||
const { reset } = useForm();
|
||||
const [deleteModal, setDeleteModal] = useState<boolean>(false);
|
||||
const [viewModal, setViewModal] = useState<boolean>(false);
|
||||
const [rowData, setRowData] = useState<any | null>(null);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
const availableSlots = useSelector(
|
||||
(state: RootState) => state?.slotReducer.availableSlots
|
||||
|
@ -51,6 +53,34 @@ export default function EVSlotList() {
|
|||
}
|
||||
};
|
||||
|
||||
const handleUpdate = async (
|
||||
id: string,
|
||||
startTime: string,
|
||||
endTime: string,
|
||||
isAvailable: boolean
|
||||
) => {
|
||||
try {
|
||||
const formattedStartTime = dayjs(startTime, "HH:mm").format(
|
||||
"HH:mm"
|
||||
);
|
||||
const formattedEndTime = dayjs(endTime, "HH:mm").format("HH:mm");
|
||||
|
||||
await dispatch(
|
||||
updateSlot({
|
||||
id,
|
||||
startTime: formattedStartTime,
|
||||
endTime: formattedEndTime,
|
||||
isAvailable,
|
||||
})
|
||||
).unwrap();
|
||||
|
||||
await dispatch(fetchAvailableSlots());
|
||||
handleCloseModal();
|
||||
} catch (error) {
|
||||
console.error("Update failed", error);
|
||||
}
|
||||
};
|
||||
|
||||
const slotColumns: Column[] = [
|
||||
{ id: "srno", label: "Sr No" },
|
||||
{ id: "name", label: "Station Name" },
|
||||
|
@ -62,29 +92,24 @@ export default function EVSlotList() {
|
|||
{ id: "action", label: "Action", align: "center" },
|
||||
];
|
||||
|
||||
// Make sure dayjs is imported
|
||||
|
||||
const slotRows = availableSlots?.length
|
||||
? availableSlots.map((slot, index) => {
|
||||
const formattedDate = dayjs(slot?.date).format("YYYY-MM-DD");
|
||||
const startTime = dayjs(slot?.startTime).format("HH:mm");
|
||||
const endTime = dayjs(slot?.endTime).format("HH:mm");
|
||||
const formattedDate = dayjs(slot?.date).format("YYYY-MM-DD");
|
||||
const startTime = dayjs(slot?.startTime).format("HH:mm");
|
||||
const endTime = dayjs(slot?.endTime).format("HH:mm");
|
||||
|
||||
console.log("first", startTime);
|
||||
return {
|
||||
srno: index + 1,
|
||||
id: slot?.id ?? "NA",
|
||||
stationId: slot?.stationId ?? "NA",
|
||||
name: slot?.ChargingStation?.name ?? "NA",
|
||||
date: formattedDate ?? "NA",
|
||||
startTime: startTime ?? "NA",
|
||||
endTime: endTime ?? "NA",
|
||||
isAvailable: slot?.isAvailable ? "Yes" : "No",
|
||||
};
|
||||
})
|
||||
return {
|
||||
srno: index + 1,
|
||||
id: slot?.id ?? "NA",
|
||||
stationId: slot?.stationId ?? "NA",
|
||||
name: slot?.ChargingStation?.name ?? "NA",
|
||||
date: formattedDate ?? "NA",
|
||||
startTime: startTime ?? "NA",
|
||||
endTime: endTime ?? "NA",
|
||||
isAvailable: slot?.isAvailable ? "Yes" : "No",
|
||||
};
|
||||
})
|
||||
: [];
|
||||
console.log("Rows",slotRows)
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -105,6 +130,12 @@ console.log("Rows",slotRows)
|
|||
handleClose={handleCloseModal}
|
||||
handleAddSlot={handleAddSlot}
|
||||
/>
|
||||
<EditSlotModal
|
||||
open={editModalOpen}
|
||||
handleClose={handleCloseModal}
|
||||
handleUpdate={handleUpdate}
|
||||
editRow={rowData}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,11 +9,9 @@ import {
|
|||
managerList,
|
||||
addManager,
|
||||
updateManager,
|
||||
deleteManager,
|
||||
} from "../../redux/slices/managerSlice";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
|
||||
export default function ManagerList() {
|
||||
const [addModalOpen, setAddModalOpen] = useState<boolean>(false);
|
||||
const [editModalOpen, setEditModalOpen] = useState<boolean>(false);
|
||||
|
@ -67,7 +65,7 @@ export default function ManagerList() {
|
|||
|
||||
// Handle updating an existing manager
|
||||
const handleUpdate = async (
|
||||
id: number,
|
||||
id: string,
|
||||
name: string,
|
||||
email: string,
|
||||
phone: string,
|
||||
|
@ -101,65 +99,29 @@ export default function ManagerList() {
|
|||
{ id: "registeredAddress", label: "Station Location" },
|
||||
{ id: "action", label: "Action", align: "center" },
|
||||
];
|
||||
// const categoryColumns: Column[] = [
|
||||
// { id: "srno", label: "Sr No" },
|
||||
// { id: "name", label: "Name" },
|
||||
// { id: "email", label: "Email" },
|
||||
// { id: "phone", label: "Phone" },
|
||||
// { id: "stationName", label: "Station Name" }, // Added station name column
|
||||
// { id: "stationAddress", label: "Station Location" }, // Added station address column
|
||||
// { id: "action", label: "Action", align: "center" },
|
||||
// ];
|
||||
|
||||
|
||||
// Filter managers based on search term
|
||||
const filteredManagers = managers?.filter(
|
||||
(manager) =>
|
||||
manager.name?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
manager.email?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
manager.phone?.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
|
||||
);
|
||||
|
||||
// Format rows to display manager details
|
||||
// const categoryRows = filteredManagers?.length
|
||||
// ? filteredManagers?.map(
|
||||
// (
|
||||
// manager: {
|
||||
// id: number;
|
||||
// name: string;
|
||||
// email: string;
|
||||
// phone: string;
|
||||
|
||||
|
||||
// },
|
||||
// index: number
|
||||
// ) => ({
|
||||
// id: manager?.id,
|
||||
// srno: index + 1,
|
||||
// name: manager?.name,
|
||||
// email: manager?.email,
|
||||
// phone: manager.phone ?? "NA",
|
||||
// })
|
||||
// )
|
||||
// : [];
|
||||
|
||||
const categoryRows = filteredManagers?.length
|
||||
? filteredManagers.map((manager, index) => {
|
||||
const station = manager?.ChargingStation; // Correct access to the ChargingStation data
|
||||
return {
|
||||
id: manager.id,
|
||||
srno: index + 1,
|
||||
name: manager.name,
|
||||
email: manager.email,
|
||||
phone: manager.phone ?? "NA",
|
||||
stationName: station?.name ?? "NA", // Corrected station name
|
||||
registeredAddress: station?.registeredAddress ?? "NA", // Corrected station address
|
||||
};
|
||||
})
|
||||
: [];
|
||||
|
||||
// const filteredManagers = managers?.filter(
|
||||
// (manager) =>
|
||||
// manager.name?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
// manager.email?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
// manager.phone?.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
// );
|
||||
|
||||
const categoryRows = managers?.length
|
||||
? managers.map((manager, index) => {
|
||||
const station = manager?.chargingStation; // Correct access to the ChargingStation data
|
||||
return {
|
||||
id: manager.id,
|
||||
srno: index + 1,
|
||||
name: manager.name,
|
||||
email: manager.email,
|
||||
phone: manager.phone ?? "NA",
|
||||
stationName: station?.name ?? "NA", // Corrected station name
|
||||
registeredAddress: station?.registeredAddress ?? "NA", // Corrected station address
|
||||
};
|
||||
})
|
||||
: [];
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -42,14 +42,24 @@ const ProfilePage = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<Container sx={{ py: 4 }}>
|
||||
<Container
|
||||
sx={{
|
||||
py: 4,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h4" gutterBottom>
|
||||
Account Info
|
||||
</Typography>
|
||||
<Card
|
||||
sx={{
|
||||
width: "1132px",
|
||||
height: "331px",
|
||||
gap: "24px",
|
||||
borderRadius: "12px",
|
||||
padding: "16px",
|
||||
maxWidth: "100%",
|
||||
margin: "0 auto",
|
||||
backgroundColor: "#1C1C1C",
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
|
@ -62,16 +72,29 @@ const ProfilePage = () => {
|
|||
<Avatar
|
||||
alt="User Avatar"
|
||||
src="/avatar.png"
|
||||
sx={{ width: 36, height: 36 }}
|
||||
sx={{ width: "60px", height: "60px" }}
|
||||
/>
|
||||
<Box>
|
||||
<Typography
|
||||
variant="body1"
|
||||
width="1028px"
|
||||
height="24px"
|
||||
fontWeight={500}
|
||||
fontSize={"20px"}
|
||||
lineHeight={"100%"}
|
||||
sx={{ color: "#FFFFFF" }}
|
||||
>
|
||||
{user?.name || "No Admin"}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="#D9D8D8">
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="#D9D8D8"
|
||||
fontWeight={400}
|
||||
fontSize={"16px"}
|
||||
lineHeight={"100%"}
|
||||
width="46px"
|
||||
height="19px"
|
||||
>
|
||||
{user?.userType || "N/A"}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
@ -88,6 +111,11 @@ const ProfilePage = () => {
|
|||
>
|
||||
<Typography
|
||||
variant="body1"
|
||||
width="1048px"
|
||||
height="19px"
|
||||
fontWeight={500}
|
||||
fontSize={"16px"}
|
||||
lineHeight={"100%"}
|
||||
sx={{ color: "#FFFFFF" }}
|
||||
>
|
||||
Personal Information
|
||||
|
@ -95,23 +123,39 @@ const ProfilePage = () => {
|
|||
<Link
|
||||
variant="body1"
|
||||
href="/edit-profile"
|
||||
|
||||
color="#52ACDF"
|
||||
|
||||
>
|
||||
Edit
|
||||
</Link>
|
||||
</Stack>
|
||||
|
||||
<Grid container>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<Grid
|
||||
container
|
||||
width="1100px"
|
||||
height="38px"
|
||||
// gap={"100px"}
|
||||
>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
sm={4}
|
||||
width="64px"
|
||||
height="16px"
|
||||
fontSize={"14px"}
|
||||
lineHeight={"100%"}
|
||||
>
|
||||
<Typography
|
||||
variant="body1"
|
||||
fontWeight={500}
|
||||
sx={{ color: "#FFFFFF" }}
|
||||
>
|
||||
Full Name:
|
||||
</Typography>
|
||||
<Typography variant="body2" color="#D9D8D8">
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight={400}
|
||||
color="#D9D8D8"
|
||||
>
|
||||
{user?.name || "N/A"}
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
@ -139,10 +183,20 @@ const ProfilePage = () => {
|
|||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Typography variant="body1" sx={{ color: "#FFFFFF" }}>
|
||||
<Typography
|
||||
variant="body1"
|
||||
width="21px"
|
||||
height="16px"
|
||||
sx={{ color: "#FFFFFF" }}
|
||||
>
|
||||
Bio:
|
||||
</Typography>
|
||||
<Typography variant="body2" color="#D9D8D8">
|
||||
<Typography
|
||||
variant="body2"
|
||||
width="1100px"
|
||||
height="32px"
|
||||
color="#D9D8D8"
|
||||
>
|
||||
{user?.bio || "No bio available."}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import CustomTable, { Column } from "../../components/CustomTable";
|
||||
import { RootState } from "../../redux/reducers";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { AppDispatch } from "../../redux/store/store";
|
||||
import { Chip } from "@mui/material";
|
||||
import AddStationModal from "../../components/AddStationModal";
|
||||
import CustomTable, { Column } from "../../components/CustomTable";
|
||||
import EditStationModal from "../../components/EditStationModal";
|
||||
import {
|
||||
createStation,
|
||||
|
@ -12,7 +8,11 @@ import {
|
|||
toggleStatus,
|
||||
updateStation,
|
||||
} from "../../redux/slices/stationSlice";
|
||||
import { Chip, Switch } from "@mui/material";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { RootState } from "../../redux/reducers";
|
||||
import { AppDispatch } from "../../redux/store/store";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
export default function StationList() {
|
||||
const [addModalOpen, setAddModalOpen] = useState<boolean>(false);
|
||||
|
@ -25,10 +25,13 @@ export default function StationList() {
|
|||
const [rowData, setRowData] = useState<any | null>(null);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
|
||||
const vehicles = useSelector(
|
||||
(state: RootState) => state.vehicleReducer.vehicles
|
||||
);
|
||||
const stations = useSelector(
|
||||
(state: RootState) => state.stationReducer.stations
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(stationList());
|
||||
}, [dispatch]);
|
||||
|
@ -48,7 +51,8 @@ export default function StationList() {
|
|||
const handleAddStation = async (data: {
|
||||
name: string;
|
||||
registeredAddress: string;
|
||||
totalSlots: string;
|
||||
totalSlots: number;
|
||||
allowedCarIds: number[];
|
||||
}) => {
|
||||
try {
|
||||
await dispatch(createStation(data)); // Dispatch action to add Station
|
||||
|
@ -63,7 +67,8 @@ export default function StationList() {
|
|||
id: string,
|
||||
name: string,
|
||||
registeredAddress: string,
|
||||
totalSlots: string
|
||||
totalSlots: number,
|
||||
allowedCarIds: number[]
|
||||
) => {
|
||||
try {
|
||||
await dispatch(
|
||||
|
@ -72,7 +77,8 @@ export default function StationList() {
|
|||
name,
|
||||
registeredAddress,
|
||||
totalSlots,
|
||||
|
||||
status: 0,
|
||||
allowedCarIds, // Pass the updated allowedCarIds
|
||||
})
|
||||
);
|
||||
await dispatch(stationList());
|
||||
|
@ -82,57 +88,49 @@ export default function StationList() {
|
|||
}
|
||||
};
|
||||
|
||||
const handleStatusToggle = async(id: string, newStatus: number) => {
|
||||
await dispatch(toggleStatus({ id, status: newStatus }));
|
||||
|
||||
const handleStatusToggle = async (id: string, newStatus: number) => {
|
||||
await dispatch(toggleStatus({ id, status: newStatus }));
|
||||
};
|
||||
// Toggle station status
|
||||
// const handleStatusToggle = async (id: string, currentStatus: number) => {
|
||||
// try {
|
||||
// const newStatus = currentStatus === 1 ? 0 : 1; // Toggle between Active(1) and Inactive(0)
|
||||
// await dispatch(
|
||||
// updateStation({
|
||||
// id,
|
||||
// status: newStatus,
|
||||
// name: stations.name,
|
||||
// registeredAddress: stations.registeredAddress,
|
||||
// totalSlots: stations.totalSlots,
|
||||
// })
|
||||
// );
|
||||
// await dispatch(stationList());
|
||||
// } catch (error) {
|
||||
// console.error("Error toggling status", error);
|
||||
// }
|
||||
// };
|
||||
const filterStations = stations?.filter((station) =>
|
||||
station.name.toLocaleLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
const categoryRows = filterStations?.length
|
||||
? filterStations?.map((station: Station, index: number) => ({
|
||||
id: station.id,
|
||||
srno: index + 1,
|
||||
name: station.name,
|
||||
registeredAddress:station.registeredAddress,
|
||||
totalSlots:station.totalSlots,
|
||||
status: (
|
||||
<Chip
|
||||
label={
|
||||
station.status === 1 ? "Available" : "Not Available"
|
||||
}
|
||||
color={station.status === 1 ? "primary" : "default"}
|
||||
variant="outlined"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
width: "80px",
|
||||
textAlign: "center",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
/>
|
||||
),
|
||||
statusValue: station.status,
|
||||
}))
|
||||
// const filterStations = Array.isArray(stations)
|
||||
// ? stations.filter((station) =>
|
||||
// station.name
|
||||
// .toLocaleLowerCase()
|
||||
// .includes(searchTerm.toLowerCase())
|
||||
// )
|
||||
// : [];
|
||||
|
||||
// Mapping and formatting vehicles
|
||||
const categoryRows = stations?.length
|
||||
? stations?.map((station: any, index: number) => {
|
||||
// Format the selected vehicles from the allowedCars array
|
||||
const formattedVehicles = station.allowedCars?.map(
|
||||
(car: any) => car.name
|
||||
);
|
||||
|
||||
// Format the vehicle list like "Tata Punch Electric, Royal" or similar
|
||||
const vehicleDisplay = formattedVehicles
|
||||
? formattedVehicles.length > 1
|
||||
? `${formattedVehicles.slice(0, 2).join(", ")} + ${
|
||||
formattedVehicles.length - 2
|
||||
}`
|
||||
: formattedVehicles.join(", ")
|
||||
: "No vehicles"; // In case there are no vehicles selected
|
||||
|
||||
return {
|
||||
id: station.id,
|
||||
srno: index + 1,
|
||||
name: station.name,
|
||||
registeredAddress: station.registeredAddress,
|
||||
totalSlots: station.totalSlots,
|
||||
vehicles: vehicleDisplay, // Add the formatted vehicle display here
|
||||
status:
|
||||
station.status === 1 ? "Available" : "Not Available", // Format status text
|
||||
statusValue: station.status, // Status value for toggling
|
||||
};
|
||||
})
|
||||
: [];
|
||||
console.log("Rowssss", categoryRows);
|
||||
|
||||
const categoryColumns: Column[] = [
|
||||
{ id: "srno", label: "Sr No" },
|
||||
|
@ -140,35 +138,11 @@ export default function StationList() {
|
|||
{ id: "name", label: "Station Name" },
|
||||
{ id: "registeredAddress", label: "Station Location" },
|
||||
{ id: "totalSlots", label: "Total Slots" },
|
||||
//{ id: "status", label: "Status" },
|
||||
{ id: "vehicles", label: "Vehicles" }, // Add Vehicles column here
|
||||
{ id: "status", label: "Status" }, // Add Status column here
|
||||
{ id: "action", label: "Action", align: "center" },
|
||||
];
|
||||
|
||||
// Filter stations based on search term
|
||||
// const filterStations = stations?.filter((station) =>
|
||||
// station.name.toLocaleLowerCase().includes(searchTerm.toLowerCase())
|
||||
// );
|
||||
|
||||
// // Prepare categoryRows with toggle switch for status
|
||||
// const categoryRows = filterStations?.length
|
||||
// ? filterStations?.map((station: any, index: number) => ({
|
||||
// id: station.id,
|
||||
// srno: index + 1,
|
||||
// name: station.name,
|
||||
// status: (
|
||||
// <Switch
|
||||
// checked={station.status === 1}
|
||||
// onChange={() =>
|
||||
// handleStatusToggle(station.id, station.status)
|
||||
// }
|
||||
// color="primary"
|
||||
// inputProps={{ "aria-label": "station-status-toggle" }}
|
||||
// />
|
||||
// ),
|
||||
// statusValue: station.status,
|
||||
// }))
|
||||
// : [];
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomTable
|
||||
|
@ -194,6 +168,7 @@ export default function StationList() {
|
|||
handleClose={handleCloseModal}
|
||||
handleUpdate={handleUpdate}
|
||||
editRow={rowData}
|
||||
vehicles={vehicles}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import CustomTable, { Column } from "../../components/CustomTable";
|
||||
import { RootState } from "../../redux/reducers";
|
||||
|
@ -15,13 +15,11 @@ import EditVehicleModal from "../../components/EditVehicleModal";
|
|||
export default function VehicleList() {
|
||||
const [addModalOpen, setAddModalOpen] = useState<boolean>(false);
|
||||
const [editModalOpen, setEditModalOpen] = useState<boolean>(false);
|
||||
const [editRow, setEditRow] = useState<any>(null);
|
||||
const { reset } = useForm();
|
||||
|
||||
const [deleteModal, setDeleteModal] = useState<boolean>(false);
|
||||
const [viewModal, setViewModal] = useState<boolean>(false);
|
||||
const [rowData, setRowData] = useState<any | null>(null);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
const vehicles = useSelector(
|
||||
(state: RootState) => state.vehicleReducer.vehicles
|
||||
|
@ -32,7 +30,7 @@ export default function VehicleList() {
|
|||
}, [dispatch]);
|
||||
|
||||
const handleClickOpen = () => {
|
||||
setRowData(null); // Reset row data when opening for new admin
|
||||
setRowData(null);
|
||||
setAddModalOpen(true);
|
||||
};
|
||||
|
||||
|
@ -44,7 +42,7 @@ export default function VehicleList() {
|
|||
};
|
||||
|
||||
const handleAddVehicle = async (data: {
|
||||
vehicleName: string;
|
||||
name: string;
|
||||
company: string;
|
||||
modelName: string;
|
||||
chargeType: string;
|
||||
|
@ -60,7 +58,7 @@ export default function VehicleList() {
|
|||
};
|
||||
|
||||
const handleUpdate = async (
|
||||
id: number,
|
||||
id: string,
|
||||
name: string,
|
||||
company: string,
|
||||
modelName: string,
|
||||
|
@ -95,24 +93,24 @@ export default function VehicleList() {
|
|||
{ id: "action", label: "Action", align: "center" },
|
||||
];
|
||||
|
||||
const filteredVehicles = vehicles?.filter(
|
||||
(vehicle) =>
|
||||
vehicle.name?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
vehicle.company?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
vehicle.modelName
|
||||
?.toLowerCase()
|
||||
.includes(searchTerm.toLowerCase()) ||
|
||||
vehicle.chargeType
|
||||
?.toLowerCase()
|
||||
.includes(searchTerm.toLowerCase()) ||
|
||||
vehicle.imageUrl?.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
// const filteredVehicles = vehicles?.filter(
|
||||
// (vehicle) =>
|
||||
// vehicle.name?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
// vehicle.company?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
// vehicle.modelName
|
||||
// ?.toLowerCase()
|
||||
// .includes(searchTerm.toLowerCase()) ||
|
||||
// vehicle.chargeType
|
||||
// ?.toLowerCase()
|
||||
// .includes(searchTerm.toLowerCase()) ||
|
||||
// vehicle.imageUrl?.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
// );
|
||||
|
||||
const categoryRows = filteredVehicles?.length
|
||||
? filteredVehicles?.map(
|
||||
const categoryRows = vehicles?.length
|
||||
? vehicles?.map(
|
||||
(
|
||||
vehicle: {
|
||||
id: number;
|
||||
id: string;
|
||||
name: string;
|
||||
company: string;
|
||||
modelName: string;
|
||||
|
@ -132,8 +130,6 @@ export default function VehicleList() {
|
|||
)
|
||||
: [];
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomTable
|
||||
|
@ -159,7 +155,6 @@ export default function VehicleList() {
|
|||
handleUpdate={handleUpdate}
|
||||
editRow={rowData}
|
||||
/>
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
|
||||
import http from "../../lib/https";
|
||||
import { toast } from "sonner";
|
||||
interface VehicleBrand {
|
||||
name: string;
|
||||
id: string;
|
||||
company: string;
|
||||
}
|
||||
|
||||
interface Vehicle {
|
||||
id: number;
|
||||
brandId?: string;
|
||||
id: string;
|
||||
name: string;
|
||||
company: string;
|
||||
modelName: string;
|
||||
|
@ -11,18 +17,41 @@ interface Vehicle {
|
|||
imageUrl: string;
|
||||
}
|
||||
interface VehicleState {
|
||||
vehicleBrands: VehicleBrand[];
|
||||
vehicles: Vehicle[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
const initialState: VehicleState = {
|
||||
vehicleBrands: [],
|
||||
vehicles: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
};
|
||||
export const fetchVehicleBrands = createAsyncThunk<
|
||||
VehicleBrand[],
|
||||
void,
|
||||
{ rejectValue: string }
|
||||
>("vehicle/fetchVehicleBrands", async (_, { rejectWithValue }) => {
|
||||
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");
|
||||
}
|
||||
|
||||
// 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,
|
||||
Vehicle[],
|
||||
void,
|
||||
{ rejectValue: string }
|
||||
>("fetchVehicles", async (_, { rejectWithValue }) => {
|
||||
|
@ -122,6 +151,22 @@ const vehicleSlice = createSlice({
|
|||
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) => {
|
||||
state.loading = false;
|
||||
state.error =
|
||||
action.payload || "Failed to fetch vehicle brands";
|
||||
})
|
||||
.addCase(addVehicle.pending, (state) => {
|
||||
state.loading = true;
|
||||
// state.error = null;
|
||||
|
@ -133,12 +178,9 @@ const vehicleSlice = createSlice({
|
|||
state.vehicles.push(action.payload);
|
||||
}
|
||||
)
|
||||
.addCase(
|
||||
addVehicle.rejected,
|
||||
(state, action: PayloadAction<string | undefined>) => {
|
||||
state.loading = false;
|
||||
}
|
||||
)
|
||||
.addCase(addVehicle.rejected, (state) => {
|
||||
state.loading = false;
|
||||
})
|
||||
.addCase(updateVehicle.pending, (state) => {
|
||||
state.loading = true;
|
||||
})
|
||||
|
|
|
@ -60,6 +60,7 @@ export const getCarPorts = createAsyncThunk<
|
|||
>("fetchCarPorts", async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await http.get("/get-vehicle-port-dropdown");
|
||||
|
||||
return response.data.data; // Adjust based on actual API response
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(
|
||||
|
@ -119,7 +120,22 @@ export const addBooking = createAsyncThunk<
|
|||
);
|
||||
}
|
||||
});
|
||||
|
||||
export const deleteBooking = createAsyncThunk<
|
||||
string, // Return type (id of deleted slot)
|
||||
string,
|
||||
{ rejectValue: string }
|
||||
>("booking/deleteBooking", async (id, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await http.delete(`/delete-booking/${id}`);
|
||||
toast.success("Slot deleted successfully");
|
||||
return id; // Return the id of the deleted slot
|
||||
} catch (error: any) {
|
||||
toast.error("Error deleting the slot: " + error?.message);
|
||||
return rejectWithValue(
|
||||
error.response?.data?.message || "An error occurred"
|
||||
);
|
||||
}
|
||||
});
|
||||
const bookSlice = createSlice({
|
||||
name: "booking",
|
||||
initialState,
|
||||
|
@ -167,7 +183,32 @@ const bookSlice = createSlice({
|
|||
(state, action: PayloadAction<string[]>) => {
|
||||
state.carPorts = action.payload;
|
||||
}
|
||||
);
|
||||
)
|
||||
.addCase(deleteBooking.pending, (state) => {
|
||||
state.loading = true;
|
||||
})
|
||||
.addCase(
|
||||
deleteBooking.fulfilled,
|
||||
(state, action: PayloadAction<string>) => {
|
||||
state.loading = false;
|
||||
// Ensure we're filtering the correct array (bookings)
|
||||
state.bookings = state.bookings.filter(
|
||||
(booking) =>
|
||||
String(booking.id) !== String(action.payload)
|
||||
);
|
||||
// Also update slots array if it exists
|
||||
if (state.bookings) {
|
||||
state.bookings = state.bookings.filter(
|
||||
(booking) =>
|
||||
String(booking.id) !== String(action.payload)
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
.addCase(deleteBooking.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload || "Failed to delete slot";
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -6,11 +6,12 @@ import { toast } from "sonner";
|
|||
|
||||
interface Manager {
|
||||
Manager: any;
|
||||
id: number;
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
stationId: string;
|
||||
chargingStation:{name:string, registeredAddress:string};
|
||||
}
|
||||
|
||||
interface ManagerState {
|
||||
|
@ -73,7 +74,7 @@ export const addManager = createAsyncThunk<
|
|||
// Update Manager (Async Thunk)
|
||||
export const updateManager = createAsyncThunk<
|
||||
Manager,
|
||||
{ id: number; managerData: Manager },
|
||||
{ id: string; managerData: Manager },
|
||||
{ rejectValue: string }
|
||||
>("updateManager", async ({ id, managerData }, { rejectWithValue }) => {
|
||||
if (!id) {
|
||||
|
@ -174,6 +175,9 @@ const managerSlice = createSlice({
|
|||
})
|
||||
.addCase(deleteManager.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
state.managers = state.managers.filter(
|
||||
(manager) => String(manager.id) !== String(action.payload)
|
||||
);
|
||||
})
|
||||
.addCase(deleteManager.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
|
|
|
@ -5,7 +5,7 @@ import { toast } from "sonner";
|
|||
// Define TypeScript types
|
||||
interface Slot {
|
||||
id: string;
|
||||
stationId:string;
|
||||
stationId: string;
|
||||
date: string;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
|
@ -88,13 +88,23 @@ export const createSlot = createAsyncThunk<
|
|||
// Update Slot details
|
||||
export const updateSlot = createAsyncThunk<
|
||||
Slot,
|
||||
{ id: number; startTime: string; endTime: string }, // Argument type (slot update data)
|
||||
{
|
||||
id: string;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
isAvailable: boolean;
|
||||
},
|
||||
{ rejectValue: string }
|
||||
>("slots/updateSlot", async ({ id, ...slotData }, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await http.patch(`/slots/${id}`, slotData);
|
||||
const response = await http.patch(`/update-availability/${id}`, {
|
||||
...slotData,
|
||||
// Ensure data matches exactly what backend expects
|
||||
startHour: slotData.startTime,
|
||||
endHour: slotData.endTime,
|
||||
});
|
||||
toast.success("Slot updated successfully");
|
||||
return response.data.data; // Return updated slot data
|
||||
return response.data.data;
|
||||
} catch (error: any) {
|
||||
toast.error("Error updating the slot: " + error?.message);
|
||||
return rejectWithValue(
|
||||
|
@ -164,14 +174,15 @@ const slotSlice = createSlice({
|
|||
(state, action: PayloadAction<Slot>) => {
|
||||
state.loading = false;
|
||||
// Update the slot in the state with the updated data
|
||||
const index = state.slots.findIndex(
|
||||
const index = state.availableSlots.findIndex(
|
||||
(slot) => slot.id === action.payload.id
|
||||
);
|
||||
if (index !== -1) {
|
||||
state.slots[index] = action.payload;
|
||||
state.availableSlots[index] = action.payload;
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
.addCase(updateSlot.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload || "Failed to update slot";
|
||||
|
|
|
@ -8,8 +8,9 @@ interface Station {
|
|||
id: string;
|
||||
name: string;
|
||||
registeredAddress: string;
|
||||
totalSlots: string;
|
||||
totalSlots: number;
|
||||
status: number;
|
||||
allowedCarIds: number[];
|
||||
}
|
||||
|
||||
interface StationState {
|
||||
|
@ -46,21 +47,43 @@ export const stationList = createAsyncThunk<any, void, { rejectValue: string }>(
|
|||
}
|
||||
}
|
||||
);
|
||||
export const getAllStations = createAsyncThunk<
|
||||
any,
|
||||
void,
|
||||
{ rejectValue: string }
|
||||
>("getAllStations", async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
const token = localStorage?.getItem("authToken");
|
||||
if (!token) throw new Error("No token found");
|
||||
|
||||
const response = await http.get("/get-all-stations");
|
||||
|
||||
if (!response.data) throw new Error("Invalid API response");
|
||||
|
||||
// Return the full response to handle in the reducer
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
toast.error("Error Fetching Stations: " + error.message);
|
||||
return rejectWithValue(
|
||||
error?.response?.data?.message || "An error occurred"
|
||||
);
|
||||
}
|
||||
});
|
||||
// Create Station
|
||||
export const createStation = createAsyncThunk<
|
||||
any,
|
||||
{
|
||||
name: string;
|
||||
registeredAddress: string;
|
||||
totalSlots: string;
|
||||
totalSlots: number;
|
||||
allowedCarIds: number[];
|
||||
},
|
||||
{ rejectValue: string }
|
||||
>("Station/createStation", async (data, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await http.post("/create-station", data);
|
||||
toast.success("Station created successfully");
|
||||
return response.data;
|
||||
return response.data; // Assuming the response contains the created station data
|
||||
} catch (error: any) {
|
||||
toast.error(
|
||||
"Failed to create Station: " +
|
||||
|
@ -72,25 +95,31 @@ export const createStation = createAsyncThunk<
|
|||
}
|
||||
});
|
||||
|
||||
// Update Station details
|
||||
// Update Station details
|
||||
export const updateStation = createAsyncThunk(
|
||||
"updateStation",
|
||||
async ({ id, ...stationData }: Station, { rejectWithValue }) => {
|
||||
try {
|
||||
// Exclude the `status` from the update payload
|
||||
const { status, ...updateData } = stationData;
|
||||
|
||||
// Send the update request without the `status`
|
||||
const response = await http.patch(
|
||||
`/update-station/${id}`,
|
||||
stationData
|
||||
updateData
|
||||
);
|
||||
toast.success("Station Deatils updated successfully");
|
||||
toast.success("Station Details updated successfully");
|
||||
return response?.data;
|
||||
} catch (error: any) {
|
||||
toast.error("Error updating the user: " + error);
|
||||
toast.error("Error updating the station: " + error);
|
||||
return rejectWithValue(
|
||||
error.response?.data?.message || "An error occurred"
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const deleteStation = createAsyncThunk<
|
||||
string,
|
||||
string,
|
||||
|
@ -156,17 +185,30 @@ const stationSlice = createSlice({
|
|||
stationList.fulfilled,
|
||||
(state, action: PayloadAction<any>) => {
|
||||
state.loading = false;
|
||||
// Properly extract stations from the response data structure
|
||||
state.stations =
|
||||
action.payload.data?.results ||
|
||||
action.payload.data ||
|
||||
[];
|
||||
// Correct data extraction
|
||||
state.stations = action.payload.data?.stations || [];
|
||||
}
|
||||
)
|
||||
.addCase(stationList.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload || "Failed to fetch stations";
|
||||
})
|
||||
.addCase(getAllStations.pending, (state) => {
|
||||
state.loading = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(
|
||||
getAllStations.fulfilled,
|
||||
(state, action: PayloadAction<any>) => {
|
||||
state.loading = false;
|
||||
state.stations = action.payload.data || [];
|
||||
}
|
||||
)
|
||||
|
||||
.addCase(getAllStations.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload || "Failed to fetch stations";
|
||||
})
|
||||
.addCase(createStation.pending, (state) => {
|
||||
state.loading = true;
|
||||
})
|
||||
|
@ -174,12 +216,12 @@ const stationSlice = createSlice({
|
|||
createStation.fulfilled,
|
||||
(state, action: PayloadAction<any>) => {
|
||||
state.loading = false;
|
||||
// Add the newly created station to the state if it exists in the response
|
||||
if (action.payload.data) {
|
||||
state.stations.push(action.payload.data);
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
.addCase(
|
||||
createStation.rejected,
|
||||
(state, action: PayloadAction<string | undefined>) => {
|
||||
|
@ -223,8 +265,21 @@ const stationSlice = createSlice({
|
|||
})
|
||||
.addCase(updateStation.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload;
|
||||
// If the station was updated, find and update it in the state
|
||||
const updatedStation = action.payload;
|
||||
const stationIndex = state.stations.findIndex(
|
||||
(station) => station.id === updatedStation.id
|
||||
);
|
||||
if (stationIndex !== -1) {
|
||||
// Here, merge the updated station with the existing one
|
||||
// Ensure `status` is not overwritten if not explicitly updated
|
||||
state.stations[stationIndex] = {
|
||||
...state.stations[stationIndex],
|
||||
...updatedStation,
|
||||
};
|
||||
}
|
||||
})
|
||||
|
||||
.addCase(updateStation.rejected, (state) => {
|
||||
state.loading = false;
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue