diff --git a/src/components/AddBookingModal/index.tsx b/src/components/AddBookingModal/index.tsx index 42588c8..21fa1f8 100644 --- a/src/components/AddBookingModal/index.tsx +++ b/src/components/AddBookingModal/index.tsx @@ -27,6 +27,8 @@ import { CustomTextField, } from "../AddEditUserModel/styled.css.tsx"; import { stationList } from "../../redux/slices/stationSlice.ts"; +import { fetchAvailableSlots } from "../../redux/slices/slotSlice.ts"; +import dayjs from "dayjs"; export default function AddBookingModal({ open, @@ -48,8 +50,12 @@ export default function AddBookingModal({ const stations = useSelector( (state: RootState) => state?.stationReducer.stations ); - + const availableSlots = useSelector( + (state: RootState) => state.slotReducer.availableSlots + ); + console.log("first", availableSlots); useEffect(() => { + dispatch(fetchAvailableSlots()); dispatch(stationList()); }, [dispatch]); @@ -284,7 +290,7 @@ export default function AddBookingModal({ {/* Start Time and End Time */} - {/* Start Time */} + Start Time @@ -305,7 +311,7 @@ export default function AddBookingModal({ /> - {/* End Time */} + End Time @@ -346,6 +352,114 @@ export default function AddBookingModal({ })} /> + + {/* + + {" Time Slot "} + + ( + + + Select Time Slot + + + {errors.timeSlot && ( + + {errors.timeSlot.message} + + )} + + )} + rules={{ required: "Time Slot is required" }} + /> + */} {/* Submit Button */} diff --git a/src/components/AddStationModal/index.tsx b/src/components/AddStationModal/index.tsx index 7cb912b..ff7ba74 100644 --- a/src/components/AddStationModal/index.tsx +++ b/src/components/AddStationModal/index.tsx @@ -16,7 +16,10 @@ 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 { + fetchVehicleBrands, + vehicleList, +} from "../../redux/slices/VehicleSlice"; // Adjust this import path accordingly import { CustomIconButton, CustomTextField, @@ -40,10 +43,18 @@ export default function AddStationModal({ const vehicles = useSelector( (state: RootState) => state.vehicleReducer.vehicles ); // Adjust according to your actual state structure + const vehicleBrands = useSelector( + (state: RootState) => state.vehicleReducer.vehicleBrands + ); + const [selectedBrand, setSelectedBrand] = useState(""); useEffect(() => { + dispatch(fetchVehicleBrands()); dispatch(vehicleList()); // Fetch vehicles when the component mounts }, [dispatch]); + const filteredVehicles = vehicles.filter( + (vehicle) => vehicle.company === selectedBrand + ); const [selectedVehicles, setSelectedVehicles] = useState([]); @@ -57,26 +68,36 @@ export default function AddStationModal({ // Function to map selected vehicle names to corresponding vehicle ids const getVehicleIds = () => { + console.log("Selected Vehicles: ", selectedVehicles); + console.log("Vehicles List: ", vehicles); return vehicles .filter((vehicle) => selectedVehicles.includes(vehicle.name)) - .map((vehicle) => vehicle.id); // Return an array of ids based on the selected names + .map((vehicle) => vehicle.id); }; const onSubmit = (data: any) => { + // Log the data before sending to backend + console.log("Payload being sent to hfgghfhbackend:", data); + const vehicleIds = getVehicleIds(); // Get the ids of the selected vehicles + console.log("Vehicle IDs: ", vehicleIds); // 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 + totalSlots: Number(data.totalSlots), // Ensure this is a number }; + console.log("Payload to backend:", payload); // Log the final payload + // Handle adding the station with the constructed payload handleAddStation(payload); handleClose(); // Close modal after adding reset(); - setSelectedVehicles([]); // Reset selected vehicles after submission + setSelectedVehicles([]); + setSelectedBrand(""); // Reset selected vehicles after submission }; return ( @@ -231,50 +252,103 @@ export default function AddStationModal({ {/* Vehicle Name Dropdown with Checkboxes */} - - - Vehicle Name - + + + + Select Vehicle Brand + + + Choose Brand + + + {errors.vehicleBrand + ? errors.vehicleBrand.message + : ""} + + + - {/* Dropdown with Checkboxes */} - - Choose Vehicles - - - {errors.vehicleName - ? errors.vehicleName.message - : ""} - - + {/* Vehicle Name Dropdown with Checkboxes */} + + + Vehicle Name + + + + Choose Vehicles + + + {errors.vehicleName + ? errors.vehicleName.message + : ""} + + + @@ -303,4 +377,4 @@ export default function AddStationModal({ ); -} \ No newline at end of file +} diff --git a/src/components/CustomTable/index.tsx b/src/components/CustomTable/index.tsx index b0a76f9..2337733 100644 --- a/src/components/CustomTable/index.tsx +++ b/src/components/CustomTable/index.tsx @@ -108,7 +108,7 @@ const CustomTable: React.FC = ({ const [searchQuery, setSearchQuery] = React.useState(""); const [currentPage, setCurrentPage] = React.useState(1); const usersPerPage = 10; - const { user, isLoading } = useSelector( + const { user, isLoading } = useSelector( (state: RootState) => state?.profileReducer ); const open = Boolean(anchorEl); @@ -137,7 +137,6 @@ const CustomTable: React.FC = ({ } switch (tableType) { - case "admin": dispatch(deleteAdmin(id || "")); break; @@ -484,168 +483,175 @@ const CustomTable: React.FC = ({ /> {/* Menu Actions */} - {open && ( - - - - {viewModal && tableType === "admin" && ( - - handleViewButton(selectedRow?.id) - } - open={viewModal} - setViewModal={setViewModal} - id={selectedRow?.id} - /> - )} - {viewModal && tableType === "vehicle" && ( - - handleViewButton(selectedRow?.id) - } - open={viewModal} - setViewModal={setViewModal} - id={selectedRow?.id} - /> - )} - {viewModal && tableType === "manager" && ( - - handleViewButton(selectedRow?.id) - } - open={viewModal} - setViewModal={setViewModal} - id={selectedRow?.id} - /> - )} - {viewModal && tableType === "user" && ( - - handleViewButton(selectedRow?.id) - } - open={viewModal} - setViewModal={setViewModal} - id={selectedRow?.id} - /> - )} - {viewModal && tableType === "station" && ( - - handleViewButton(selectedRow?.id) - } - open={viewModal} - setViewModal={setViewModal} - id={selectedRow?.id} - /> - )} - - - {tableType === "role" && ( - - )} - {tableType === "station" && ( - - )} + + {viewModal && tableType === "admin" && ( + + handleViewButton(selectedRow?.id) + } + open={viewModal} + setViewModal={setViewModal} + id={selectedRow?.id} + /> + )} + {viewModal && tableType === "vehicle" && ( + + handleViewButton(selectedRow?.id) + } + open={viewModal} + setViewModal={setViewModal} + id={selectedRow?.id} + /> + )} + {viewModal && tableType === "manager" && ( + + handleViewButton(selectedRow?.id) + } + open={viewModal} + setViewModal={setViewModal} + id={selectedRow?.id} + /> + )} + {viewModal && tableType === "user" && ( + + handleViewButton(selectedRow?.id) + } + open={viewModal} + setViewModal={setViewModal} + id={selectedRow?.id} + /> + )} + {viewModal && tableType === "station" && ( + + handleViewButton(selectedRow?.id) + } + open={viewModal} + setViewModal={setViewModal} + id={selectedRow?.id} + /> + )} - - - - )} + + {tableType === "role" && ( + + )} + {tableType === "station" && ( + + )} + + + + + )} {/* Modals */} {deleteModal && ( = ({ email: data.email, phone: data.phone, stationId: data.stationId, - + Manager: undefined, + id: 0, + chargingStation: "" }, }) ).unwrap(); // Ensure that it throws an error if the update fails diff --git a/src/components/EditSlotModal/index.tsx b/src/components/EditSlotModal/index.tsx new file mode 100644 index 0000000..f1ad819 --- /dev/null +++ b/src/components/EditSlotModal/index.tsx @@ -0,0 +1,263 @@ +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 + +interface EditSlotModalProps { + open: boolean; + handleClose: () => void; + editRow: any; // Slot data including id +} + +interface FormData { + date: string; + startTime: string; + endTime: string; + isAvailable: boolean; +} + +const EditSlotModal: React.FC = ({ + open, + handleClose, + editRow, +}) => { + const dispatch = useDispatch(); // Use dispatch to send Redux actions + const { + control, + handleSubmit, + formState: { errors }, + setValue, + reset, + } = useForm({ + defaultValues: { + date: "", + startTime: "", + endTime: "", + isAvailable: false, + }, + }); + + const [loading, setLoading] = useState(false); + const [isAvailable, setIsAvailable] = useState( + editRow?.isAvailable || false + ); // Default to editRow availability + + // Set values if editRow is provided + useEffect(() => { + if (editRow) { + setValue("date", editRow.date); + setValue("startTime", editRow.startTime); + setValue("endTime", editRow.endTime); + setIsAvailable(editRow.isAvailable); // Set the initial availability correctly + } else { + reset(); + } + }, [editRow, setValue, reset]); + + const onSubmit = async (data: FormData) => { + if (editRow) { + try { + // Convert boolean availability to 1/0 + const availabilityStatus = isAvailable ? true : false; + + await dispatch( + updateSlot({ + id: editRow.id, // Slot ID + slotData: { + date: data.date, + startTime: data.startTime, + endTime: data.endTime, + isAvailable: availabilityStatus, + }, + }) + ).unwrap(); // Ensure that it throws an error if the update fails + dispatch(fetchAvailableSlots()); // Assuming this action fetches the updated slot list + handleClose(); // Close modal on success + reset(); // Reset form fields after submit + } catch (error) { + console.error(error); + // Handle the error or show a toast + } finally { + setLoading(false); // Stop loading state + } + } + }; + + return ( + { + if (reason === "backdropClick") { + return; + } + handleClose(); // Close modal when clicking cross or cancel + }} + aria-labelledby="edit-slot-modal" + > + + {/* Header */} + + + Edit Slot + + + + + + + {/* Horizontal Line */} + + + {/* Input Fields */} + + {/* Date */} + + + Date + + ( + + )} + /> + + + {/* Start Time */} + + + Start Time + + ( + + )} + /> + + + {/* End Time */} + + + End Time + + ( + + )} + /> + + + {/* Availability Toggle */} + + + + {isAvailable ? "Available" : "Not Available"} + + + + + {/* Submit Button */} + + + + + + ); +}; + +export default EditSlotModal; diff --git a/src/lib/https.ts b/src/lib/https.ts index 612552c..b6ad723 100644 --- a/src/lib/https.ts +++ b/src/lib/https.ts @@ -15,8 +15,8 @@ http.interceptors.response.use( (response) => response, (error) => { - if (error.response && error.response.status === 401) { - // window.location.href = "/login"; + if (error.response && error.response.status === 403 ) { + window.location.href = "/login"; // const history = useHistory(); // history.push("/login"); diff --git a/src/pages/EvSlotList/index.tsx b/src/pages/EvSlotList/index.tsx index 207f26e..4e8e0c7 100644 --- a/src/pages/EvSlotList/index.tsx +++ b/src/pages/EvSlotList/index.tsx @@ -4,9 +4,15 @@ 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 EditManagerModal from "../../components/EditManagerModal"; +import EditSlotModal from "../../components/EditSlotModal"; export default function EVSlotList() { const [addModalOpen, setAddModalOpen] = useState(false); const [editModalOpen, setEditModalOpen] = useState(false); @@ -50,6 +56,31 @@ export default function EVSlotList() { console.error("Error adding slot", error); } }; + console.log("isAvailable type:", typeof isAvailable); + const handleUpdate = async ( + id: string, + date: string, + startTime: string, + endTime: string, + isAvailable: boolean + ) => { + try { + // Update manager with stationId + await dispatch( + updateSlot({ + id, + date, + startTime, + endTime, + isAvailable, + }) + ); + await dispatch(fetchAvailableSlots()); // Refresh the list after update + handleCloseModal(); // Close modal after update + } catch (error) { + console.error("Update failed", error); + } + }; const slotColumns: Column[] = [ { id: "srno", label: "Sr No" }, @@ -66,25 +97,23 @@ export default function EVSlotList() { 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"); - - 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", - }; - }) + 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("availability", availableSlots.isAvailable); + 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", + }; + }) : []; -console.log("Rows",slotRows) - return ( <> @@ -105,6 +134,12 @@ console.log("Rows",slotRows) handleClose={handleCloseModal} handleAddSlot={handleAddSlot} /> + ); } diff --git a/src/redux/slices/VehicleSlice.ts b/src/redux/slices/VehicleSlice.ts index c4de897..ba71ebb 100644 --- a/src/redux/slices/VehicleSlice.ts +++ b/src/redux/slices/VehicleSlice.ts @@ -1,9 +1,14 @@ import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit"; import http from "../../lib/https"; import { toast } from "sonner"; +interface VehicleBrand { + id: string; + company: string; +} interface Vehicle { - id: number; + brandId: string; + id: string; name: string; company: string; modelName: string; @@ -11,15 +16,36 @@ 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"); + } + // Assuming that 'data' contains an array of objects with a 'company' field + return response.data.data.map((item: any) => ({ + id: item.company, // You can use 'company' as the unique identifier + name: item.company, // The name field will be used in the dropdown + })); + } catch (error: any) { + return rejectWithValue("Failed to fetch vehicle brands"); + } +}); export const vehicleList = createAsyncThunk< Vehicle, @@ -122,6 +148,21 @@ 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) => { + 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; diff --git a/src/redux/slices/slotSlice.ts b/src/redux/slices/slotSlice.ts index 2541727..467d2ee 100644 --- a/src/redux/slices/slotSlice.ts +++ b/src/redux/slices/slotSlice.ts @@ -92,7 +92,10 @@ export const updateSlot = createAsyncThunk< { 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 + ); toast.success("Slot updated successfully"); return response.data.data; // Return updated slot data } catch (error: any) {