Charging station new updates in api integration and mangerModal bookingModal changes

This commit is contained in:
jaanvi 2025-03-25 18:21:36 +05:30
parent fdff9c1dd9
commit 4df93ec1d9
9 changed files with 457 additions and 195 deletions

View file

@ -13,19 +13,20 @@ 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 { stationList } from "../../redux/slices/stationSlice.ts";
export default function AddBookingModal({
open,
@ -44,6 +45,13 @@ 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
);
useEffect(() => {
dispatch(stationList());
}, [dispatch]);
useEffect(() => {
// Fetch car names and car ports
@ -66,7 +74,7 @@ export default function AddBookingModal({
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 +138,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 +209,6 @@ export default function AddBookingModal({
size="small"
error={!!errors.carName}
>
<InputLabel>Car Name</InputLabel>
<Select
{...field}
label="Car Name"

View file

@ -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,48 @@ 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"
>
{stations.map((station) => (
<MenuItem
key={station.id}
value={station.name}
>
{station.name}
</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 +251,6 @@ export default function AddManagerModal({
: "primary"
}
size="small"
// onMouseDown={togglePasswordVisibility}
InputProps={{
endAdornment: (
<InputAdornment position="end">
@ -247,9 +278,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 +302,6 @@ export default function AddManagerModal({
})}
/>
</Box>
</Box>
{/* Submit Button */}

View file

@ -1,6 +1,22 @@
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 { vehicleList } from "../../redux/slices/VehicleSlice"; // Adjust this import path accordingly
import {
CustomIconButton,
CustomTextField,
@ -18,10 +34,49 @@ 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
useEffect(() => {
dispatch(vehicleList()); // Fetch vehicles when the component mounts
}, [dispatch]);
const [selectedVehicles, setSelectedVehicles] = useState<string[]>([]);
// Handle the change in selected vehicles (checkboxes)
const handleCheckboxChange = (
event: React.ChangeEvent<{ value: unknown }>
) => {
const value = event.target.value as string[];
setSelectedVehicles(value);
};
// Function to map selected vehicle names to corresponding vehicle ids
const getVehicleIds = () => {
return vehicles
.filter((vehicle) => selectedVehicles.includes(vehicle.name))
.map((vehicle) => vehicle.id); // Return an array of ids based on the selected names
};
const onSubmit = (data: any) => {
handleAddStation(data); // Add station to the list
const vehicleIds = getVehicleIds(); // Get the ids of the selected vehicles
// Prepare the data to be sent to the backend
const payload = {
...data,
status: 1, // Default status, can be adjusted if needed
allowedCarIds: vehicleIds, // Pass the vehicle ids to the backend
};
// Handle adding the station with the constructed payload
handleAddStation(payload);
handleClose(); // Close modal after adding
reset();
setSelectedVehicles([]); // Reset selected vehicles after submission
};
return (
@ -77,7 +132,6 @@ export default function AddStationModal({
gap: 2,
}}
>
{/* First Row - Two Inputs */}
<Box sx={{ display: "flex", gap: 2 }}>
<Box
sx={{
@ -175,6 +229,53 @@ export default function AddStationModal({
/>
</Box>
</Box>
{/* Vehicle Name Dropdown with Checkboxes */}
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "100%",
}}
>
<Typography variant="body2" fontWeight={500}>
Vehicle Name
</Typography>
{/* Dropdown with Checkboxes */}
<FormControl fullWidth>
<InputLabel>Choose Vehicles</InputLabel>
<Select
multiple
value={selectedVehicles}
onChange={handleCheckboxChange}
renderValue={(selected) =>
(selected as string[]).join(", ")
}
>
{vehicles?.map((vehicle) => (
<MenuItem
key={vehicle.id}
value={vehicle.name}
>
<Checkbox
checked={selectedVehicles.includes(
vehicle.name
)}
/>
<ListItemText
primary={vehicle.name}
/>
</MenuItem>
))}
</Select>
<FormHelperText>
{errors.vehicleName
? errors.vehicleName.message
: ""}
</FormHelperText>
</FormControl>
</Box>
</Box>
{/* Submit Button */}
@ -202,4 +303,4 @@ export default function AddStationModal({
</Box>
</Modal>
);
}
}

View file

@ -145,7 +145,7 @@ export default function AddVehicleModal({
{...register("company", {
required: "Company is required",
minLength: {
value: 5,
value: 3,
message:
"Company must be at least 5 characters long",
},

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

View file

@ -1,5 +1,16 @@
import React, { useEffect } from "react";
import { Box, Button, Typography, Modal } from "@mui/material";
import {
Box,
Button,
Typography,
Modal,
FormControl,
InputLabel,
Select,
MenuItem,
Checkbox,
ListItemText,
} from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { useForm, Controller } from "react-hook-form";
import {
@ -7,6 +18,15 @@ import {
CustomTextField,
} from "../AddEditUserModel/styled.css.tsx";
// Define the types for your form data
interface FormData {
name: string;
registeredAddress: string;
totalSlots: number;
status: number;
allowedCarIds: string[]; // Assuming allowedCarIds are the vehicle IDs
}
interface EditStationModalProps {
open: boolean;
handleClose: () => void;
@ -15,16 +35,10 @@ interface EditStationModalProps {
name: string,
registeredAddress: string,
totalSlots: number,
status: number
allowedCarIds: string[]
) => void;
editRow: any;
}
interface FormData {
name: string;
registeredAddress: string;
totalSlots: number;
status: number;
editRow?: any;
vehicles: { id: string; name: string }[];
}
const EditStationModal: React.FC<EditStationModalProps> = ({
@ -32,34 +46,42 @@ const EditStationModal: React.FC<EditStationModalProps> = ({
handleClose,
handleUpdate,
editRow,
vehicles,
}) => {
const {
control,
handleSubmit,
formState: { errors },
setValue,
reset,
} = useForm<FormData>({
defaultValues: {
name: "",
registeredAddress: "",
totalSlots: 0,
status: 1,
},
});
const { control, handleSubmit, setValue, reset, watch } = useForm<FormData>(
{
defaultValues: {
name: "",
registeredAddress: "",
totalSlots: 0,
status: 1,
allowedCarIds: [],
},
}
);
// Set values if editRow is provided
// Watch allowedCarIds from the form state
const allowedCarIds = watch("allowedCarIds");
// Set values when editRow is provided
useEffect(() => {
if (editRow) {
setValue("name", editRow.name);
setValue("registeredAddress", editRow.registeredAddress);
setValue("totalSlots", editRow.totalSlots);
setValue("status", editRow.number);
setValue("status", editRow.status);
// Set the allowedCarIds with the previously selected vehicle IDs
setValue(
"allowedCarIds",
editRow.allowedCarIds?.map((car: any) => car.id) || []
);
} else {
reset();
}
}, [editRow, setValue, reset]);
// Handle form submit
const onSubmit = (data: FormData) => {
if (editRow) {
handleUpdate(
@ -67,10 +89,10 @@ const EditStationModal: React.FC<EditStationModalProps> = ({
data.name,
data.registeredAddress,
data.totalSlots,
data.status
data.allowedCarIds
);
}
handleClose(); // Close the modal
handleClose(); // Close the modal after update
reset(); // Reset the form fields
};
@ -140,27 +162,12 @@ 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}
fullWidth
placeholder="Enter Station Name"
size="small"
error={!!errors.name}
helperText={errors.name?.message}
/>
)}
/>
@ -183,19 +190,12 @@ const EditStationModal: React.FC<EditStationModalProps> = ({
<Controller
name="registeredAddress"
control={control}
rules={{
required: "Registered Address is required",
}}
render={({ field }) => (
<CustomTextField
{...field}
fullWidth
placeholder="Enter Registered Address"
size="small"
error={!!errors.registeredAddress}
helperText={
errors.registeredAddress?.message
}
/>
)}
/>
@ -221,13 +221,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}
@ -235,14 +228,62 @@ const EditStationModal: React.FC<EditStationModalProps> = ({
placeholder="Enter Total Slots"
size="small"
type="number"
error={!!errors.totalSlots}
helperText={errors.totalSlots?.message}
/>
)}
/>
</Box>
</Box>
{/* Vehicle Dropdown with Checkboxes */}
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "100%",
}}
>
<Typography variant="body2" fontWeight={500}>
Vehicle Names
</Typography>
<FormControl fullWidth>
<InputLabel>Choose Vehicles</InputLabel>
<Select
multiple
value={allowedCarIds || []} // Watch the allowedCarIds to reflect selected values
onChange={(e) => {
// Update the allowedCarIds when the selection changes
setValue(
"allowedCarIds",
e.target.value as string[]
); // Update allowedCarIds on change
}}
renderValue={(selected) => {
return (selected as string[])
.map((id) => {
const vehicle = vehicles?.find(
(vehicle) => vehicle.id === id
);
return vehicle ? vehicle.name : "";
})
.join(", ");
}}
>
{vehicles?.map((vehicle) => (
<MenuItem
key={vehicle.id}
value={vehicle.id}
>
<Checkbox
checked={allowedCarIds?.includes(
vehicle.id
)}
/>
<ListItemText primary={vehicle.name} />
</MenuItem>
))}
</Select>
</FormControl>
</Box>
</Box>
{/* Submit Button */}

View file

@ -16,9 +16,6 @@ http.interceptors.response.use(
(error) => {
if (error.response && error.response.status === 401) {
// window.location.href = "/login";
// const history = useHistory();

View file

@ -1,18 +1,13 @@
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,
stationList,
toggleStatus,
updateStation,
} from "../../redux/slices/stationSlice";
import { Chip, Switch } from "@mui/material";
import { createStation, stationList, toggleStatus, updateStation } from "../../redux/slices/stationSlice";
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);
@ -28,6 +23,10 @@ export default function StationList() {
const stations = useSelector(
(state: RootState) => state.stationReducer.stations
);
const vehicles = useSelector(
(state: RootState) => state.vehicleReducer.vehicles
);
useEffect(() => {
dispatch(stationList());
@ -63,7 +62,8 @@ export default function StationList() {
id: string,
name: string,
registeredAddress: string,
totalSlots: string
totalSlots: string,
allowedCarIds: number[]
) => {
try {
await dispatch(
@ -72,7 +72,8 @@ export default function StationList() {
name,
registeredAddress,
totalSlots,
status: 0,
allowedCarIds, // Pass the updated allowedCarIds
})
);
await dispatch(stationList());
@ -82,56 +83,44 @@ 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())
);
// Mapping and formatting vehicles
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,
}))
? filterStations?.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
};
})
: [];
const categoryColumns: Column[] = [
@ -140,35 +129,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 +159,7 @@ export default function StationList() {
handleClose={handleCloseModal}
handleUpdate={handleUpdate}
editRow={rowData}
vehicles={vehicles}
/>
</>
);

View file

@ -10,6 +10,7 @@ interface Station {
registeredAddress: string;
totalSlots: string;
status: number;
allowedCarIds: number[];
}
interface StationState {
@ -54,6 +55,7 @@ export const createStation = createAsyncThunk<
name: string;
registeredAddress: string;
totalSlots: string;
allowedCarIds: number[];
},
{ rejectValue: string }
>("Station/createStation", async (data, { rejectWithValue }) => {