Dynamic Ev Apis Integration implemented

This commit is contained in:
jaanvi 2025-04-03 10:54:55 +05:30
parent 57160520fb
commit 4a15fd3293
9 changed files with 318 additions and 24 deletions

View file

@ -0,0 +1,76 @@
import {
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Button,
TextField,
} from "@mui/material";
import { useForm, SubmitHandler } from "react-hook-form";
interface FormValues {
city: string;
}
const AddStationLocationModal = ({
open,
handleClose,
handleAddStation,
}: any) => {
const {
register,
handleSubmit,
reset,
formState: { errors },
} = useForm<FormValues>();
const onSubmit: SubmitHandler<FormValues> = (data) => {
handleAddStation(data);
reset();
handleClose();
};
return (
<Dialog open={open} onClose={handleClose}>
<DialogTitle>Search Near By Stations</DialogTitle>
<DialogContent>
<form onSubmit={handleSubmit(onSubmit)}>
<TextField
{...register("city", {
required: "City name is required",
minLength: {
value: 2,
message:
"City name must be at least 2 characters",
},
})}
label="City Name"
fullWidth
margin="normal"
error={!!errors.city}
helperText={errors.city?.message}
/>
<DialogActions>
<Button onClick={handleClose} color="secondary">
Cancel
</Button>
<Button
type="submit"
sx={{
backgroundColor: "#52ACDF",
color: "white",
borderRadius: "8px",
width: "100px",
"&:hover": { backgroundColor: "#439BC1" },
}}
>
Search Station
</Button>
</DialogActions>
</form>
</DialogContent>
</Dialog>
);
};
export default AddStationLocationModal;

View file

