From 1e922f1ac548d174bc2731e01640f0af22a0405d Mon Sep 17 00:00:00 2001 From: jaanvi Date: Wed, 12 Mar 2025 18:03:15 +0530 Subject: [PATCH 1/3] some bug fixes and add viewModal for user --- src/components/AddEditCategoryModal/index.tsx | 16 +- src/components/AddManagerModal/index.tsx | 278 ++++----- src/components/AddUserModel/index.tsx | 12 +- src/components/AddVehicleModal/index.tsx | 22 +- src/components/CustomTable/index.tsx | 81 +-- src/components/EditManagerModal/index.tsx | 432 ++----------- src/components/EditVehicleModal/index.tsx | 27 +- src/components/MenuContent/index.tsx | 2 +- src/components/Modals/LogOutModal/index.tsx | 177 +++--- src/components/Modals/UserViewModal/index.tsx | 117 ++++ src/pages/Auth/Login/index.tsx | 577 ++++++++++-------- src/pages/Dashboard/index.tsx | 3 +- src/pages/ManagerList/index.tsx | 93 ++- src/pages/VehicleList/index.tsx | 3 - src/redux/slices/managerSlice.ts | 23 +- src/redux/slices/userSlice.ts | 33 +- src/router.tsx | 46 +- 17 files changed, 955 insertions(+), 987 deletions(-) create mode 100644 src/components/Modals/UserViewModal/index.tsx diff --git a/src/components/AddEditCategoryModal/index.tsx b/src/components/AddEditCategoryModal/index.tsx index 9802f69..63d7686 100644 --- a/src/components/AddEditCategoryModal/index.tsx +++ b/src/components/AddEditCategoryModal/index.tsx @@ -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/AddManagerModal/index.tsx b/src/components/AddManagerModal/index.tsx index 8094776..d7c0588 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"; +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,24 @@ 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, + roleName: "Manager", // You can replace this with dynamic role if needed }; 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 +55,21 @@ 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 */} + + + Manager Name + + - {/* Second Row - Email, Password */} - + {/* Email and Password */} + {/* Email */} - + Email @@ -158,91 +164,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 @@ -266,13 +254,7 @@ export default function AddManagerModal({ open, handleClose }: Props) { {/* Registered Address */} - + Registered Address diff --git a/src/components/AddUserModel/index.tsx b/src/components/AddUserModel/index.tsx index c07240c..69ec91b 100644 --- a/src/components/AddUserModel/index.tsx +++ b/src/components/AddUserModel/index.tsx @@ -93,7 +93,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 }) => ( { + if (reason === "backdropClick") { + return; + } + handleClose(); // Close modal when clicking cross or cancel + }} aria-labelledby="add-vehicle-modal" > @@ -232,7 +247,6 @@ export default function AddVehicleModal({ }} > -// -// -// -// ); -// }; -// export default EditManagerModal; import React, { useEffect, useState } from "react"; import { Box, @@ -323,7 +9,7 @@ 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, @@ -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({ @@ -394,7 +77,7 @@ const EditManagerModal: React.FC = ({ }, }) ).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 +92,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 */} - - {/* Manager Name */} - + {/* Input Fields (2 inputs per row) */} + + {/* Manager Name and Email in one row */} + Manager Name ( + rules={{ + required: "Manager Name is required", + minLength: { + value: 3, + message: + "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", + }, + }} + render={({ field }) => ( = ({ size="small" error={!!errors.name} helperText={errors.name?.message} + /> )} /> - {/* Registered Address */} - + + + Email + + ( + + )} + /> + + + {/* Registered Address and Phone Number in one row */} + Registered Address @@ -508,42 +223,7 @@ const EditManagerModal: React.FC = ({ /> - {/* Email */} - - - Email - - ( - - )} - /> - - - {/* Phone Number */} - + Phone Number diff --git a/src/components/EditVehicleModal/index.tsx b/src/components/EditVehicleModal/index.tsx index 3d51f09..fe9ef13 100644 --- a/src/components/EditVehicleModal/index.tsx +++ b/src/components/EditVehicleModal/index.tsx @@ -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..acb72fa 100644 --- a/src/components/MenuContent/index.tsx +++ b/src/components/MenuContent/index.tsx @@ -52,7 +52,7 @@ export default function MenuContent({ hidden }: PropType) { userRole === "admin" && { text: "Vehicles", icon: , - url: "/panel/vehicles", // Placeholder for now + url: "/panel/vehicle-list", // Placeholder for now }, ]; 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/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/pages/Auth/Login/index.tsx b/src/pages/Auth/Login/index.tsx index 45cadd2..974bafa 100644 --- a/src/pages/Auth/Login/index.tsx +++ b/src/pages/Auth/Login/index.tsx @@ -9,9 +9,9 @@ import { TextField, Typography, Grid, - IconButton, + Link, - InputAdornment + InputAdornment, } from "@mui/material"; import { useForm, Controller, SubmitHandler } from "react-hook-form"; import { useDispatch } from "react-redux"; @@ -22,7 +22,9 @@ import { toast } from "sonner"; import { useNavigate } from "react-router-dom"; import { Visibility, VisibilityOff } from "@mui/icons-material"; import { Card, SignInContainer } from "./styled.css.tsx"; - +import { + CustomIconButton, +} from "../../../components/AddUserModel/styled.css.tsx"; interface ILoginForm { email: string; password: string; @@ -30,9 +32,8 @@ interface ILoginForm { export default function Login(props: { disableCustomTheme?: boolean }) { const [open, setOpen] = React.useState(false); - const [isClicked, setIsClicked] = React.useState(false); const [showPassword, setShowPassword] = React.useState(false); - + const { control, handleSubmit, @@ -48,17 +49,22 @@ export default function Login(props: { disableCustomTheme?: boolean }) { const handleClose = () => { 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 +86,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); @@ -43,7 +34,7 @@ export default function ManagerList() { }, [dispatch]); const handleClickOpen = () => { - setRowData(null); // Reset row data when opening for new admin + setRowData(null); // Reset row data when opening for new manager setAddModalOpen(true); }; @@ -77,56 +68,64 @@ export default function ManagerList() { registeredAddress: 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 + await dispatch( + updateManager({ + id, + name, + email, + phone, + registeredAddress, + }) + ); + await dispatch(managerList()); // Refresh the list after update + handleCloseModal(); // Close modal after update } catch (error) { console.error("Update failed", error); } }; - // Remove 'stationName' from columns const categoryColumns: Column[] = [ { id: "srno", label: "Sr No" }, { id: "name", label: "Name" }, { id: "email", label: "Email" }, { id: "phone", label: "Phone" }, { id: "registeredAddress", label: "Station Location" }, - { 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", - }) + const filteredManagers = managers?.filter( + (manager) => + manager.name?.toLowerCase().includes(searchTerm.toLowerCase()) || + manager.email?.toLowerCase().includes(searchTerm.toLowerCase()) || + manager.phone?.toLowerCase().includes(searchTerm.toLowerCase()) || + manager.registeredAddress + ?.toLowerCase() + .includes(searchTerm.toLowerCase()) ); + const categoryRows = filteredManagers?.length + ? filteredManagers?.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", + }) + ) + : []; + return ( <> ("fetchManagers", async (_, { rejectWithValue }) => { try { + 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; @@ -46,7 +49,6 @@ export const managerList = createAsyncThunk< // Create Manager (Async Thunk) export const addManager = createAsyncThunk< - Manager, Manager, { rejectValue: string } >("addManager", async (data, { rejectWithValue }) => { @@ -103,7 +105,7 @@ export const deleteManager = createAsyncThunk< // Create Slice const managerSlice = createSlice({ - name: "maanger", + name: "manager", initialState, reducers: {}, extraReducers: (builder) => { @@ -147,13 +149,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 +168,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/userSlice.ts b/src/redux/slices/userSlice.ts index 2347971..20ab9b4 100644 --- a/src/redux/slices/userSlice.ts +++ b/src/redux/slices/userSlice.ts @@ -56,7 +56,6 @@ export const createUser = createAsyncThunk< name: string; email: string; phone: string; - }, { rejectValue: string } >("/CreateUser", async (data, { rejectWithValue }) => { @@ -70,8 +69,6 @@ export const createUser = createAsyncThunk< } }); - - export const updateUser = createAsyncThunk( "updateUser", async ({ id, ...userData }: User, { rejectWithValue }) => { @@ -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(`/${id}/delete-user`); + 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..fd15284 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -2,30 +2,31 @@ 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")); 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 +43,6 @@ export default function AppRouter() { {/* Auth Routes */} - } - index - /> } /> } /> @@ -57,25 +53,17 @@ export default function AppRouter() { path="dashboard" element={ } /> } /> - } - /> - } - /> + } /> } @@ -84,7 +72,7 @@ export default function AppRouter() { path="user-list" element={ } /> } @@ -94,7 +82,7 @@ export default function AppRouter() { path="manager-list" element={ } /> } @@ -105,7 +93,7 @@ export default function AppRouter() { path="role-list" element={ } /> } @@ -114,7 +102,7 @@ export default function AppRouter() { path="vehicle-list" element={ } /> } @@ -123,7 +111,7 @@ export default function AppRouter() { path="permissions" element={ } /> } @@ -132,7 +120,7 @@ export default function AppRouter() { path="profile" element={ } /> } From 690af22199eeab36ad0b5588b1c8ee899ca607be Mon Sep 17 00:00:00 2001 From: jaanvi Date: Thu, 13 Mar 2025 16:25:46 +0530 Subject: [PATCH 2/3] Create Charging Station Ui and Updation in Managers --- src/components/AddManagerModal/index.tsx | 91 +++--- src/components/AddStationModal/index.tsx | 205 +++++++++++++ src/components/CustomTable/index.tsx | 38 +++ src/components/EditManagerModal/index.tsx | 73 ++--- src/components/EditStationModal/index.tsx | 270 ++++++++++++++++++ src/components/MenuContent/index.tsx | 9 +- .../Modals/StationViewModal/index.tsx | 127 ++++++++ src/pages/ManagerList/index.tsx | 22 +- src/pages/StationList/index.tsx | 192 +++++++++++++ src/redux/reducers.ts | 2 + src/redux/slices/VehicleSlice.ts | 4 +- src/redux/slices/adminSlice.ts | 4 +- src/redux/slices/managerSlice.ts | 6 +- src/redux/slices/stationSlice.ts | 246 ++++++++++++++++ src/redux/slices/userSlice.ts | 6 +- src/router.tsx | 66 ++--- 16 files changed, 1211 insertions(+), 150 deletions(-) create mode 100644 src/components/AddStationModal/index.tsx create mode 100644 src/components/EditStationModal/index.tsx create mode 100644 src/components/Modals/StationViewModal/index.tsx create mode 100644 src/pages/StationList/index.tsx create mode 100644 src/redux/slices/stationSlice.ts diff --git a/src/components/AddManagerModal/index.tsx b/src/components/AddManagerModal/index.tsx index d7c0588..db71397 100644 --- a/src/components/AddManagerModal/index.tsx +++ b/src/components/AddManagerModal/index.tsx @@ -31,7 +31,6 @@ export default function AddManagerModal({ reset, } = useForm(); const [showPassword, setShowPassword] = useState(false); - // Handle form submission const onSubmit = async (data: any) => { @@ -41,6 +40,7 @@ export default function AddManagerModal({ phone: data.phone, registeredAddress: data.registeredAddress, password: data.password, + stationId: data.stationId, // Send stationId here roleName: "Manager", // You can replace this with dynamic role if needed }; @@ -55,11 +55,10 @@ export default function AddManagerModal({ } }; - - const togglePasswordVisibility = (e: React.MouseEvent) => { + const togglePasswordVisibility = (e: React.MouseEvent) => { e.preventDefault(); // Prevent focus loss setShowPassword((prev) => !prev); - }; + }; return ( {/* Manager Name */} - - - Manager Name - - + + + + Manager Name + + + + + + Station Id + + + - {/* Email and Password */} {/* Email */} @@ -256,7 +277,7 @@ export default function AddManagerModal({ {/* Registered Address */} - Registered Address + Station Location { + 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/CustomTable/index.tsx b/src/components/CustomTable/index.tsx index b00b1fe..1481d1f 100644 --- a/src/components/CustomTable/index.tsx +++ b/src/components/CustomTable/index.tsx @@ -34,6 +34,8 @@ import { CustomIconButton } from "../AddUserModel/styled.css.tsx"; import ManagerViewModal from "../Modals/ViewManagerModal"; import UserViewModal from "../Modals/UserViewModal/index.tsx"; import { deleteUser, userList } from "../../redux/slices/userSlice.ts"; +import { deleteStation } from "../../redux/slices/stationSlice.ts"; +import StationViewModal from "../Modals/StationViewModal/index.tsx"; // Styled components for customization const StyledTableCell = styled(TableCell)(({ theme }) => ({ [`&.${tableCellClasses.head}`]: { @@ -144,6 +146,9 @@ const CustomTable: React.FC = ({ case "user": dispatch(deleteUser(id || "")); break; + case "station": + dispatch(deleteStation(id || "")); + break; default: console.error("Unknown table type:", tableType); return; @@ -231,6 +236,8 @@ const CustomTable: React.FC = ({ ? "Managers" : tableType === "vehicle" ? "Vehicles" + : tableType === "station" + ? "Charging Station" : "List"}
@@ -302,6 +309,8 @@ const CustomTable: React.FC = ({ ? "Manager" : tableType === "vehicle" ? "Vehicle" + : tableType === "station" + ? "Charging Station" : "Item"}
@@ -519,6 +528,16 @@ const CustomTable: React.FC = ({ id={selectedRow?.id} /> )} + {viewModal && tableType === "station" && ( + + handleViewButton(selectedRow?.id) + } + open={viewModal} + setViewModal={setViewModal} + id={selectedRow?.id} + /> + )} )} + {tableType === "station" && ( + + )} +
+
+
+ ); +}; + +export default EditStationModal; diff --git a/src/components/MenuContent/index.tsx b/src/components/MenuContent/index.tsx index acb72fa..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/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/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/pages/ManagerList/index.tsx b/src/pages/ManagerList/index.tsx index aeedbbc..e7db51b 100644 --- a/src/pages/ManagerList/index.tsx +++ b/src/pages/ManagerList/index.tsx @@ -29,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 manager setAddModalOpen(true); }; + // Close all modals const handleCloseModal = () => { setAddModalOpen(false); setEditModalOpen(false); @@ -45,13 +48,16 @@ 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 @@ -60,14 +66,17 @@ export default function ManagerList() { } }; + // Handle updating an existing manager const handleUpdate = async ( id: number, name: string, email: string, phone: string, - registeredAddress: string + registeredAddress: string, + stationId: string ) => { try { + // Update manager with stationId await dispatch( updateManager({ id, @@ -75,6 +84,7 @@ export default function ManagerList() { email, phone, registeredAddress, + stationId, }) ); await dispatch(managerList()); // Refresh the list after update @@ -84,16 +94,18 @@ export default function ManagerList() { } }; - + // 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: "registeredAddress", label: "Station Location" }, + { id: "stationName", label: "Station Name" }, { id: "action", label: "Action", align: "center" }, ]; + // Filter managers based on search term const filteredManagers = managers?.filter( (manager) => manager.name?.toLowerCase().includes(searchTerm.toLowerCase()) || @@ -104,6 +116,7 @@ export default function ManagerList() { .includes(searchTerm.toLowerCase()) ); + // Format rows to display manager details const categoryRows = filteredManagers?.length ? filteredManagers?.map( ( @@ -113,6 +126,7 @@ export default function ManagerList() { email: string; phone: string; registeredAddress: string; + stationName: string; }, index: number ) => ({ @@ -122,12 +136,14 @@ export default function ManagerList() { email: manager?.email, phone: manager.phone ?? "NA", registeredAddress: manager?.registeredAddress ?? "NA", + stationName: manager?.stationName ?? "NA", }) ) : []; 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); + } + }; + + // 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: "", + // registeredAddress: "", + // 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, + status: ( + + ), + statusValue: station.status, + })) + : []; + + const categoryColumns: Column[] = [ + { id: "srno", label: "Sr No" }, + { 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)} + tableType="station" + handleClickOpen={handleClickOpen} + /> + + + + ); +} 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 712f569..fa09e76 100644 --- a/src/redux/slices/managerSlice.ts +++ b/src/redux/slices/managerSlice.ts @@ -10,7 +10,7 @@ interface Manager { email: string; phone: string; registeredAddress: string; - roleId: number; + stationId: string; } interface ManagerState { @@ -74,7 +74,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) { @@ -92,7 +92,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) { diff --git a/src/redux/slices/stationSlice.ts b/src/redux/slices/stationSlice.ts new file mode 100644 index 0000000..2f6d91c --- /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-station"); + + 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 { + const response = await http.delete(`/delete-station/${id}`); + toast.success(response.data?.message); + 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(`${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 20ab9b4..92950ac 100644 --- a/src/redux/slices/userSlice.ts +++ b/src/redux/slices/userSlice.ts @@ -60,7 +60,7 @@ export const createUser = createAsyncThunk< { 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( @@ -73,7 +73,7 @@ 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) { @@ -91,7 +91,7 @@ export const deleteUser = createAsyncThunk< { rejectValue: string } >("deleteUser", async (id, { rejectWithValue }) => { try { - const response = await http.delete(`/${id}/delete-user`); + const response = await http.delete(`/delete-user/${id}`); toast.success(response.data?.message); return id; } catch (error: any) { diff --git a/src/router.tsx b/src/router.tsx index fd15284..5b2cbdb 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -18,6 +18,7 @@ const UserList = lazy(() => import("./pages/UserList")); 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 { @@ -51,79 +52,44 @@ export default function AppRouter() { }> } - /> - } + element={} />} /> } - /> - } + element={} />} /> } - /> - } - /> - - } - /> - } + element={} />} /> + } />} + /> } - /> - } + element={} />} /> } - /> - } + element={} />} /> } - /> + } /> } /> + } />} + /> } - /> - } + element={} />} /> From ef2e6a3b5165ae8fa08b40fbe300c09703e1e0c3 Mon Sep 17 00:00:00 2001 From: jaanvi Date: Mon, 17 Mar 2025 18:28:52 +0530 Subject: [PATCH 3/3] API integeration for chargingStation Managers and changes in Admin --- src/components/AddEditCategoryModal/index.tsx | 2 +- .../index.tsx | 32 +++---- .../styled.css.tsx | 0 src/components/AddManagerModal/index.tsx | 25 +----- src/components/AddStationModal/index.tsx | 4 +- src/components/AddVehicleModal/index.tsx | 5 +- src/components/CustomTable/index.tsx | 2 +- src/components/EditManagerModal/index.tsx | 30 ++++++- src/components/EditStationModal/index.tsx | 12 +-- src/components/EditVehicleModal/index.tsx | 2 +- .../Modals/ViewManagerModal/index.tsx | 3 +- src/components/Modals/ViewModal/index.tsx | 3 +- src/pages/AddEditRolePage/index.tsx | 4 +- src/pages/AdminList/index.tsx | 49 +++++------ src/pages/Auth/Login/index.tsx | 4 +- src/pages/ManagerList/index.tsx | 84 ++++++++++++------- src/pages/StationList/index.tsx | 32 ++++--- src/pages/UserList/index.tsx | 2 +- src/redux/slices/managerSlice.ts | 12 ++- src/redux/slices/stationSlice.ts | 10 +-- 20 files changed, 173 insertions(+), 144 deletions(-) rename src/components/{AddUserModel => AddEditUserModel}/index.tsx (95%) rename src/components/{AddUserModel => AddEditUserModel}/styled.css.tsx (100%) diff --git a/src/components/AddEditCategoryModal/index.tsx b/src/components/AddEditCategoryModal/index.tsx index 63d7686..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 { diff --git a/src/components/AddUserModel/index.tsx b/src/components/AddEditUserModel/index.tsx similarity index 95% rename from src/components/AddUserModel/index.tsx rename to src/components/AddEditUserModel/index.tsx index 69ec91b..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 { @@ -234,7 +232,7 @@ const AddUserModal: React.FC = ({ {/* 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 db71397..5309be9 100644 --- a/src/components/AddManagerModal/index.tsx +++ b/src/components/AddManagerModal/index.tsx @@ -14,7 +14,7 @@ import { addManager, managerList } from "../../redux/slices/managerSlice"; import { CustomIconButton, CustomTextField, -} from "../AddUserModel/styled.css.tsx"; +} from "../AddEditUserModel/styled.css.tsx"; import React, { useState, useRef } from "react"; export default function AddManagerModal({ @@ -38,10 +38,9 @@ export default function AddManagerModal({ name: data.name, email: data.email, phone: data.phone, - registeredAddress: data.registeredAddress, password: data.password, stationId: data.stationId, // Send stationId here - roleName: "Manager", // You can replace this with dynamic role if needed + }; try { @@ -274,26 +273,6 @@ export default function AddManagerModal({ /> - {/* Registered Address */} - - - Station Location - - -
{/* Submit Button */} diff --git a/src/components/AddStationModal/index.tsx b/src/components/AddStationModal/index.tsx index 299d90c..16cb174 100644 --- a/src/components/AddStationModal/index.tsx +++ b/src/components/AddStationModal/index.tsx @@ -4,7 +4,7 @@ import CloseIcon from "@mui/icons-material/Close"; import { CustomIconButton, CustomTextField, -} from "../AddUserModel/styled.css.tsx"; // Assuming custom styled components +} from "../AddEditUserModel/styled.css.tsx"; // Assuming custom styled components export default function AddStationModal({ open, @@ -125,7 +125,7 @@ export default function AddStationModal({ = ({ @@ -44,6 +45,7 @@ const EditManagerModal: React.FC = ({ name: "", email: "", phone: "", + stationId: "", }, }); @@ -55,6 +57,7 @@ const EditManagerModal: React.FC = ({ setValue("name", editRow.name); setValue("email", editRow.email); setValue("phone", editRow.phone); + setValue("stationId", editRow.stationId); } else { reset(); } @@ -70,6 +73,8 @@ const EditManagerModal: React.FC = ({ name: data.name, email: data.email, phone: data.phone, + stationId: data.stationId, + }, }) ).unwrap(); // Ensure that it throws an error if the update fails @@ -212,6 +217,26 @@ const EditManagerModal: React.FC = ({ )} />
+ {/* + + StationId + + ( + + )} + /> + */}
{/* Submit Button */} @@ -240,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 index 775309d..ada74a7 100644 --- a/src/components/EditStationModal/index.tsx +++ b/src/components/EditStationModal/index.tsx @@ -5,7 +5,7 @@ import { useForm, Controller } from "react-hook-form"; import { CustomIconButton, CustomTextField, -} from "../AddUserModel/styled.css.tsx"; +} from "../AddEditUserModel/styled.css.tsx"; interface EditStationModalProps { open: boolean; @@ -15,7 +15,7 @@ interface EditStationModalProps { name: string, registeredAddress: string, totalSlots: number, - imageUrl: string + status: number ) => void; editRow: any; } @@ -24,7 +24,7 @@ interface FormData { name: string; registeredAddress: string; totalSlots: number; - imageUrl: string; + status: number; } const EditStationModal: React.FC = ({ @@ -44,7 +44,7 @@ const EditStationModal: React.FC = ({ name: "", registeredAddress: "", totalSlots: 0, - imageUrl: "", + status: 1, }, }); @@ -54,7 +54,7 @@ const EditStationModal: React.FC = ({ setValue("name", editRow.name); setValue("registeredAddress", editRow.registeredAddress); setValue("totalSlots", editRow.totalSlots); - setValue("imageUrl", editRow.imageUrl); + setValue("status", editRow.number); } else { reset(); } @@ -67,7 +67,7 @@ const EditStationModal: React.FC = ({ data.name, data.registeredAddress, data.totalSlots, - data.imageUrl + data.status ); } handleClose(); // Close the modal diff --git a/src/components/EditVehicleModal/index.tsx b/src/components/EditVehicleModal/index.tsx index fe9ef13..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; 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 ( <> - (false); @@ -53,7 +53,6 @@ export default function ManagerList() { name: string; email: string; phone: string; - registeredAddress: string; stationId: string; }) => { try { @@ -72,7 +71,6 @@ export default function ManagerList() { name: string, email: string, phone: string, - registeredAddress: string, stationId: string ) => { try { @@ -83,7 +81,6 @@ export default function ManagerList() { name, email, phone, - registeredAddress, stationId, }) ); @@ -100,46 +97,69 @@ export default function ManagerList() { { id: "name", label: "Name" }, { id: "email", label: "Email" }, { id: "phone", label: "Phone" }, - { id: "registeredAddress", label: "Station Location" }, { 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" }, + // ]; + // 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()) || - manager.registeredAddress - ?.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; - registeredAddress: string; - stationName: string; - }, - index: number - ) => ({ - id: manager?.id, - srno: index + 1, - name: manager?.name, - email: manager?.email, - phone: manager.phone ?? "NA", - registeredAddress: manager?.registeredAddress ?? "NA", - stationName: manager?.stationName ?? "NA", - }) - ) - : []; + // 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 ( <> diff --git a/src/pages/StationList/index.tsx b/src/pages/StationList/index.tsx index d6dd138..82395ed 100644 --- a/src/pages/StationList/index.tsx +++ b/src/pages/StationList/index.tsx @@ -4,16 +4,12 @@ import CustomTable, { Column } from "../../components/CustomTable"; import { RootState } from "../../redux/reducers"; import { useDispatch, useSelector } from "react-redux"; import { AppDispatch } from "../../redux/store/store"; -import { - addVehicle, - updateVehicle, - vehicleList, -} from "../../redux/slices/VehicleSlice"; import AddStationModal from "../../components/AddStationModal"; import EditStationModal from "../../components/EditStationModal"; import { createStation, stationList, + toggleStatus, updateStation, } from "../../redux/slices/stationSlice"; import { Chip, Switch } from "@mui/material"; @@ -76,6 +72,7 @@ export default function StationList() { name, registeredAddress, totalSlots, + }) ); await dispatch(stationList()); @@ -85,16 +82,23 @@ export default function StationList() { } }; + 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: "", - // registeredAddress: "", - // totalSlots: "" - // })); + // 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); @@ -109,6 +113,8 @@ export default function StationList() { id: station.id, srno: index + 1, name: station.name, + registeredAddress:station.registeredAddress, + totalSlots:station.totalSlots, status: ( 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/redux/slices/managerSlice.ts b/src/redux/slices/managerSlice.ts index fa09e76..783668d 100644 --- a/src/redux/slices/managerSlice.ts +++ b/src/redux/slices/managerSlice.ts @@ -5,11 +5,11 @@ 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; stationId: string; } @@ -35,8 +35,8 @@ export const managerList = createAsyncThunk< try { const token = localStorage?.getItem("authToken"); if (!token) throw new Error("No token found"); - - const response = await http.get("manager-list"); + + const response = await http.get("/manager-list"); if (!response.data?.data) throw new Error("Invalid API response"); return response.data.data; } catch (error: any) { @@ -50,6 +50,12 @@ export const managerList = createAsyncThunk< // Create Manager (Async Thunk) export const addManager = createAsyncThunk< Manager, + { + name: string; + email: string; + phone: string; + stationId: string; + }, { rejectValue: string } >("addManager", async (data, { rejectWithValue }) => { try { diff --git a/src/redux/slices/stationSlice.ts b/src/redux/slices/stationSlice.ts index 2f6d91c..f5e4203 100644 --- a/src/redux/slices/stationSlice.ts +++ b/src/redux/slices/stationSlice.ts @@ -32,7 +32,7 @@ export const stationList = createAsyncThunk( const token = localStorage?.getItem("authToken"); if (!token) throw new Error("No token found"); - const response = await http.get("/get-station"); + const response = await http.get("/get-stations"); if (!response.data) throw new Error("Invalid API response"); @@ -97,8 +97,8 @@ export const deleteStation = createAsyncThunk< { rejectValue: string } >("deleteStation", async (id, { rejectWithValue }) => { try { - const response = await http.delete(`/delete-station/${id}`); - toast.success(response.data?.message); + await http.delete(`/delete-station/${id}`); + toast.success("Station deleted successfully!"); return id; } catch (error: any) { toast.error("Error deleting the Station" + error); @@ -113,9 +113,9 @@ export const toggleStatus = createAsyncThunk< any, { id: string; status: number }, { rejectValue: string } ->("Station/toggleStatus", async ({ id, status }, { rejectWithValue }) => { +>("station/toggleStatus", async ({ id, status }, { rejectWithValue }) => { try { - const response = await http.patch(`${id}`, { status }); + const response = await http.patch(`/update-station/${id}`, { status }); if (response.data.statusCode === 200) { toast.success(