Dynamic Ev Apis Integration implemented
This commit is contained in:
parent
57160520fb
commit
4a15fd3293
76
src/components/AddStationLocation/addStationLocation.tsx
Normal file
76
src/components/AddStationLocation/addStationLocation.tsx
Normal 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;
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
92
src/pages/ExternalStationList/externalStationList.tsx
Normal file
92
src/pages/ExternalStationList/externalStationList.tsx
Normal 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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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() {
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
})
|
||||
|
|
|
@ -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 */}
|
||||
|
|
Loading…
Reference in a new issue