diff --git a/src/components/AddEditCategoryModal/index.tsx b/src/components/AddEditCategoryModal/index.tsx index 9802f69..d802b5d 100644 --- a/src/components/AddEditCategoryModal/index.tsx +++ b/src/components/AddEditCategoryModal/index.tsx @@ -14,7 +14,7 @@ import { useForm, Controller } from "react-hook-form"; import { CustomIconButton, CustomTextField, -} from "../AddUserModel/styled.css.tsx"; +} from "../AddEditUserModel/styled.css.tsx"; //By Jaanvi : Edit Model :: 11-feb-25 interface AddEditCategoryModalProps { @@ -102,7 +102,12 @@ const AddEditCategoryModal: React.FC = ({ return ( { + if (reason === "backdropClick") { + return; + } + handleClose(); // Close modal when clicking cross or cancel + }} aria-labelledby="add-edit-category-modal" > = ({ message: "Maximum 30 characters allowed", }, + pattern: { + value: /^[A-Za-z\s]+$/, // Only letters and spaces are allowed + message: + "Admin Name must only contain letters and spaces", + }, }} render={({ field }) => ( = ({ rules={{ required: "Email is required", pattern: { - value: /\S+@\S+\.\S+/, + value: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, message: - "Please enter a valid email address.", + "Please enter a valid email address (e.g., example@domain.com).", }, }} render={({ field }) => ( diff --git a/src/components/AddUserModel/index.tsx b/src/components/AddEditUserModel/index.tsx similarity index 91% rename from src/components/AddUserModel/index.tsx rename to src/components/AddEditUserModel/index.tsx index c07240c..1a84146 100644 --- a/src/components/AddUserModel/index.tsx +++ b/src/components/AddEditUserModel/index.tsx @@ -30,7 +30,6 @@ interface AddUserModalProps { id: string, name: string, email: string, - password: string, phone: string, ) => void; @@ -50,24 +49,24 @@ const AddUserModal: React.FC = ({ handleSubmit, formState: { errors }, reset, + setValue, } = useForm({ defaultValues: { name: "", email: "", - password: "", phone: "", }, }); - // useEffect(() => { - // if (editRow) { - // setValue("name", editRow.name); - // setValue("email", editRow.email); - // setValue("password", editRow.password); - // setValue("phone", editRow.phone); - // } else { - // reset(); - // } - // }, [editRow, setValue, reset]); + + useEffect(() => { + if (editRow) { + setValue("name", editRow.name); + setValue("email", editRow.email); + setValue("phone", editRow.phone); + } else { + reset(); + } + }, [editRow, setValue,reset]); const onSubmit = (data: FormData) => { if (editRow) { @@ -75,7 +74,6 @@ const AddUserModal: React.FC = ({ editRow.id, data.name, data.email, - data.password, data.phone ); } else { @@ -93,7 +91,12 @@ const AddUserModal: React.FC = ({ return ( { + if (reason === "backdropClick") { + return; + } + handleClose(); // Close modal when clicking cross or cancel + }} aria-labelledby="add-user-modal" > = ({ message: "Maximum 30 characters allowed", }, + pattern: { + value: /^[A-Za-z\s]+$/, // Only letters and spaces are allowed + message: + "User Name must only contain letters and spaces", + }, }} render={({ field }) => ( = ({ {/* Second Row - Two Inputs */} - = ({ /> )} /> - + )} = ({ export default AddUserModal; +function setValue(arg0: string, name: any) { + throw new Error("Function not implemented."); +} + diff --git a/src/components/AddUserModel/styled.css.tsx b/src/components/AddEditUserModel/styled.css.tsx similarity index 100% rename from src/components/AddUserModel/styled.css.tsx rename to src/components/AddEditUserModel/styled.css.tsx diff --git a/src/components/AddManagerModal/index.tsx b/src/components/AddManagerModal/index.tsx index 8094776..5309be9 100644 --- a/src/components/AddManagerModal/index.tsx +++ b/src/components/AddManagerModal/index.tsx @@ -1,22 +1,27 @@ - import { Controller, useForm } from "react-hook-form"; -import { Box, Button, Typography, Modal, IconButton, InputAdornment } from "@mui/material"; +import { + Box, + Button, + Typography, + Modal, + IconButton, + InputAdornment, +} from "@mui/material"; import CloseIcon from "@mui/icons-material/Close"; import { Visibility, VisibilityOff } from "@mui/icons-material"; import { useDispatch } from "react-redux"; -import { addManager } from "../../redux/slices/managerSlice"; +import { addManager, managerList } from "../../redux/slices/managerSlice"; import { CustomIconButton, CustomTextField, -} from "../AddUserModel/styled.css.tsx"; -import React from "react"; +} from "../AddEditUserModel/styled.css.tsx"; +import React, { useState, useRef } from "react"; -type Props = { - open: boolean; - handleClose: () => void; -}; - -export default function AddManagerModal({ open, handleClose }: Props) { +export default function AddManagerModal({ + open, + handleClose, + handleAddManager, +}) { const dispatch = useDispatch(); const { control, @@ -25,22 +30,23 @@ export default function AddManagerModal({ open, handleClose }: Props) { formState: { errors }, reset, } = useForm(); - const [showPassword, setShowPassword] = React.useState(false); + const [showPassword, setShowPassword] = useState(false); + // Handle form submission const onSubmit = async (data: any) => { const managerData = { name: data.name, email: data.email, phone: data.phone, - registeredAddress: data.registeredAddress, - roleId: data.role, - password: data.password, - roleName: "Manager", // Add a role name (you can replace this with a dynamic value if needed) + password: data.password, + stationId: data.stationId, // Send stationId here + }; try { // Dispatch the addManager action - await dispatch(addManager(managerData)).unwrap(); + await dispatch(addManager(managerData)); + dispatch(managerList()); handleClose(); // Close modal after successful addition reset(); // Reset form fields } catch (error) { @@ -48,15 +54,20 @@ export default function AddManagerModal({ open, handleClose }: Props) { } }; - // Toggle password visibility - const togglePasswordVisibility = () => { + const togglePasswordVisibility = (e: React.MouseEvent) => { + e.preventDefault(); // Prevent focus loss setShowPassword((prev) => !prev); }; return ( { + if (reason === "backdropClick") { + return; + } + handleClose(); // Close modal when clicking cross or cancel + }} aria-labelledby="add-manager-modal" > - {/* First Row - Manager Name */} - - {/* Manager Name */} - + {/* Manager Name */} + + Manager Name @@ -117,25 +121,47 @@ export default function AddManagerModal({ open, handleClose }: Props) { {...register("name", { required: "Manager Name is required", minLength: { - value: 2, + value: 3, message: - "Manager Name must be at least 2 characters long", + "Minimum 3 characters required", + }, + maxLength: { + value: 30, + message: + "Maximum 30 characters allowed", + }, + pattern: { + value: /^[A-Za-z\s]+$/, // Only letters and spaces are allowed + message: + "Manager Name must only contain letters and spaces", }, })} /> + + + Station Id + + + - - {/* Second Row - Email, Password */} - + {/* Email and Password */} + {/* Email */} - + Email @@ -158,91 +184,73 @@ export default function AddManagerModal({ open, handleClose }: Props) { {/* Password */} - - + + Password + + - - Password - - ( - - - {showPassword ? ( - - ) : ( - - )} - - - ), - }} - error={!!errors.password} - helperText={ - errors.password?.message - } - /> - )} - /> - - + render={({ field }) => ( + + + {showPassword ? ( + + ) : ( + + )} + + + ), + }} + error={!!errors.password} + helperText={errors.password?.message} + /> + )} + /> + - {/* Third Row - Phone Number, Registered Address */} - - {/* Phone Number */} - + {/* Phone and Registered Address */} + + {/* Phone */} + Phone Number @@ -265,32 +273,6 @@ export default function AddManagerModal({ open, handleClose }: Props) { /> - {/* Registered Address */} - - - Registered Address - - - {/* Submit Button */} diff --git a/src/components/AddStationModal/index.tsx b/src/components/AddStationModal/index.tsx new file mode 100644 index 0000000..16cb174 --- /dev/null +++ b/src/components/AddStationModal/index.tsx @@ -0,0 +1,205 @@ +import { useForm } from "react-hook-form"; +import { Box, Button, Typography, Modal } from "@mui/material"; +import CloseIcon from "@mui/icons-material/Close"; +import { + CustomIconButton, + CustomTextField, +} from "../AddEditUserModel/styled.css.tsx"; // Assuming custom styled components + +export default function AddStationModal({ + open, + handleClose, + handleAddStation, +}) { + const { + register, + handleSubmit, + formState: { errors }, + reset, + } = useForm(); + + const onSubmit = (data: any) => { + handleAddStation(data); // Add station to the list + handleClose(); // Close modal after adding + reset(); + }; + + return ( + { + if (reason === "backdropClick") { + return; + } + handleClose(); // Close modal when clicking cross or cancel + }} + aria-labelledby="add-station-modal" + > + + {/* Header */} + + + Add Charging Station + + + + + + + {/* Horizontal Line */} + + + {/* Form */} +
+ {/* Input Fields */} + + {/* First Row - Two Inputs */} + + + + Station Name + + + + + + + Station Location + + + + + + {/* Second Row - Total Slots */} + + + + Total Slots + + + + + + + {/* Submit Button */} + + + +
+
+
+ ); +} diff --git a/src/components/AddVehicleModal/index.tsx b/src/components/AddVehicleModal/index.tsx index e7d86c9..f7747f8 100644 --- a/src/components/AddVehicleModal/index.tsx +++ b/src/components/AddVehicleModal/index.tsx @@ -6,7 +6,10 @@ import { Modal, } from "@mui/material"; import CloseIcon from "@mui/icons-material/Close"; -import { CustomIconButton, CustomTextField } from "../AddUserModel/styled.css.tsx"; +import { + CustomIconButton, + CustomTextField, +} from "../AddEditUserModel/styled.css.tsx"; export default function AddVehicleModal({ open, handleClose, @@ -28,7 +31,12 @@ export default function AddVehicleModal({ return ( { + if (reason === "backdropClick") { + return; + } + handleClose(); // Close modal when clicking cross or cancel + }} aria-labelledby="add-vehicle-modal" > @@ -232,7 +250,6 @@ export default function AddVehicleModal({ }} > @@ -513,6 +518,26 @@ const filteredRows = rows.filter( 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} + /> + )} + )} -//
-//
-//
-// ); -// }; -// export default EditManagerModal; import React, { useEffect, useState } from "react"; import { Box, @@ -323,11 +9,11 @@ import { import CloseIcon from "@mui/icons-material/Close"; import { useForm, Controller } from "react-hook-form"; import { useDispatch } from "react-redux"; -import { updateManager } from "../../redux/slices/managerSlice"; // Import the updateManager action +import { managerList, updateManager } from "../../redux/slices/managerSlice"; // Import the updateManager action import { CustomIconButton, CustomTextField, -} from "../AddUserModel/styled.css.tsx"; // Custom styled components +} from "../AddEditUserModel/styled.css.tsx"; // Custom styled components interface EditManagerModalProps { open: boolean; @@ -338,8 +24,8 @@ interface EditManagerModalProps { interface FormData { name: string; email: string; - registeredAddress: string; phone: string; + stationId: string; } const EditManagerModal: React.FC = ({ @@ -358,8 +44,8 @@ const EditManagerModal: React.FC = ({ defaultValues: { name: "", email: "", - registeredAddress: "", phone: "", + stationId: "", }, }); @@ -370,8 +56,8 @@ const EditManagerModal: React.FC = ({ if (editRow) { setValue("name", editRow.name); setValue("email", editRow.email); - setValue("registeredAddress", editRow.registeredAddress); setValue("phone", editRow.phone); + setValue("stationId", editRow.stationId); } else { reset(); } @@ -379,9 +65,6 @@ const EditManagerModal: React.FC = ({ const onSubmit = async (data: FormData) => { if (editRow) { - setLoading(true); // Start loading state - - // Dispatch the updateManager action from Redux try { await dispatch( updateManager({ @@ -389,12 +72,13 @@ const EditManagerModal: React.FC = ({ managerData: { name: data.name, email: data.email, - registeredAddress: data.registeredAddress, phone: data.phone, + stationId: data.stationId, + }, }) ).unwrap(); // Ensure that it throws an error if the update fails - + dispatch(managerList()); handleClose(); // Close modal on success reset(); // Reset form fields after submit } catch (error) { @@ -409,7 +93,12 @@ const EditManagerModal: React.FC = ({ return ( { + if (reason === "backdropClick") { + return; + } + handleClose(); // Close modal when clicking cross or cancel + }} aria-labelledby="edit-manager-modal" > = ({ {/* Horizontal Line */} - {/* Input Fields */} - + {/* Input Fields (Name, Email, and Phone) */} + {/* Manager Name */} - + Manager Name ( = ({ /> - {/* Registered Address */} - - - Registered Address - - ( - - )} - /> - - {/* Email */} - + Email @@ -537,13 +197,7 @@ const EditManagerModal: React.FC = ({ {/* Phone Number */} - + Phone Number @@ -563,6 +217,26 @@ const EditManagerModal: React.FC = ({ )} /> + {/* + + StationId + + ( + + )} + /> + */} {/* Submit Button */} @@ -591,5 +265,4 @@ const EditManagerModal: React.FC = ({ ); }; - -export default EditManagerModal; +export default EditManagerModal; \ No newline at end of file diff --git a/src/components/EditStationModal/index.tsx b/src/components/EditStationModal/index.tsx new file mode 100644 index 0000000..ada74a7 --- /dev/null +++ b/src/components/EditStationModal/index.tsx @@ -0,0 +1,270 @@ +import React, { useEffect } from "react"; +import { Box, Button, Typography, Modal } from "@mui/material"; +import CloseIcon from "@mui/icons-material/Close"; +import { useForm, Controller } from "react-hook-form"; +import { + CustomIconButton, + CustomTextField, +} from "../AddEditUserModel/styled.css.tsx"; + +interface EditStationModalProps { + open: boolean; + handleClose: () => void; + handleUpdate: ( + id: string, + name: string, + registeredAddress: string, + totalSlots: number, + status: number + ) => void; + editRow: any; +} + +interface FormData { + name: string; + registeredAddress: string; + totalSlots: number; + status: number; +} + +const EditStationModal: React.FC = ({ + open, + handleClose, + handleUpdate, + editRow, +}) => { + const { + control, + handleSubmit, + formState: { errors }, + setValue, + reset, + } = useForm({ + defaultValues: { + name: "", + registeredAddress: "", + totalSlots: 0, + status: 1, + }, + }); + + // Set values if editRow is provided + useEffect(() => { + if (editRow) { + setValue("name", editRow.name); + setValue("registeredAddress", editRow.registeredAddress); + setValue("totalSlots", editRow.totalSlots); + setValue("status", editRow.number); + } else { + reset(); + } + }, [editRow, setValue, reset]); + + const onSubmit = (data: FormData) => { + if (editRow) { + handleUpdate( + editRow.id, + data.name, + data.registeredAddress, + data.totalSlots, + data.status + ); + } + handleClose(); // Close the modal + reset(); // Reset the form fields + }; + + return ( + { + if (reason === "backdropClick") { + return; + } + handleClose(); // Close modal when clicking cross or cancel + }} + aria-labelledby="edit-station-modal" + > + + {/* Header */} + + + Edit Charging Station + + + + + + + {/* Horizontal Line */} + + + {/* Input Fields */} + + {/* First Row - Two Inputs */} + + + + Station Name + + ( + + )} + /> + + + + + Station Location + + ( + + )} + /> + + + + {/* Second Row - Total Slots */} + + + + Total Slots + + ( + + )} + /> + + + + + + {/* Submit Button */} + + + + + + ); +}; + +export default EditStationModal; diff --git a/src/components/EditVehicleModal/index.tsx b/src/components/EditVehicleModal/index.tsx index 3d51f09..33ee197 100644 --- a/src/components/EditVehicleModal/index.tsx +++ b/src/components/EditVehicleModal/index.tsx @@ -11,7 +11,7 @@ import { updateVehicle } from "../../redux/slices/VehicleSlice"; import { CustomIconButton, CustomTextField, -} from "../AddUserModel/styled.css.tsx"; +} from "../AddEditUserModel/styled.css.tsx"; interface EditVehicleModalProps { open: boolean; handleClose: () => void; @@ -87,7 +87,12 @@ const EditVehicleModal: React.FC = ({ return ( { + if (reason === "backdropClick") { + return; + } + handleClose(); // Close modal when clicking cross or cancel + }} aria-labelledby="edit-vehicle-modal" > = ({ ( = ({ size="small" error={!!errors.name} helperText={errors.name?.message} + /> )} /> diff --git a/src/components/MenuContent/index.tsx b/src/components/MenuContent/index.tsx index 25539bb..8884e48 100644 --- a/src/components/MenuContent/index.tsx +++ b/src/components/MenuContent/index.tsx @@ -43,17 +43,24 @@ export default function MenuContent({ hidden }: PropType) { text: "Users", icon: , url: "/panel/user-list", + }, + userRole === "admin" && { + text: "Charging Stations", + icon: , + url: "/panel/station-list", // Placeholder for now }, userRole === "admin" && { text: "Managers", icon: , url: "/panel/manager-list", // Placeholder for now }, + userRole === "admin" && { text: "Vehicles", icon: , - url: "/panel/vehicles", // Placeholder for now + url: "/panel/vehicle-list", // Placeholder for now }, + ]; const filteredMenuItems = baseMenuItems.filter(Boolean); @@ -99,4 +106,4 @@ export default function MenuContent({ hidden }: PropType) { ); -} \ No newline at end of file +} diff --git a/src/components/Modals/LogOutModal/index.tsx b/src/components/Modals/LogOutModal/index.tsx index 4f788d6..088d075 100644 --- a/src/components/Modals/LogOutModal/index.tsx +++ b/src/components/Modals/LogOutModal/index.tsx @@ -1,81 +1,112 @@ import { Box, Button, Modal, Typography } from "@mui/material"; -import CloseIcon from '@mui/icons-material/Close'; - +import CloseIcon from "@mui/icons-material/Close"; + type Props = { - open: boolean; - setLogoutModal: Function; - handlelogout: any; + open: boolean; + setLogoutModal: Function; + handlelogout: any; }; - + const style = { - position: "absolute", - top: "50%", - left: "50%", - transform: "translate(-50%, -50%)", - width: 330, - bgcolor: "background.paper", - borderRadius: 1.5, - boxShadow: 24, - p: 3, + position: "absolute", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + width: 330, + bgcolor: "background.paper", + borderRadius: 1.5, + boxShadow: 24, + p: 3, }; - + const btnStyle = { py: 1, px: 5, width: "50%", textTransform: "capitalize" }; - + export default function LogoutModal({ - open, - setLogoutModal, - handlelogout, + open, + setLogoutModal, + handlelogout, }: Props) { - return ( - - - - - Logout - setLogoutModal(false)} sx={{ cursor: "pointer", display: "flex", alignItems: "center" }}> - - - - - - Are you sure you want to Logout? - - - - - - - - ); -} \ No newline at end of file + // Function to prevent closing the modal when clicking outside + const handleClose = (event: React.SyntheticEvent) => { + // Close only when clicking the close button, not the backdrop + setLogoutModal(false); + }; + + return ( + + reason !== "backdropClick" && setLogoutModal(false) + } // Prevent closing on backdrop click + aria-labelledby="modal-modal-title" + aria-describedby="modal-modal-description" + BackdropProps={{ + onClick: (e) => e.stopPropagation(), // Stop propagation on backdrop click to prevent closing the modal + }} + > + + + + Logout + setLogoutModal(false)} + sx={{ + cursor: "pointer", + display: "flex", + alignItems: "center", + }} + > + + + + + + Are you sure you want to Logout? + + + + + + + + ); +} diff --git a/src/components/Modals/StationViewModal/index.tsx b/src/components/Modals/StationViewModal/index.tsx new file mode 100644 index 0000000..b47ac08 --- /dev/null +++ b/src/components/Modals/StationViewModal/index.tsx @@ -0,0 +1,127 @@ +import React, { useEffect, useState } from "react"; +import { Box, Modal, Typography, Divider, Grid } from "@mui/material"; +import CloseIcon from "@mui/icons-material/Close"; +import { useSelector } from "react-redux"; +import { RootState } from "../../../redux/reducers"; + +type Props = { + open: boolean; + setViewModal: Function; + handleView: (id: string | undefined) => void; + id?: number | undefined; +}; + +const style = { + position: "absolute", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + width: 400, + bgcolor: "background.paper", + borderRadius: 2, + boxShadow: "0px 4px 20px rgba(0, 0, 0, 0.15)", + p: 4, + display: "flex", + flexDirection: "column", + alignItems: "center", + gap: 2, +}; + +export default function StationViewModal({ open, setViewModal, id }: Props) { + const { stations } = useSelector( + (state: RootState) => state.stationReducer + ); + const [selectedStation, setSelectedStation] = useState(null); + + useEffect(() => { + if (id) { + const station = stations.find((station) => station.id === id); + setSelectedStation(station || null); + } + }, [id, stations]); + + return ( + + + + + + {selectedStation?.name || "Station"}'s Details + + setViewModal(false)} + sx={{ + cursor: "pointer", + display: "flex", + alignItems: "center", + }} + > + + + + + + + + {selectedStation ? ( + + + + Station Name:{" "} + + {selectedStation.name} + + + + + + Station Location:{" "} + + {selectedStation.registeredAddress} + + + + + + Total Slots: + + {selectedStation.totalSlots} + + + + + + Status: + + {selectedStation.status === "available" + ? "Available" + : "Not Available"} + + + + + ) : ( + + No station found with this ID + + )} + + + ); +} diff --git a/src/components/Modals/UserViewModal/index.tsx b/src/components/Modals/UserViewModal/index.tsx new file mode 100644 index 0000000..ffb6c27 --- /dev/null +++ b/src/components/Modals/UserViewModal/index.tsx @@ -0,0 +1,117 @@ +import React, { useEffect, useState } from "react"; +import { Box, Modal, Typography, Divider, Grid } from "@mui/material"; +import CloseIcon from "@mui/icons-material/Close"; +import { useSelector } from "react-redux"; +import { RootState } from "../../../redux/reducers"; + +type Props = { + open: boolean; + setViewModal: Function; + handleView: (id: string | undefined) => void; + id?: string | undefined; +}; + +const style = { + position: "absolute", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + width: 400, + bgcolor: "background.paper", + borderRadius: 2, + boxShadow: "0px 4px 20px rgba(0, 0, 0, 0.15)", + p: 4, + display: "flex", + flexDirection: "column", + alignItems: "center", + gap: 2, +}; + +export default function UserViewModal({ open, setViewModal, id }: Props) { + const { users } = useSelector( + (state: RootState) => state.userReducer // Assuming users are stored in userReducer + ); + const [selectedUser, setSelectedUser] = useState(null); + + useEffect(() => { + if (id) { + const user = users.find((user) => user.id === id); + setSelectedUser(user || null); + } + }, [id, users]); + + return ( + + + + + + {selectedUser?.name || "User"}'s Details + + setViewModal(false)} + sx={{ + cursor: "pointer", + display: "flex", + alignItems: "center", + }} + > + + + + + + + + {selectedUser ? ( + + + + Name:{" "} + + {selectedUser.name} + + + + + + Email:{" "} + + {selectedUser.email} + + + + + + Phone:{" "} + + {selectedUser.phone ?? "N/A"} + + + + + ) : ( + + No user found with this ID + + )} + + + ); +} diff --git a/src/components/Modals/ViewManagerModal/index.tsx b/src/components/Modals/ViewManagerModal/index.tsx index 4b4b850..5ee6b2f 100644 --- a/src/components/Modals/ViewManagerModal/index.tsx +++ b/src/components/Modals/ViewManagerModal/index.tsx @@ -35,6 +35,7 @@ export default function ManagerViewModal({ open, setViewModal, id }: Props) { useEffect(() => { if (id) { + const manager = managers.find((manager) => manager.id === id); setSelectedManager(manager || null); } @@ -107,7 +108,7 @@ export default function ManagerViewModal({ open, setViewModal, id }: Props) { - Registered Address: + Station Location: {selectedManager.registeredAddress} diff --git a/src/components/Modals/ViewModal/index.tsx b/src/components/Modals/ViewModal/index.tsx index 502c11c..68d24db 100644 --- a/src/components/Modals/ViewModal/index.tsx +++ b/src/components/Modals/ViewModal/index.tsx @@ -107,7 +107,8 @@ export default function ViewModal({ open, setViewModal, id }: Props) { Address: - {selectedAdmin.registeredAddress ?? "N/A"} + {selectedAdmin?.Admins?.[0] + ?.registeredAddress ?? "N/A"} diff --git a/src/pages/AddEditRolePage/index.tsx b/src/pages/AddEditRolePage/index.tsx index 7641930..0a8249a 100644 --- a/src/pages/AddEditRolePage/index.tsx +++ b/src/pages/AddEditRolePage/index.tsx @@ -20,9 +20,7 @@ import { useDispatch, useSelector } from "react-redux"; import { createRole } from "../../redux/slices/roleSlice"; // Import the createRole action import { AppDispatch, RootState } from "../../redux/store/store"; // Assuming this is the path to your store file import { toast } from "sonner"; -import { - CustomTextField, -} from "../../components/AddUserModel/styled.css.tsx"; +import { CustomTextField } from "../../components/AddEditUserModel/styled.css.tsx"; // Define the data structure for permission interface Permission { module: string; diff --git a/src/pages/AdminList/index.tsx b/src/pages/AdminList/index.tsx index 30705dc..7af0318 100644 --- a/src/pages/AdminList/index.tsx +++ b/src/pages/AdminList/index.tsx @@ -69,7 +69,6 @@ export default function AdminList() { email, phone, registeredAddress, - }) ); await dispatch(adminList()); @@ -86,40 +85,30 @@ export default function AdminList() { { id: "registeredAddress", label: "Address" }, { id: "action", label: "Action", align: "center" }, ]; - // const filteredAdmins = admins?.filter( - // (admin) => - // admin.name.toLowerCase().includes(searchTerm.toLowerCase()) || - // admin.email.toLowerCase().includes(searchTerm.toLowerCase()) || - // admin.phone.toLowerCase().includes(searchTerm.toLowerCase()) || - // admin.registeredAddress - // .toLowerCase() - // .includes(searchTerm.toLowerCase()) - // ); const categoryRows = admins?.map( - ( - admin: { - id: string; - name: string; - email: string; - phone: string; - registeredAddress: string; - }, - index: number - ) => ({ - id: admin?.id, - srno: index + 1, - name: admin?.name, - email: admin?.email, - phone: admin?.phone, - registeredAddress: admin?.registeredAddress || "NA", - }) - ) - ; + ( + admin: { + id: string; + name: string; + email: string; + phone: string; + Admins: { registeredAddress: string }[]; // Adjusted to support array of Admins + }, + index: number + ) => ({ + id: admin?.id, + srno: index + 1, + name: admin?.name, + email: admin?.email, + phone: admin?.phone, + registeredAddress: admin?.Admins?.[0]?.registeredAddress || "NA", + }) + ); + return ( <> - { setOpen(false); }; - + + const togglePasswordVisibility = (e: React.MouseEvent) => { + e.preventDefault(); // Prevent focus loss + setShowPassword((prev) => !prev); + }; + const onSubmit: SubmitHandler = async (data: ILoginForm) => { - try { - const response = await dispatch(loginUser(data)).unwrap(); - if (response?.data?.token) { - router("/panel/dashboard"); + try { + const response = await dispatch(loginUser(data)).unwrap(); + if (response?.data?.token) { + router("/panel/dashboard"); + } + } catch (error: any) { + console.log("Login failed:", error); } - } catch (error: any) { - console.log("Login failed:", error); - } -}; + }; return ( @@ -80,21 +84,19 @@ export default function Login(props: { disableCustomTheme?: boolean }) { {/* Form Section */} - + item + xs={12} + md={5} + sx={{ + backgroundColor: "black", + display: "flex", + justifyContent: "center", + alignItems: "center", + flexDirection: "column", + padding: { xs: "2rem", md: "3rem", lg: "3rem" }, + height: "auto", + }} + > - - + - Login - - Log in with your email and password - + + Login + + + Log in with your email and password + + {/* -------------------------------- Email Field ----------------- */} + + + Email + + ( + + )} + /> + -{/* -------------------------------- Email Field ----------------- */} - - - Email - - ( - - )} - /> - - - -{/* -------------------------------- Password Field ----------------- */} - - - Password - - ( - setShowPassword((prev) => !prev)} - edge="end" - sx={{ - color: "white", - padding: 0, - margin: 0, - backgroundColor: "transparent", - border: "none", - boxShadow: "none", - "&:hover": { backgroundColor: "transparent" }, - "&:focus": { outline: "none", border: "none" }, - }} - > - {showPassword ? : } - - ), - }} - sx={{ - "& .MuiOutlinedInput-root": { - backgroundColor: "#1E1F1F", - borderRadius: "4px", - "& fieldset": { borderColor: "#4b5255" }, - "&:hover fieldset": { borderColor: "#4b5255" }, - "&.Mui-focused fieldset": { borderColor: "#4b5255" }, - }, - "& input": { - color: "white", - fontSize: { xs: "0.9rem", sm: "1rem" }, - fontFamily: "Gilroy, sans-serif", - }, - "& .MuiInputBase-input::placeholder": { - color: "white", - opacity: 1, - fontFamily: "Gilroy, sans-serif", - }, - }} - /> - )} - /> - - + {/* -------------------------------- Password Field ----------------- */} + + + Password + + ( + + + {showPassword ? ( + + ) : ( + + )} + + + ), + }} + sx={{ + "& .MuiOutlinedInput-root": + { + backgroundColor: + "#1E1F1F", + borderRadius: "4px", + "& fieldset": { + borderColor: + "#4b5255", + }, + "&:hover fieldset": + { + borderColor: + "#4b5255", + }, + "&.Mui-focused fieldset": + { + borderColor: + "#4b5255", + }, + }, + "& input": { + color: "white", + fontSize: { + xs: "0.9rem", + sm: "1rem", + }, + fontFamily: + "Gilroy, sans-serif", + }, + "& .MuiInputBase-input::placeholder": + { + color: "white", + opacity: 1, + fontFamily: + "Gilroy, sans-serif", + }, + }} + /> + )} + /> + - - - } - label="Remember me" -/> - - - Forgot password? - + + } + label="Remember me" + /> + + Forgot password? + (false); @@ -38,15 +29,18 @@ export default function ManagerList() { (state: RootState) => state.managerReducer.managers ); + // Fetch manager list on component mount useEffect(() => { dispatch(managerList()); }, [dispatch]); + // Open Add Manager Modal const handleClickOpen = () => { - setRowData(null); // Reset row data when opening for new admin + setRowData(null); // Reset row data when opening for new manager setAddModalOpen(true); }; + // Close all modals const handleCloseModal = () => { setAddModalOpen(false); setEditModalOpen(false); @@ -54,13 +48,15 @@ export default function ManagerList() { reset(); }; + // Handle adding a new manager const handleAddManager = async (data: { name: string; email: string; phone: string; - registeredAddress: string; + stationId: string; }) => { try { + // Add manager with stationId await dispatch(addManager(data)); // Dispatch action to add manager await dispatch(managerList()); // Fetch the updated list handleCloseModal(); // Close the modal @@ -69,66 +65,105 @@ export default function ManagerList() { } }; + // Handle updating an existing manager const handleUpdate = async ( id: number, name: string, email: string, phone: string, - registeredAddress: string + stationId: string ) => { try { - // Creating the managerData object to match the expected payload structure - const managerData = { - name, - email, - phone, - registeredAddress, - }; - - // Dispatching the updateManager action with the correct payload structure - await dispatch(updateManager({ id, managerData })); - await dispatch(managerList()); // Refresh the manager list after updating - handleCloseModal(); // Close the modal after updating + // Update manager with stationId + await dispatch( + updateManager({ + id, + name, + email, + phone, + stationId, + }) + ); + await dispatch(managerList()); // Refresh the list after update + handleCloseModal(); // Close modal after update } catch (error) { console.error("Update failed", error); } }; - - // Remove 'stationName' from columns + // Columns for the manager table const categoryColumns: Column[] = [ { id: "srno", label: "Sr No" }, { id: "name", label: "Name" }, { id: "email", label: "Email" }, { id: "phone", label: "Phone" }, + { id: "stationName", label: "Station Name" }, { id: "registeredAddress", label: "Station Location" }, - { id: "action", label: "Action", align: "center" }, ]; + // const categoryColumns: Column[] = [ + // { id: "srno", label: "Sr No" }, + // { id: "name", label: "Name" }, + // { id: "email", label: "Email" }, + // { id: "phone", label: "Phone" }, + // { id: "stationName", label: "Station Name" }, // Added station name column + // { id: "stationAddress", label: "Station Location" }, // Added station address column + // { id: "action", label: "Action", align: "center" }, + // ]; - // Update rows to remove 'stationName' - const categoryRows = managers?.map( - ( - manager: { - id: number; - name: string; - email: string; - phone: string; - registeredAddress: string; - }, - index: number - ) => ({ - id: manager?.id, - srno: index + 1, - name: manager?.name, - email: manager?.email, - phone: manager.phone ?? "NA", - registeredAddress: manager?.registeredAddress ?? "NA", - }) + + // Filter managers based on search term + const filteredManagers = managers?.filter( + (manager) => + manager.name?.toLowerCase().includes(searchTerm.toLowerCase()) || + manager.email?.toLowerCase().includes(searchTerm.toLowerCase()) || + manager.phone?.toLowerCase().includes(searchTerm.toLowerCase()) + ); + // Format rows to display manager details + // const categoryRows = filteredManagers?.length + // ? filteredManagers?.map( + // ( + // manager: { + // id: number; + // name: string; + // email: string; + // phone: string; + + + // }, + // index: number + // ) => ({ + // id: manager?.id, + // srno: index + 1, + // name: manager?.name, + // email: manager?.email, + // phone: manager.phone ?? "NA", + // }) + // ) + // : []; + +const categoryRows = filteredManagers?.length + ? filteredManagers.map((manager, index) => { + const station = manager?.ChargingStation; // Correct access to the ChargingStation data + return { + id: manager.id, + srno: index + 1, + name: manager.name, + email: manager.email, + phone: manager.phone ?? "NA", + stationName: station?.name ?? "NA", // Corrected station name + registeredAddress: station?.registeredAddress ?? "NA", // Corrected station address + }; + }) + : []; + + + return ( <> + {/* Custom Table to show manager list */} + {/* Add Manager Modal */} + {/* Edit Manager Modal */} (false); + const [editModalOpen, setEditModalOpen] = useState(false); + const [editRow, setEditRow] = useState(null); + const { reset } = useForm(); + + const [deleteModal, setDeleteModal] = useState(false); + const [viewModal, setViewModal] = useState(false); + const [rowData, setRowData] = useState(null); + const [searchTerm, setSearchTerm] = useState(""); + const dispatch = useDispatch(); + const stations = useSelector( + (state: RootState) => state.stationReducer.stations + ); + + useEffect(() => { + dispatch(stationList()); + }, [dispatch]); + + const handleClickOpen = () => { + setRowData(null); // Reset row data when opening for new admin + setAddModalOpen(true); + }; + + const handleCloseModal = () => { + setAddModalOpen(false); + setEditModalOpen(false); + setRowData(null); + reset(); + }; + + const handleAddStation = async (data: { + name: string; + registeredAddress: string; + totalSlots: string; + }) => { + try { + await dispatch(createStation(data)); // Dispatch action to add Station + await dispatch(stationList()); // Fetch the updated list + handleCloseModal(); // Close the modal + } catch (error) { + console.error("Error adding Station", error); + } + }; + + const handleUpdate = async ( + id: string, + name: string, + registeredAddress: string, + totalSlots: string + ) => { + try { + await dispatch( + updateStation({ + id, + name, + registeredAddress, + totalSlots, + + }) + ); + await dispatch(stationList()); + handleCloseModal(); + } catch (error) { + console.error("Update failed", error); + } + }; + + 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()) + ); + + 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: ( + + ), + statusValue: station.status, + })) + : []; + + 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" }, + { 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: ( + // + // handleStatusToggle(station.id, station.status) + // } + // color="primary" + // inputProps={{ "aria-label": "station-status-toggle" }} + // /> + // ), + // statusValue: station.status, + // })) + // : []; + + return ( + <> + setEditModalOpen(true)} + handleStatusToggle={handleStatusToggle} + tableType="station" + handleClickOpen={handleClickOpen} + /> + + + + ); +} diff --git a/src/pages/UserList/index.tsx b/src/pages/UserList/index.tsx index 519e770..52f1579 100644 --- a/src/pages/UserList/index.tsx +++ b/src/pages/UserList/index.tsx @@ -8,7 +8,7 @@ import { createUser, updateUser, userList } from "../../redux/slices/userSlice"; import { AppDispatch, RootState } from "../../redux/store/store"; import { string } from "prop-types"; import { adminList, updateAdmin } from "../../redux/slices/adminSlice"; -import AddUserModal from "../../components/AddUserModel"; +import AddUserModal from "../../components/AddEditUserModel"; export default function UserList() { const [modalOpen, setModalOpen] = useState(false); diff --git a/src/pages/VehicleList/index.tsx b/src/pages/VehicleList/index.tsx index 6f240f5..317e1d4 100644 --- a/src/pages/VehicleList/index.tsx +++ b/src/pages/VehicleList/index.tsx @@ -1,9 +1,6 @@ import React, { useEffect, useState } from "react"; -import { Box, Button, TextField, Typography } from "@mui/material"; -import AddEditCategoryModal from "../../components/AddEditCategoryModal"; import { useForm } from "react-hook-form"; import CustomTable, { Column } from "../../components/CustomTable"; -import DeleteModal from "../../components/Modals/DeleteModal"; import { RootState } from "../../redux/reducers"; import { useDispatch, useSelector } from "react-redux"; import { AppDispatch } from "../../redux/store/store"; diff --git a/src/redux/reducers.ts b/src/redux/reducers.ts index 4b847ca..b732c03 100644 --- a/src/redux/reducers.ts +++ b/src/redux/reducers.ts @@ -7,6 +7,7 @@ import userReducer from "./slices/userSlice.ts"; import roleReducer from "./slices/roleSlice.ts"; import vehicleReducer from "./slices/VehicleSlice.ts"; import managerReducer from "../redux/slices/managerSlice.ts"; +import stationReducer from "../redux/slices/stationSlice.ts"; const rootReducer = combineReducers({ @@ -17,6 +18,7 @@ const rootReducer = combineReducers({ roleReducer, vehicleReducer, managerReducer, + stationReducer // Add other reducers here... }); diff --git a/src/redux/slices/VehicleSlice.ts b/src/redux/slices/VehicleSlice.ts index e637526..c4de897 100644 --- a/src/redux/slices/VehicleSlice.ts +++ b/src/redux/slices/VehicleSlice.ts @@ -71,7 +71,7 @@ export const updateVehicle = createAsyncThunk( async ({ id, ...vehicleData }: Vehicle, { rejectWithValue }) => { try { const response = await http.patch( - `${id}/update-vehicle`, + `/update-vehicle/${id}`, vehicleData ); toast.success("Vehicle Deatils updated successfully"); @@ -90,7 +90,7 @@ export const deleteVehicle = createAsyncThunk< { rejectValue: string } >("deleteVehicle", async (id, { rejectWithValue }) => { try { - const response = await http.delete(`/${id}/delete-vehicle`); + const response = await http.delete(`/delete-vehicle/${id}`); toast.success(response.data?.message); return id; } catch (error: any) { diff --git a/src/redux/slices/adminSlice.ts b/src/redux/slices/adminSlice.ts index b2c6928..00f306e 100644 --- a/src/redux/slices/adminSlice.ts +++ b/src/redux/slices/adminSlice.ts @@ -55,7 +55,7 @@ export const deleteAdmin = createAsyncThunk< { rejectValue: string } >("deleteAdmin", async (id, { rejectWithValue }) => { try { - const response = await http.delete(`/${id}/delete-admin`); + const response = await http.delete(`/delete-admin/${id}`); toast.success(response.data?.message); return id; } catch (error: any) { @@ -92,7 +92,7 @@ export const updateAdmin = createAsyncThunk( "updateAdmin", async ({ id, ...userData }: User, { rejectWithValue }) => { try { - const response = await http.put(`/${id}/update-admin`, userData); + const response = await http.put(`/update-admin/${id}`, userData); toast.success("Admin updated successfully"); return response?.data; } catch (error: any) { diff --git a/src/redux/slices/managerSlice.ts b/src/redux/slices/managerSlice.ts index c5a6c53..783668d 100644 --- a/src/redux/slices/managerSlice.ts +++ b/src/redux/slices/managerSlice.ts @@ -5,12 +5,12 @@ import { toast } from "sonner"; // Define the Manager interface based on the payload interface Manager { + Manager: any; id: number; name: string; email: string; phone: string; - registeredAddress: string; - roleId: number; + stationId: string; } interface ManagerState { @@ -33,7 +33,10 @@ export const managerList = createAsyncThunk< { rejectValue: string } >("fetchManagers", async (_, { rejectWithValue }) => { try { - const response = await http.get("manager-list"); + const token = localStorage?.getItem("authToken"); + if (!token) throw new Error("No token found"); + + const response = await http.get("/manager-list"); if (!response.data?.data) throw new Error("Invalid API response"); return response.data.data; } catch (error: any) { @@ -47,7 +50,12 @@ export const managerList = createAsyncThunk< // Create Manager (Async Thunk) export const addManager = createAsyncThunk< Manager, - Manager, + { + name: string; + email: string; + phone: string; + stationId: string; + }, { rejectValue: string } >("addManager", async (data, { rejectWithValue }) => { try { @@ -72,7 +80,7 @@ export const updateManager = createAsyncThunk< return rejectWithValue("Manager ID is required."); } try { - const response = await http.put(`/${id}/update-manager`, managerData); + const response = await http.put(`/update-manager/${id}`, managerData); toast.success("Manager updated successfully"); return response?.data; } catch (error: any) { @@ -90,7 +98,7 @@ export const deleteManager = createAsyncThunk< { rejectValue: string } >("deleteManager", async (id, { rejectWithValue }) => { try { - await http.delete(`/${id}/delete-manager`); + await http.delete(`/delete-manager/${id}`); toast.success("Manager deleted successfully!"); return id; } catch (error: any) { @@ -103,7 +111,7 @@ export const deleteManager = createAsyncThunk< // Create Slice const managerSlice = createSlice({ - name: "maanger", + name: "manager", initialState, reducers: {}, extraReducers: (builder) => { @@ -147,13 +155,13 @@ const managerSlice = createSlice({ }) .addCase(updateManager.fulfilled, (state, action) => { state.loading = false; - // const updatedManager = action.payload; - // const index = state.managers.findIndex( - // (manager) => manager.id === updatedManager.id - // ); - // if (index !== -1) { - // state.managers[index] = updatedManager; // Update the manager in the state - // } + const updatedManager = action.payload; + const index = state.managers.findIndex( + (manager) => manager.id === updatedManager.id + ); + if (index !== -1) { + state.managers[index] = updatedManager; // Update the manager in the state + } }) .addCase(updateManager.rejected, (state, action) => { state.loading = false; @@ -166,9 +174,6 @@ const managerSlice = createSlice({ }) .addCase(deleteManager.fulfilled, (state, action) => { state.loading = false; - state.managers = state.managers.filter( - (manager) => manager.id !== action.payload - ); }) .addCase(deleteManager.rejected, (state, action) => { state.loading = false; diff --git a/src/redux/slices/stationSlice.ts b/src/redux/slices/stationSlice.ts new file mode 100644 index 0000000..f5e4203 --- /dev/null +++ b/src/redux/slices/stationSlice.ts @@ -0,0 +1,246 @@ +import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit"; +import axios from "axios"; +import http from "../../lib/https"; +import { toast } from "sonner"; + +// Define TypeScript types +interface Station { + id: string; + name: string; + registeredAddress: string; + totalSlots: string; + status: number; +} + +interface StationState { + stations: Station[]; + loading: boolean; + error: string | null; +} + +// Initial state +const initialState: StationState = { + stations: [], + loading: false, + error: null, +}; + +export const stationList = createAsyncThunk( + "fetchStations", + async (_, { rejectWithValue }) => { + try { + const token = localStorage?.getItem("authToken"); + if (!token) throw new Error("No token found"); + + const response = await http.get("/get-stations"); + + if (!response.data) throw new Error("Invalid API response"); + + // Return the full response to handle in the reducer + return response.data; + } catch (error: any) { + toast.error("Error Fetching Stations: " + error.message); + return rejectWithValue( + error?.response?.data?.message || "An error occurred" + ); + } + } +); + +// Create Station +export const createStation = createAsyncThunk< + any, + { + name: string; + registeredAddress: string; + totalSlots: string; + }, + { rejectValue: string } +>("Station/createStation", async (data, { rejectWithValue }) => { + try { + const response = await http.post("/create-station", data); + toast.success("Station created successfully"); + return response.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" + ); + } +}); + +// Update Station details +export const updateStation = createAsyncThunk( + "updateStation", + async ({ id, ...stationData }: Station, { rejectWithValue }) => { + try { + const response = await http.patch( + `/update-station/${id}`, + stationData + ); + toast.success("Station Deatils updated successfully"); + return response?.data; + } catch (error: any) { + toast.error("Error updating the user: " + error); + return rejectWithValue( + error.response?.data?.message || "An error occurred" + ); + } + } +); +export const deleteStation = createAsyncThunk< + string, + string, + { rejectValue: string } +>("deleteStation", async (id, { rejectWithValue }) => { + try { + await http.delete(`/delete-station/${id}`); + toast.success("Station deleted successfully!"); + return id; + } catch (error: any) { + toast.error("Error deleting the Station" + error); + + return rejectWithValue( + error.response?.data?.message || "An error occurred" + ); + } +}); + +export const toggleStatus = createAsyncThunk< + any, + { id: string; status: number }, + { rejectValue: string } +>("station/toggleStatus", async ({ id, status }, { rejectWithValue }) => { + try { + const response = await http.patch(`/update-station/${id}`, { status }); + + if (response.data.statusCode === 200) { + toast.success( + response.data.message || "Status updated successfully" + ); + // Return both the response data and the requested status for reliable state updates + return { + responseData: response.data, + id, + status, + }; + } else { + throw new Error(response.data.message || "Failed to update status"); + } + } catch (error: any) { + toast.error( + "Error updating status: " + (error.message || "Unknown error") + ); + return rejectWithValue( + error.response?.data?.message || + error.message || + "An error occurred" + ); + } +}); + +const stationSlice = createSlice({ + name: "stations", + initialState, + reducers: {}, + extraReducers: (builder) => { + builder + .addCase(stationList.pending, (state) => { + state.loading = true; + state.error = null; + }) + .addCase( + stationList.fulfilled, + (state, action: PayloadAction) => { + state.loading = false; + // Properly extract stations from the response data structure + state.stations = + action.payload.data?.results || + action.payload.data || + []; + } + ) + .addCase(stationList.rejected, (state, action) => { + state.loading = false; + state.error = action.payload || "Failed to fetch stations"; + }) + .addCase(createStation.pending, (state) => { + state.loading = true; + }) + .addCase( + createStation.fulfilled, + (state, action: PayloadAction) => { + state.loading = false; + // Add the newly created station to the state if it exists in the response + if (action.payload.data) { + state.stations.push(action.payload.data); + } + } + ) + .addCase( + createStation.rejected, + (state, action: PayloadAction) => { + state.loading = false; + state.error = action.payload || "Failed to create station"; + } + ) + .addCase(toggleStatus.pending, (state) => { + state.loading = true; + }) + .addCase( + toggleStatus.fulfilled, + (state, action: PayloadAction) => { + state.loading = false; + + // Get the id and updated status from the action payload + const { id, status } = action.payload; + + // Find and update the station with the new status + const stationIndex = state.stations.findIndex( + (station) => station.id === id + ); + if (stationIndex !== -1) { + state.stations[stationIndex] = { + ...state.stations[stationIndex], + status: status, + }; + } + } + ) + .addCase( + toggleStatus.rejected, + (state, action: PayloadAction) => { + state.loading = false; + state.error = + action.payload || "Failed to toggle station status"; + } + ) + .addCase(updateStation.pending, (state) => { + state.loading = true; + }) + .addCase(updateStation.fulfilled, (state, action) => { + state.loading = false; + state.error = action.payload; + }) + .addCase(updateStation.rejected, (state) => { + state.loading = false; + }) + .addCase(deleteStation.pending, (state) => { + state.loading = true; + }) + .addCase(deleteStation.fulfilled, (state, action) => { + state.loading = false; + state.stations = state.stations.filter( + (station) => String(station.id) !== String(action.payload) + ); + }) + .addCase(deleteStation.rejected, (state) => { + state.loading = false; + }); + }, +}); + +export default stationSlice.reducer; diff --git a/src/redux/slices/userSlice.ts b/src/redux/slices/userSlice.ts index 2347971..92950ac 100644 --- a/src/redux/slices/userSlice.ts +++ b/src/redux/slices/userSlice.ts @@ -56,12 +56,11 @@ export const createUser = createAsyncThunk< name: string; email: string; phone: string; - }, { rejectValue: string } >("/CreateUser", async (data, { rejectWithValue }) => { try { - const response = await http.post("create-user", data); + const response = await http.post("/create-user", data); return response.data; } catch (error: any) { return rejectWithValue( @@ -70,13 +69,11 @@ export const createUser = createAsyncThunk< } }); - - export const updateUser = createAsyncThunk( "updateUser", async ({ id, ...userData }: User, { rejectWithValue }) => { try { - const response = await http.put(`/${id}/update-user`, userData); + const response = await http.put(`/update-user/${id}`, userData); toast.success("User updated successfully"); return response?.data; } catch (error: any) { @@ -87,6 +84,24 @@ export const updateUser = createAsyncThunk( } } ); + +export const deleteUser = createAsyncThunk< + string, + string, + { rejectValue: string } +>("deleteUser", async (id, { rejectWithValue }) => { + try { + const response = await http.delete(`/delete-user/${id}`); + toast.success(response.data?.message); + return id; + } catch (error: any) { + toast.error("Error deleting the user" + error); + + return rejectWithValue( + error.response?.data?.message || "An error occurred" + ); + } +}); const userSlice = createSlice({ name: "fetchUsers", initialState, @@ -133,6 +148,18 @@ const userSlice = createSlice({ }) .addCase(updateUser.rejected, (state) => { state.loading = false; + }) + .addCase(deleteUser.pending, (state) => { + state.loading = true; + }) + .addCase(deleteUser.fulfilled, (state, action) => { + state.loading = false; + state.users = state.users.filter( + (user) => String(user.id) !== String(action.payload) + ); + }) + .addCase(deleteUser.rejected, (state) => { + state.loading = false; }); }, }); diff --git a/src/router.tsx b/src/router.tsx index b1b40a5..5b2cbdb 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -2,30 +2,32 @@ import { Routes as BaseRoutes, Navigate, Route } from "react-router-dom"; import React, { lazy, Suspense } from "react"; import LoadingComponent from "./components/Loading"; import DashboardLayout from "./layouts/DashboardLayout"; -import RoleList from "./pages/RoleList"; -import AddEditRolePage from "./pages/AddEditRolePage"; -import VehicleList from "./pages/VehicleList"; +// import RoleList from "./pages/RoleList"; +// import AddEditRolePage from "./pages/AddEditRolePage"; +// import VehicleList from "./pages/VehicleList"; // Page imports const Login = lazy(() => import("./pages/Auth/Login")); const SignUp = lazy(() => import("./pages/Auth/SignUp")); const Dashboard = lazy(() => import("./pages/Dashboard")); -const Vehicles = lazy(() => import("./pages/VehicleList")); +const VehicleList = lazy(() => import("./pages/VehicleList")); const AdminList = lazy(() => import("./pages/AdminList")); const ProfilePage = lazy(() => import("./pages/ProfilePage")); const NotFoundPage = lazy(() => import("./pages/NotFound")); const UserList = lazy(() => import("./pages/UserList")); -const PermissionsTable = lazy(() => import("./pages/PermissionTable")); +const AddEditRolePage = lazy(() => import("./pages/AddEditRolePage")); +const RoleList = lazy(() => import("./pages/RoleList")); const ManagerList = lazy(() => import("./pages/ManagerList")); +const StationList = lazy(() => import("./pages/StationList")); interface ProtectedRouteProps { - caps: string[]; + // caps: string[]; component: React.ReactNode; } // Protected Route Component -const ProtectedRoute: React.FC = ({ caps, component }) => { +const ProtectedRoute: React.FC = ({ component }) => { if (!localStorage.getItem("authToken")) { return ; } @@ -42,11 +44,6 @@ export default function AppRouter() { {/* Auth Routes */} - } - index - /> } /> } /> @@ -55,87 +52,44 @@ export default function AppRouter() { }> } - /> - } - /> - } - /> - } + element={} />} /> + } - /> - } + element={} />} /> } - /> - } - /> - - } - /> - } + element={} />} /> + } />} + /> } - /> - } + element={} />} /> } - /> - } + element={} />} /> } - /> + } /> } /> + } />} + /> } - /> - } + element={} />} />