diff --git a/package-lock.json b/package-lock.json index 0bb098c..6e35ddc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15041,6 +15041,7 @@ }, "node_modules/typescript": { "version": "5.7.3", + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/src/App.css b/src/App.css index 74b5e05..a9f59a8 100644 --- a/src/App.css +++ b/src/App.css @@ -1,3 +1,12 @@ +html, body { + margin: 0; + padding: 0; + width: 100%; + height: 100%; + overflow: hidden; /* Prevents scrolling */ +} + + .App { text-align: center; } diff --git a/src/App.tsx b/src/App.tsx index 73bc4f6..591615e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,7 @@ import { BrowserRouter as Router } from "react-router-dom"; import AppRouter from "./router"; + function App() { return ( diff --git a/src/components/AddManagerModal/index.tsx b/src/components/AddManagerModal/index.tsx new file mode 100644 index 0000000..8094776 --- /dev/null +++ b/src/components/AddManagerModal/index.tsx @@ -0,0 +1,321 @@ + +import { Controller, useForm } from "react-hook-form"; +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 { + CustomIconButton, + CustomTextField, +} from "../AddUserModel/styled.css.tsx"; +import React from "react"; + +type Props = { + open: boolean; + handleClose: () => void; +}; + +export default function AddManagerModal({ open, handleClose }: Props) { + const dispatch = useDispatch(); + const { + control, + register, + handleSubmit, + formState: { errors }, + reset, + } = useForm(); + const [showPassword, setShowPassword] = React.useState(false); + + 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) + }; + + try { + // Dispatch the addManager action + await dispatch(addManager(managerData)).unwrap(); + handleClose(); // Close modal after successful addition + reset(); // Reset form fields + } catch (error) { + console.error("Error adding manager:", error); + } + }; + + // Toggle password visibility + const togglePasswordVisibility = () => { + setShowPassword((prev) => !prev); + }; + + return ( + + + {/* Header */} + + + Add Manager + + + + + + + {/* Horizontal Line */} + + + {/* Form */} +
+ {/* First Row - Manager Name */} + + {/* Manager Name */} + + + Manager Name + + + + + + {/* Second Row - Email, Password */} + + {/* Email */} + + + Email + + + + + {/* Password */} + + + + Password + + ( + + + {showPassword ? ( + + ) : ( + + )} + + + ), + }} + error={!!errors.password} + helperText={ + errors.password?.message + } + /> + )} + /> + + + + + {/* Third Row - Phone Number, Registered Address */} + + {/* Phone Number */} + + + Phone Number + + + + + {/* Registered Address */} + + + Registered Address + + + + + + {/* Submit Button */} + + + +
+
+
+ ); +} diff --git a/src/components/AddUserModel/index.tsx b/src/components/AddUserModel/index.tsx index 4d6a2bd..c07240c 100644 --- a/src/components/AddUserModel/index.tsx +++ b/src/components/AddUserModel/index.tsx @@ -369,7 +369,4 @@ const AddUserModal: React.FC = ({ }; export default AddUserModal; -function setValue(arg0: string, name: any) { - throw new Error("Function not implemented."); -} diff --git a/src/components/CustomTable/index.tsx b/src/components/CustomTable/index.tsx index 4723c3e..9e24d2a 100644 --- a/src/components/CustomTable/index.tsx +++ b/src/components/CustomTable/index.tsx @@ -11,6 +11,7 @@ import Paper, { paperClasses } from "@mui/material/Paper"; import { adminList, deleteAdmin } from "../../redux/slices/adminSlice"; import { vehicleList, deleteVehicle } from "../../redux/slices/VehicleSlice"; +import { deleteManager, managerList } from "../../redux/slices/managerSlice"; import { useDispatch } from "react-redux"; import { Box, @@ -33,6 +34,7 @@ import TuneIcon from "@mui/icons-material/Tune"; import { CustomIconButton, } from "../AddUserModel/styled.css.tsx"; +import ManagerViewModal from "../Modals/ViewManagerModal"; // Styled components for customization const StyledTableCell = styled(TableCell)(({ theme }) => ({ [`&.${tableCellClasses.head}`]: { @@ -78,6 +80,7 @@ interface CustomTableProps { handleStatusToggle: (id: string, currentStatus: number) => void; tableType: string; // Adding tableType prop to change header text dynamically handleClickOpen: () => void; + //handleDeleteButton: (id: string | number | undefined) => void; } const CustomTable: React.FC = ({ @@ -106,10 +109,21 @@ const CustomTable: React.FC = ({ const open = Boolean(anchorEl); const handleClick = (event: React.MouseEvent, row: Row) => { + setAnchorEl(event.currentTarget); - setSelectedRow(row); // Ensure the row data is set + setSelectedRow(row); setRowData(row); + + }; + // const handleViewButton = (id: string | undefined) => { + // if (!id) { + // console.error("ID not found for viewing."); + // return; + // } + // setViewModal(true); + // }; + const handleClose = () => { setAnchorEl(null); @@ -175,13 +189,12 @@ const CustomTable: React.FC = ({ }; const filteredRows = rows.filter( - (row) => - (row.name && - row.name.toLowerCase().includes(searchQuery.toLowerCase())) || - false + (row) => + (row.name && + row.name.toLowerCase().includes(searchQuery.toLowerCase())) || + false ); - const indexOfLastRow = currentPage * usersPerPage; const indexOfFirstRow = indexOfLastRow - usersPerPage; const currentRows = filteredRows.slice(indexOfFirstRow, indexOfLastRow); @@ -457,6 +470,7 @@ const filteredRows = rows.filter( variant="text" onClick={(e) => { e.stopPropagation(); + // setSelectedRow(row); setViewModal(true); }} color="primary" @@ -489,6 +503,16 @@ const filteredRows = rows.filter( id={selectedRow?.id} /> )} + {viewModal && tableType === "manager" && ( + + handleViewButton(selectedRow?.id) + } + open={viewModal} + setViewModal={setViewModal} + id={selectedRow?.id} + /> + )} +// +// +// +// ); +// }; +// export default EditManagerModal; +import React, { useEffect, useState } from "react"; +import { + Box, + Button, + Typography, + Modal, + CircularProgress, +} from "@mui/material"; +import CloseIcon from "@mui/icons-material/Close"; +import { useForm, Controller } from "react-hook-form"; +import { useDispatch } from "react-redux"; +import { updateManager } from "../../redux/slices/managerSlice"; // Import the updateManager action +import { + CustomIconButton, + CustomTextField, +} from "../AddUserModel/styled.css.tsx"; // Custom styled components + +interface EditManagerModalProps { + open: boolean; + handleClose: () => void; + editRow: any; // Manager data including id +} + +interface FormData { + name: string; + email: string; + registeredAddress: string; + phone: string; +} + +const EditManagerModal: React.FC = ({ + open, + handleClose, + editRow, +}) => { + const dispatch = useDispatch(); // Use dispatch to send Redux actions + const { + control, + handleSubmit, + formState: { errors }, + setValue, + reset, + } = useForm({ + defaultValues: { + name: "", + email: "", + registeredAddress: "", + phone: "", + }, + }); + + const [loading, setLoading] = useState(false); + + // Set values if editRow is provided + useEffect(() => { + if (editRow) { + setValue("name", editRow.name); + setValue("email", editRow.email); + setValue("registeredAddress", editRow.registeredAddress); + setValue("phone", editRow.phone); + } else { + reset(); + } + }, [editRow, setValue, reset]); + + const onSubmit = async (data: FormData) => { + if (editRow) { + setLoading(true); // Start loading state + + // Dispatch the updateManager action from Redux + try { + await dispatch( + updateManager({ + id: editRow.id, // Manager ID + managerData: { + name: data.name, + email: data.email, + registeredAddress: data.registeredAddress, + phone: data.phone, + }, + }) + ).unwrap(); // Ensure that it throws an error if the update fails + + handleClose(); // Close modal on success + reset(); // Reset form fields after submit + } catch (error) { + console.error(error); + // You can handle the error here or show a toast + } finally { + setLoading(false); // Stop loading state + } + } + }; + + return ( + + + {/* Header */} + + + Edit Manager + + + + + + + {/* Horizontal Line */} + + + {/* Input Fields */} + + {/* Manager Name */} + + + Manager Name + + ( + + )} + /> + + + {/* Registered Address */} + + + Registered Address + + ( + + )} + /> + + + {/* Email */} + + + Email + + ( + + )} + /> + + + {/* Phone Number */} + + + Phone Number + + ( + + )} + /> + + + + {/* Submit Button */} + + + + + + ); +}; + +export default EditManagerModal; diff --git a/src/components/EditVehicleModal/index.tsx b/src/components/EditVehicleModal/index.tsx index 07ec2e0..3d51f09 100644 --- a/src/components/EditVehicleModal/index.tsx +++ b/src/components/EditVehicleModal/index.tsx @@ -21,7 +21,7 @@ interface EditVehicleModalProps { email: string, phone: string, registeredAddress: string, - password: string + imageUrl: string ) => void; editRow: any; } diff --git a/src/components/Loading/index.tsx b/src/components/Loading/index.tsx index 8349a30..1da459a 100644 --- a/src/components/Loading/index.tsx +++ b/src/components/Loading/index.tsx @@ -1,14 +1,24 @@ import React from 'react'; -import { useSelector } from 'react-redux'; - -// import styled from './Loading.style'; +import { CircularProgress, Box, Typography } from '@mui/material'; function LoadingComponent() { - // const { isdarktheme } = useSelector((state) => ({ - // isdarktheme: state?.authReducer?.isDarkTheme, - // })); - - return (

Loading

); + return ( + + + + Loading... + + + ); } -export default LoadingComponent; \ No newline at end of file +export default LoadingComponent; diff --git a/src/components/MenuContent/index.tsx b/src/components/MenuContent/index.tsx index e23f179..25539bb 100644 --- a/src/components/MenuContent/index.tsx +++ b/src/components/MenuContent/index.tsx @@ -13,10 +13,6 @@ import { RootState } from "../../redux/store/store"; import DashboardOutlinedIcon from "@mui/icons-material/DashboardOutlined"; import ManageAccountsOutlinedIcon from "@mui/icons-material/ManageAccountsOutlined"; -//Eknoor singh and Jaanvi -//date:- 12-Feb-2025 -//Made a different variable for super admin to access all the details. - type PropType = { hidden: boolean; }; @@ -26,6 +22,7 @@ export default function MenuContent({ hidden }: PropType) { const userRole = useSelector( (state: RootState) => state.profileReducer.user?.userType ); + const baseMenuItems = [ { text: "Dashboard", @@ -37,24 +34,30 @@ export default function MenuContent({ hidden }: PropType) { icon: , url: "/panel/admin-list", }, - userRole === "admin" && { - text: "Users", - icon: , - url: "/panel/user-list", - }, userRole === "superadmin" && { text: "Roles", icon: , url: "/panel/role-list", }, userRole === "admin" && { - text: "Vehicles", + text: "Users", icon: , - url: "/panel/vehicle-list", + url: "/panel/user-list", + }, + userRole === "admin" && { + text: "Managers", + icon: , + url: "/panel/manager-list", // Placeholder for now + }, + userRole === "admin" && { + text: "Vehicles", + icon: , + url: "/panel/vehicles", // Placeholder for now }, ]; const filteredMenuItems = baseMenuItems.filter(Boolean); + return ( @@ -64,7 +67,6 @@ export default function MenuContent({ hidden }: PropType) { disablePadding sx={{ display: "block", py: 1 }} > - {/* Wrap ListItemButton with Link to enable routing */} ); -} +} \ No newline at end of file diff --git a/src/components/Modals/DeleteModal/index.tsx b/src/components/Modals/DeleteModal/index.tsx index 51fedc9..572f59a 100644 --- a/src/components/Modals/DeleteModal/index.tsx +++ b/src/components/Modals/DeleteModal/index.tsx @@ -53,7 +53,7 @@ export default function DeleteModal({ display: "flex", alignItems: "center", justifyContent: "flex-end", // Aligns the close icon to the right - marginTop: -3.5 + marginTop: -3.5, }} > diff --git a/src/components/Modals/ViewManagerModal/index.tsx b/src/components/Modals/ViewManagerModal/index.tsx new file mode 100644 index 0000000..4b4b850 --- /dev/null +++ b/src/components/Modals/ViewManagerModal/index.tsx @@ -0,0 +1,133 @@ +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 ManagerViewModal({ open, setViewModal, id }: Props) { + const { managers } = useSelector( + (state: RootState) => state.managerReducer + ); + const [selectedManager, setSelectedManager] = useState(null); + + useEffect(() => { + if (id) { + const manager = managers.find((manager) => manager.id === id); + setSelectedManager(manager || null); + } + }, [id, managers]); + + return ( + + + + + + {selectedManager?.name || "Manager"}'s Details + + setViewModal(false)} + sx={{ + cursor: "pointer", + display: "flex", + alignItems: "center", + }} + > + + + + + + + + {selectedManager ? ( + + + + Name: + + {selectedManager.name} + + + + + + Email: + + {selectedManager.email} + + + + + + Phone: + + {selectedManager.phone} + + + + + + Registered Address: + + {selectedManager.registeredAddress} + + + + + + Station Name: + + {selectedManager.stationName} + + + + + ) : ( + + No manager found with this ID + + )} + + + ); +} diff --git a/src/components/Modals/ViewModal/ViewModal.tsx b/src/components/Modals/ViewModal/ViewModal.tsx deleted file mode 100644 index 8a1df15..0000000 --- a/src/components/Modals/ViewModal/ViewModal.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { Box, Button, Modal, Typography } from "@mui/material"; -import { AppDispatch, RootState } from "../../../redux/store/store"; -import { useDispatch, useSelector } from "react-redux"; -import { useEffect, useState } from "react"; - -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: 330, - bgcolor: "background.paper", - borderRadius: 1.5, - boxShadow: 24, - p: 3, -}; - -const btnStyle = { py: 1, px: 5, width: "50%", textTransform: "capitalize", alignItems: "center" }; - -export default function ViewModal({ - open, - setViewModal, - id, // Selected user's ID -}: Props) { - const { admins } = useSelector((state: RootState) => state.adminReducer); - const [selectedAdmin, setSelectedAdmin] = useState(null); - - useEffect(() => { - if (id) { - const admin = admins.find((admin) => admin.id === id); - setSelectedAdmin(admin || null); - } - }, [id, admins]); - - return ( - - - - Details of {selectedAdmin?.name} - - {selectedAdmin ? ( - <> - Name: {selectedAdmin?.name} - Email: {selectedAdmin?.email} - Phone: {selectedAdmin?.phone} - Address: {selectedAdmin?.registeredAddress} - - ) : ( - No admin found with this ID - )} - - - - - - ); -} \ No newline at end of file diff --git a/src/components/Modals/ViewModal/index.tsx b/src/components/Modals/ViewModal/index.tsx index cc428e5..502c11c 100644 --- a/src/components/Modals/ViewModal/index.tsx +++ b/src/components/Modals/ViewModal/index.tsx @@ -89,20 +89,26 @@ export default function ViewModal({ open, setViewModal, id }: Props) { Phone: - {selectedAdmin.phone} + + {selectedAdmin.phone} + Email: - {selectedAdmin.email} + + {selectedAdmin.email} + Address: - {selectedAdmin.registeredAddress ?? "N/A"} + + {selectedAdmin.registeredAddress ?? "N/A"} + diff --git a/src/pages/Auth/Login/index.tsx b/src/pages/Auth/Login/index.tsx index 3da495f..45cadd2 100644 --- a/src/pages/Auth/Login/index.tsx +++ b/src/pages/Auth/Login/index.tsx @@ -11,6 +11,7 @@ import { Grid, IconButton, Link, + InputAdornment } from "@mui/material"; import { useForm, Controller, SubmitHandler } from "react-hook-form"; import { useDispatch } from "react-redux"; @@ -29,7 +30,9 @@ 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, @@ -69,7 +72,7 @@ export default function Login(props: { disableCustomTheme?: boolean }) { md={7} sx={{ background: `url('/mainPageLogo.png') center/cover no-repeat`, - height: { xs: "0%", sm: "0%", md: "100%" }, + // height: { xs: "0%", sm: "50%", md: "100%" }, backgroundSize: "cover", display: { xs: "none", md: "block" }, // Hide the image on xs and sm screens }} @@ -77,19 +80,21 @@ 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", // ✅ Allows the height to adjust dynamically + }} +> + - + + - - Login - - - Log in with your email and password - + Login + + Log in with your email and password + - - - Email - - ( - - )} - /> - - - - Password - - ( - - - - setShowPassword( - (prev) => !prev - ) - } - > - {showPassword ? ( - - ) : ( - - )} - - - )} - /> - +{/* -------------------------------- 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", + }, + }} + /> + )} + /> + + - - } - label="Remember me" - /> - - Forgot password? - + + + } + label="Remember me" +/> + + + Forgot password? + + (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 managers = useSelector( + (state: RootState) => state.managerReducer.managers + ); + + useEffect(() => { + dispatch(managerList()); + }, [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 handleAddManager = async (data: { + name: string; + email: string; + phone: string; + registeredAddress: string; + }) => { + try { + await dispatch(addManager(data)); // Dispatch action to add manager + await dispatch(managerList()); // Fetch the updated list + handleCloseModal(); // Close the modal + } catch (error) { + console.error("Error adding manager", error); + } + }; + + const handleUpdate = async ( + id: number, + name: string, + email: string, + phone: string, + 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 + } 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", + }) + ); + + return ( + <> + setEditModalOpen(true)} + tableType="manager" + handleClickOpen={handleClickOpen} + /> + + + + ); +} diff --git a/src/redux/reducers.ts b/src/redux/reducers.ts index 3092653..4b847ca 100644 --- a/src/redux/reducers.ts +++ b/src/redux/reducers.ts @@ -6,6 +6,7 @@ import profileReducer from "./slices/profileSlice"; 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"; const rootReducer = combineReducers({ @@ -14,7 +15,9 @@ const rootReducer = combineReducers({ profileReducer, userReducer, roleReducer, - vehicleReducer + vehicleReducer, + managerReducer, + // Add other reducers here... }); export type RootState = ReturnType; diff --git a/src/redux/slices/managerSlice.ts b/src/redux/slices/managerSlice.ts new file mode 100644 index 0000000..c5a6c53 --- /dev/null +++ b/src/redux/slices/managerSlice.ts @@ -0,0 +1,180 @@ +import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit"; +import http from "../../lib/https"; +import { toast } from "sonner"; + +// Define the Manager interface based on the payload + +interface Manager { + id: number; + name: string; + email: string; + phone: string; + registeredAddress: string; + roleId: number; +} + +interface ManagerState { + managers: Manager[]; + loading: boolean; + error: string | null; +} + +// Initial state +const initialState: ManagerState = { + managers: [], + loading: false, + error: null, +}; + +// Fetch Manager List (Async Thunk) +export const managerList = createAsyncThunk< + Manager[], + void, + { rejectValue: string } +>("fetchManagers", async (_, { rejectWithValue }) => { + try { + const response = await http.get("manager-list"); + if (!response.data?.data) throw new Error("Invalid API response"); + return response.data.data; + } catch (error: any) { + toast.error("Error Fetching Managers: " + error.message); + return rejectWithValue( + error?.response?.data?.message || "An error occurred" + ); + } +}); + +// Create Manager (Async Thunk) +export const addManager = createAsyncThunk< + Manager, + Manager, + { rejectValue: string } +>("addManager", async (data, { rejectWithValue }) => { + try { + const response = await http.post("create-manager", data); + toast.success("Manager created successfully"); + return response.data?.data; + } catch (error: any) { + toast.error("Error creating manager: " + error.message); + return rejectWithValue( + error.response?.data?.message || "An error occurred" + ); + } +}); + +// Update Manager (Async Thunk) +export const updateManager = createAsyncThunk< + Manager, + { id: number; managerData: Manager }, + { rejectValue: string } +>("updateManager", async ({ id, managerData }, { rejectWithValue }) => { + if (!id) { + return rejectWithValue("Manager ID is required."); + } + try { + const response = await http.put(`/${id}/update-manager`, managerData); + toast.success("Manager updated successfully"); + return response?.data; + } catch (error: any) { + toast.error("Error updating manager: " + error.message); + return rejectWithValue( + error.response?.data?.message || "An error occurred" + ); + } +}); + +// Delete Manager (Async Thunk) +export const deleteManager = createAsyncThunk< + string, + string, + { rejectValue: string } +>("deleteManager", async (id, { rejectWithValue }) => { + try { + await http.delete(`/${id}/delete-manager`); + toast.success("Manager deleted successfully!"); + return id; + } catch (error: any) { + toast.error("Error deleting manager: " + error.message); + return rejectWithValue( + error.response?.data?.message || "An error occurred" + ); + } +}); + +// Create Slice +const managerSlice = createSlice({ + name: "maanger", + initialState, + reducers: {}, + extraReducers: (builder) => { + builder + // Fetch Managers + .addCase(managerList.pending, (state) => { + state.loading = true; + state.error = null; + }) + .addCase( + managerList.fulfilled, + (state, action: PayloadAction) => { + state.loading = false; + state.managers = action.payload; + } + ) + .addCase(managerList.rejected, (state, action) => { + state.loading = false; + state.error = action.payload || "Failed to fetch managers"; + }) + + // Add Manager + .addCase(addManager.pending, (state) => { + state.loading = true; + }) + .addCase( + addManager.fulfilled, + (state, action: PayloadAction) => { + state.loading = false; + state.managers.push(action.payload); + } + ) + .addCase(addManager.rejected, (state, action) => { + state.loading = false; + state.error = action.payload || "Failed to add manager"; + }) + + // Update Manager + .addCase(updateManager.pending, (state) => { + state.loading = true; + }) + .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 + // } + }) + .addCase(updateManager.rejected, (state, action) => { + state.loading = false; + state.error = action.payload || "Failed to update manager"; + }) + + // Delete Manager + .addCase(deleteManager.pending, (state) => { + state.loading = true; + }) + .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; + state.error = action.payload || "Failed to delete manager"; + }); + }, +}); + +export default managerSlice.reducer; diff --git a/src/router.tsx b/src/router.tsx index 9af14d0..b1b40a5 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -16,6 +16,8 @@ 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 ManagerList = lazy(() => import("./pages/ManagerList")); + interface ProtectedRouteProps { caps: string[]; @@ -87,6 +89,18 @@ export default function AppRouter() { /> } /> + + } + /> + } + /> + +