@ -85,7 +85,7 @@ interface CustomTableProps {
deleteModal: boolean;
handleStatusToggle?: (id: string, currentStatus: number) => void;
tableType: string; // Adding tableType prop to change header text dynamically
handleClickOpen: () => void;
handleClickOpen?: () => void;
}
const CustomTable: React.FC<CustomTableProps> = ({
@ -207,6 +207,7 @@ const CustomTable: React.FC<CustomTableProps> = ({
(row) =>
(row.name &&
row.name.toLowerCase().includes(searchQuery.toLowerCase())) ||
row.registeredAddress.toLowerCase().includes(searchQuery.toLowerCase()) ||
false
);
@ -254,6 +255,8 @@ const CustomTable: React.FC<CustomTableProps> = ({
return "Vehicles";
case "station":
return "Charging Station";
case "external-station":
return "Charging Station";
case "booking":
return "Booking";
case "slots":
@ -318,7 +321,9 @@ const CustomTable: React.FC<CustomTableProps> = ({
width: "100%",
}}
>
{!(user?.userType === "user" && tableType === "slots") && (
{!(
user?.userType === "user" &&
(tableType === "slots" )) && (
<Button
sx={{
backgroundColor: "#52ACDF",
@ -349,6 +354,8 @@ const CustomTable: React.FC<CustomTableProps> = ({
return "Booking";
case "slots":
return "Slot";
case "external-station":
return "Location";
default:
return "Item";
}

View file

@ -92,23 +92,42 @@ const EditStationModal: React.FC<EditStationModalProps> = ({
setValue("status", editRow.status);
setValue("allowedCarIds", editRow.allowedCarIds || []);
setSelectedVehicles(editRow.allowedCarIds || []);
// Set selectedBrands based on the vehicles associated with the station
const brands = vehicles
.filter((vehicle) => editRow.allowedCarIds.includes(vehicle.id))
.map((vehicle) => vehicle.company);
setSelectedBrands(brands);
} else {
reset();
setSelectedBrands([]); // Ensure it's cleared when not editing
setSelectedVehicles([]);
}
}, [editRow, setValue, reset]);
}, [editRow, setValue, reset, vehicles]);
// 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[]);
const brands = event.target.value as string[];
setSelectedBrands(brands);
// Filter vehicles based on the new brand selection
const filtered = vehicles.filter((vehicle) =>
brands.includes(vehicle.company)
);
setSelectedVehicles(filtered.map((v) => v.name)); // Automatically select vehicles for the selected brands
};
// Handle changes in vehicle selection
const handleCheckboxChange = (
event: React.ChangeEvent<{ value: unknown }>
@ -118,23 +137,24 @@ const EditStationModal: React.FC<EditStationModalProps> = ({
setValue("allowedCarIds", value); // Update allowedCarIds in form state
};
const onSubmit = (data: FormData) => {
const vehicleIds = vehicles
.filter((vehicle) => selectedVehicles.includes(vehicle.name))
.map((vehicle) => Number(vehicle.id));
const onSubmit = (data: FormData) => {
const vehicleIds = vehicles
.filter((vehicle) => selectedVehicles.includes(vehicle.name))
.map((vehicle) => Number(vehicle.id));
handleUpdate(
editRow.id,
data.name,
data.registeredAddress,
data.totalSlots,
vehicleIds
);
handleClose();
reset();
setSelectedBrands([]); // Reset brands after submit
setSelectedVehicles([]); // Reset selected vehicles
};
handleUpdate(
editRow.id,
data.name,
data.registeredAddress,
data.totalSlots,
vehicleIds
);
handleClose();
reset();
//setSelectedBrands([]); // Reset brands after submit
setSelectedVehicles([]); // Reset selected vehicles
};
console.log("editRow:", editRow);

View file

@ -81,6 +81,11 @@ export default function MenuContent({ hidden }: PropType) {
icon: <ChecklistSharpIcon />,
url: "/panel/slot-list", // Placeholder for now
},
userRole === "user" && {
text: "Near By Stations",
icon: <EvStationIcon />,
url: "/panel/external-station-list", // Placeholder for now
},
];
const filteredMenuItems = baseMenuItems.filter(Boolean);

View file

@ -22,7 +22,7 @@ export default function EVSlotList() {
const availableSlots = useSelector(
(state: RootState) => state?.slotReducer.availableSlots
);
const { user } = useSelector((state: RootState) => state?.profileReducer);
useEffect(() => {
dispatch(fetchAvailableSlots());
}, [dispatch]);
@ -89,7 +89,9 @@ export default function EVSlotList() {
{ id: "startTime", label: "Start Time" },
{ id: "endTime", label: "End Time" },
{ id: "isAvailable", label: "Is Available", align: "center" },
{ id: "action", label: "Action", align: "center" },
...(user?.userType === "manager"
? [{ id: "action", label: "Action", align: "center" as const }]
: []),
];
const slotRows = availableSlots?.length

View file

@ -0,0 +1,92 @@
import CustomTable, { Column } from "../../components/CustomTable/customTable";
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "../../redux/reducers";
import { AppDispatch } from "../../redux/store/store";
import { addStationLocation, externalStationList, } from "../../redux/slices/stationSlice"; // Import setCity
import AddStationLocationModal from "../../components/AddStationLocation/addStationLocation";
import { useForm } from "react-hook-form";
export default function ExternalStationList() {
const dispatch = useDispatch<AppDispatch>();
const { stations, loading, error } = useSelector(
(state: RootState) => state.stationReducer
);
const [city, setCityState] = useState("");
const [addModalOpen, setAddModalOpen] = useState<boolean>(false);
const [rowData, setRowData] = useState<any | null>(null);
const { reset } = useForm();
// Fetch stations when the component mounts
useEffect(() => {
dispatch(externalStationList());
}, [dispatch]);
const handleClickOpen = () => {
setRowData(null);
setAddModalOpen(true);
};
const handleCloseModal = () => {
setAddModalOpen(false);
setRowData(null);
reset();
};
const handleAddStation = async (data: { city: string }) => {
try {
await dispatch(addStationLocation(data));
dispatch(externalStationList());
handleCloseModal();
} catch (error) {
console.error("Error adding slot", error);
}
};
// Handle City Submission
// Error Handling UI
if (error) {
console.error("Error fetching stations:", error);
}
// Mapping and formatting stations
const categoryRows = stations?.length
? stations.map((station: any, index: number) => ({
id: station.id,
srno: index + 1,
name: station.name,
registeredAddress: station.registeredAddress,
totalSlots: station.totalSlots,
status: station.status === 1 ? "Available" : "Not Available",
}))
: [];
const categoryColumns: Column[] = [
{ id: "srno", label: "Sr No" },
{ id: "id", label: "Station ID" },
{ id: "name", label: "Station Name" },
{ id: "registeredAddress", label: "Station Location" },
{ id: "totalSlots", label: "Total Slots" },
{ id: "status", label: "Status" },
];
return (
<>
{/* Custom Table */}
<CustomTable
columns={categoryColumns}
rows={categoryRows}
tableType="external-station"
loading={loading}
currentPanel="external-station-list"
handleClickOpen={handleClickOpen}
/>
<AddStationLocationModal
open={addModalOpen}
handleClose={handleCloseModal}
handleAddStation={handleAddStation}
/>
</>
);
}

View file

@ -23,6 +23,8 @@ export default function RoleList() {
const [rowData, setRowData] = React.useState<any | null>(null);
const [searchTerm, setSearchTerm] = useState("");
const [showPermissions, setShowPermissions] = useState(false);
const [modalOpen, setModalOpen] = useState(false);
const dispatch = useDispatch<AppDispatch>();
const navigate = useNavigate();
@ -120,3 +122,4 @@ export default function RoleList() {
);
}

View file

@ -11,6 +11,7 @@ interface Station {
totalSlots: number;
status: number;
allowedCarIds: number[];
city: string;
}
interface StationState {
@ -47,6 +48,50 @@ export const stationList = createAsyncThunk<any, void, { rejectValue: string }>(
}
}
);
export const externalStationList = createAsyncThunk<
any,
void,
{ rejectValue: string }
>("fetchExternalStations", async (_, { rejectWithValue }) => {
try {
const response = await http.get("/external-charging-stations");
if (!response.data) throw new Error("Invalid API response");
// Return the response data directly, the structure will be handled in the reducer
return response.data;
} catch (error: any) {
console.error("Error fetching external stations:", error);
toast.error(
"Error Fetching Stations: " +
(error.response?.data?.message || error.message)
);
return rejectWithValue(
error.response?.data?.message ||
error.message ||
"An error occurred"
);
}
});
export const addStationLocation = createAsyncThunk<
any,
{
city: string;
},
{ rejectValue: string }
>("Station/addStationLocation", async ({ city }, { rejectWithValue }) => {
try {
const response = await http.post("/charging-stations/fetch", { city });
toast.success("Station created successfully");
return response.data; // Assuming the response contains the created station data
} catch (error: any) {
toast.error(
"Failed to create Station: " +
(error.response?.data?.message || "Unknown error")
);
return rejectWithValue(
error.response?.data?.message || "An error occurred"
);
}
});
export const getAllStations = createAsyncThunk<
any,
void,
@ -209,6 +254,47 @@ const stationSlice = createSlice({
state.loading = false;
state.error = action.payload || "Failed to fetch stations";
})
.addCase(externalStationList.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(
externalStationList.fulfilled,
(state, action: PayloadAction<any>) => {
state.loading = false;
console.log("first", action.payload.data);
state.stations = action.payload.data || [];
}
)
.addCase(externalStationList.rejected, (state, action) => {
state.loading = false;
state.error =
action.payload || "Failed to fetch external stations";
})
.addCase(addStationLocation.pending, (state) => {
state.loading = true;
})
.addCase(
addStationLocation.fulfilled,
(state, action: PayloadAction<any>) => {
state.loading = false;
if (action.payload.data) {
state.stations.push(action.payload.data);
}
}
)
.addCase(
addStationLocation.rejected,
(state, action: PayloadAction<string | undefined>) => {
state.loading = false;
state.error = action.payload || "Failed to create station";
}
)
.addCase(createStation.pending, (state) => {
state.loading = true;
})

View file

@ -22,7 +22,7 @@ const StationList = lazy(() => import("./pages/StationList"));
const EVSlotManagement = lazy(() => import("./pages/EVSlotManagement"));
const BookingList = lazy(() => import("./pages/BookingList"));
const EvSlotList = lazy(() => import("./pages/EvSlotList"));
const ExternalStationList = lazy(() => import("./pages/ExternalStationList/externalStationList.tsx"));
interface ProtectedRouteProps {
// caps: string[];
@ -108,7 +108,10 @@ export default function AppRouter() {
path="slot-list"
element={<ProtectedRoute component={<EvSlotList />} />}
/>
<Route
path="external-station-list"
element={<ProtectedRoute component={<ExternalStationList />} />}
/>
</Route>
{/* Catch-all Route */}