dev-jaanvi #1
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;
|
deleteModal: boolean;
|
||||||
handleStatusToggle?: (id: string, currentStatus: number) => void;
|
handleStatusToggle?: (id: string, currentStatus: number) => void;
|
||||||
tableType: string; // Adding tableType prop to change header text dynamically
|
tableType: string; // Adding tableType prop to change header text dynamically
|
||||||
handleClickOpen: () => void;
|
handleClickOpen?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CustomTable: React.FC<CustomTableProps> = ({
|
const CustomTable: React.FC<CustomTableProps> = ({
|
||||||
|
@ -207,6 +207,7 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
||||||
(row) =>
|
(row) =>
|
||||||
(row.name &&
|
(row.name &&
|
||||||
row.name.toLowerCase().includes(searchQuery.toLowerCase())) ||
|
row.name.toLowerCase().includes(searchQuery.toLowerCase())) ||
|
||||||
|
row.registeredAddress.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -254,6 +255,8 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
||||||
return "Vehicles";
|
return "Vehicles";
|
||||||
case "station":
|
case "station":
|
||||||
return "Charging Station";
|
return "Charging Station";
|
||||||
|
case "external-station":
|
||||||
|
return "Charging Station";
|
||||||
case "booking":
|
case "booking":
|
||||||
return "Booking";
|
return "Booking";
|
||||||
case "slots":
|
case "slots":
|
||||||
|
@ -318,7 +321,9 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
||||||
width: "100%",
|
width: "100%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!(user?.userType === "user" && tableType === "slots") && (
|
{!(
|
||||||
|
user?.userType === "user" &&
|
||||||
|
(tableType === "slots" )) && (
|
||||||
<Button
|
<Button
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: "#52ACDF",
|
backgroundColor: "#52ACDF",
|
||||||
|
@ -349,6 +354,8 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
||||||
return "Booking";
|
return "Booking";
|
||||||
case "slots":
|
case "slots":
|
||||||
return "Slot";
|
return "Slot";
|
||||||
|
case "external-station":
|
||||||
|
return "Location";
|
||||||
default:
|
default:
|
||||||
return "Item";
|
return "Item";
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,23 +92,42 @@ const EditStationModal: React.FC<EditStationModalProps> = ({
|
||||||
setValue("status", editRow.status);
|
setValue("status", editRow.status);
|
||||||
setValue("allowedCarIds", editRow.allowedCarIds || []);
|
setValue("allowedCarIds", editRow.allowedCarIds || []);
|
||||||
setSelectedVehicles(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 {
|
} else {
|
||||||
reset();
|
reset();
|
||||||
|
setSelectedBrands([]); // Ensure it's cleared when not editing
|
||||||
|
setSelectedVehicles([]);
|
||||||
}
|
}
|
||||||
}, [editRow, setValue, reset]);
|
}, [editRow, setValue, reset, vehicles]);
|
||||||
|
|
||||||
|
|
||||||
// Filter vehicles based on selected brands
|
// Filter vehicles based on selected brands
|
||||||
const filteredVehicles = vehicles.filter((vehicle) =>
|
const filteredVehicles = vehicles.filter((vehicle) =>
|
||||||
selectedBrands.includes(vehicle.company)
|
selectedBrands.includes(vehicle.company)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
// Handle changes in vehicle brand selection
|
// Handle changes in vehicle brand selection
|
||||||
const handleBrandChange = (
|
const handleBrandChange = (
|
||||||
event: React.ChangeEvent<{ value: unknown }>
|
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
|
// Handle changes in vehicle selection
|
||||||
const handleCheckboxChange = (
|
const handleCheckboxChange = (
|
||||||
event: React.ChangeEvent<{ value: unknown }>
|
event: React.ChangeEvent<{ value: unknown }>
|
||||||
|
@ -132,9 +151,10 @@ const EditStationModal: React.FC<EditStationModalProps> = ({
|
||||||
);
|
);
|
||||||
handleClose();
|
handleClose();
|
||||||
reset();
|
reset();
|
||||||
//setSelectedBrands([]); // Reset brands after submit
|
setSelectedBrands([]); // Reset brands after submit
|
||||||
setSelectedVehicles([]); // Reset selected vehicles
|
setSelectedVehicles([]); // Reset selected vehicles
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("editRow:", editRow);
|
console.log("editRow:", editRow);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,11 @@ export default function MenuContent({ hidden }: PropType) {
|
||||||
icon: <ChecklistSharpIcon />,
|
icon: <ChecklistSharpIcon />,
|
||||||
url: "/panel/slot-list", // Placeholder for now
|
url: "/panel/slot-list", // Placeholder for now
|
||||||
},
|
},
|
||||||
|
userRole === "user" && {
|
||||||
|
text: "Near By Stations",
|
||||||
|
icon: <EvStationIcon />,
|
||||||
|
url: "/panel/external-station-list", // Placeholder for now
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const filteredMenuItems = baseMenuItems.filter(Boolean);
|
const filteredMenuItems = baseMenuItems.filter(Boolean);
|
||||||
|
|
|
@ -22,7 +22,7 @@ export default function EVSlotList() {
|
||||||
const availableSlots = useSelector(
|
const availableSlots = useSelector(
|
||||||
(state: RootState) => state?.slotReducer.availableSlots
|
(state: RootState) => state?.slotReducer.availableSlots
|
||||||
);
|
);
|
||||||
|
const { user } = useSelector((state: RootState) => state?.profileReducer);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(fetchAvailableSlots());
|
dispatch(fetchAvailableSlots());
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
@ -89,7 +89,9 @@ export default function EVSlotList() {
|
||||||
{ id: "startTime", label: "Start Time" },
|
{ id: "startTime", label: "Start Time" },
|
||||||
{ id: "endTime", label: "End Time" },
|
{ id: "endTime", label: "End Time" },
|
||||||
{ id: "isAvailable", label: "Is Available", align: "center" },
|
{ 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
|
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 [rowData, setRowData] = React.useState<any | null>(null);
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const [showPermissions, setShowPermissions] = useState(false);
|
const [showPermissions, setShowPermissions] = useState(false);
|
||||||
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
const dispatch = useDispatch<AppDispatch>();
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
@ -120,3 +122,4 @@ export default function RoleList() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ interface Station {
|
||||||
totalSlots: number;
|
totalSlots: number;
|
||||||
status: number;
|
status: number;
|
||||||
allowedCarIds: number[];
|
allowedCarIds: number[];
|
||||||
|
city: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StationState {
|
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<
|
export const getAllStations = createAsyncThunk<
|
||||||
any,
|
any,
|
||||||
void,
|
void,
|
||||||
|
@ -209,6 +254,47 @@ const stationSlice = createSlice({
|
||||||
state.loading = false;
|
state.loading = false;
|
||||||
state.error = action.payload || "Failed to fetch stations";
|
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) => {
|
.addCase(createStation.pending, (state) => {
|
||||||
state.loading = true;
|
state.loading = true;
|
||||||
})
|
})
|
||||||
|
|
|
@ -22,7 +22,7 @@ const StationList = lazy(() => import("./pages/StationList"));
|
||||||
const EVSlotManagement = lazy(() => import("./pages/EVSlotManagement"));
|
const EVSlotManagement = lazy(() => import("./pages/EVSlotManagement"));
|
||||||
const BookingList = lazy(() => import("./pages/BookingList"));
|
const BookingList = lazy(() => import("./pages/BookingList"));
|
||||||
const EvSlotList = lazy(() => import("./pages/EvSlotList"));
|
const EvSlotList = lazy(() => import("./pages/EvSlotList"));
|
||||||
|
const ExternalStationList = lazy(() => import("./pages/ExternalStationList/externalStationList.tsx"));
|
||||||
|
|
||||||
interface ProtectedRouteProps {
|
interface ProtectedRouteProps {
|
||||||
// caps: string[];
|
// caps: string[];
|
||||||
|
@ -108,7 +108,10 @@ export default function AppRouter() {
|
||||||
path="slot-list"
|
path="slot-list"
|
||||||
element={<ProtectedRoute component={<EvSlotList />} />}
|
element={<ProtectedRoute component={<EvSlotList />} />}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="external-station-list"
|
||||||
|
element={<ProtectedRoute component={<ExternalStationList />} />}
|
||||||
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
{/* Catch-all Route */}
|
{/* Catch-all Route */}
|
||||||
|
|
Loading…
Reference in a new issue