diff --git a/src/components/AddEditCategoryModal/index.tsx b/src/components/AddEditCategoryModal/index.tsx index ecf4fa9..521bbba 100644 --- a/src/components/AddEditCategoryModal/index.tsx +++ b/src/components/AddEditCategoryModal/index.tsx @@ -6,10 +6,12 @@ import { DialogActions, DialogContent, DialogTitle, + IconButton, TextField, } from "@mui/material"; import CloseIcon from "@mui/icons-material/Close"; import { useForm, Controller } from "react-hook-form"; +import { Visibility, VisibilityOff } from "@mui/icons-material"; //By Jaanvi : Edit Model :: 11-feb-25 interface AddEditCategoryModalProps { @@ -86,6 +88,8 @@ const AddEditCategoryModal: React.FC = ({ } }, [editRow, setValue, reset]); + const [showPassword, setShowPassword] = React.useState(false); + return ( = ({ /> )} /> - ( - - )} - /> + {!editRow && ( + ( + <> + + + + setShowPassword((prev) => !prev) + } + > + {showPassword ? ( + + ) : ( + + )} + + + + )} + /> + )} void; - handleCreate: (data: FormData) => void; - handleUpdate: ( - id: string, - name: string, - resource: { - moduleName: string; - moduleId: string; - permissions: string[]; - }[] - ) => void; - editRow: any; - data: { - resource: { - moduleName: string; - moduleId: string; - permissions: string[]; - }[]; - }; // Assuming `data` is passed as a prop -} - -interface FormData { - name: string; - resource: { - moduleName: string; - moduleId: string; - permissions: string[]; - }[]; -} - -const AddRoleModal: React.FC = ({ - open, - handleClose, - handleCreate, - handleUpdate, - editRow, - data, -}) => { - const { - control, - handleSubmit, - formState: { errors }, - setValue, - reset, - getValues, // Access getValues from the form methods here - } = useForm({ - defaultValues: { - name: "", - resource: [], // Ensure resource is initialized as an empty array - }, - }); - - - useEffect(() => { - if (editRow) { - setValue("name", editRow.name); - setValue("resource", editRow.resource); - } - }, [editRow, setValue]); - - // Handles permissions checkbox change for a specific resource - const handlePermissionChange = ( - resourceIndex: number, - permission: string, - checked: boolean - ) => { - const updatedResources = [...getValues().resource]; // Use getValues to get the current form values - const resource = updatedResources[resourceIndex]; - - if (checked) { - // Add permission if checked - resource.permissions = [ - ...new Set([...resource.permissions, permission]), - ]; - } else { - // Remove permission if unchecked - resource.permissions = resource.permissions.filter( - (p) => p !== permission - ); - } - - setValue("resource", updatedResources); // Update the resource field in form state - }; - - const onSubmit = (data: FormData) => { - if (editRow) { - handleUpdate(editRow.id, data.name, data.resource); - } else { - handleCreate(data); - } - - handleClose(); - reset(); - }; - - return ( - - - {editRow ? "Edit Role" : "Add Role"} - - - - - - - {/* Role Name Field */} - ( - - )} - /> - - {/* Resource Field */} - ( - - Resource - - - {errors.resource?.message} - - - )} - /> - - {/* Permissions Checkbox Fields for each resource */} - {getValues().resource && - getValues().resource.length > 0 && - getValues().resource.map((resource, resourceIndex) => ( - - - - {resource.moduleName} Permissions - - - handlePermissionChange( - resourceIndex, - "view", - e.target.checked - ) - } - /> - } - label="View" - /> - - handlePermissionChange( - resourceIndex, - "edit", - e.target.checked - ) - } - /> - } - label="Edit" - /> - - handlePermissionChange( - resourceIndex, - "delete", - e.target.checked - ) - } - /> - } - label="Delete" - /> - - { - errors.resource?.[resourceIndex] - ?.permissions?.message - } - - - - ))} - - - - - - - - ); -}; - -export default AddRoleModal; diff --git a/src/components/AppNavbar/index.tsx b/src/components/AppNavbar/index.tsx index 49933ef..5652b4f 100644 --- a/src/components/AppNavbar/index.tsx +++ b/src/components/AppNavbar/index.tsx @@ -73,7 +73,7 @@ export default function AppNavbar() { Dashboard - + {/* */} diff --git a/src/components/CustomTable/index.tsx b/src/components/CustomTable/index.tsx index 037e03d..4927964 100644 --- a/src/components/CustomTable/index.tsx +++ b/src/components/CustomTable/index.tsx @@ -61,6 +61,8 @@ interface CustomTableProps { viewModal: boolean; setViewModal: Function; deleteModal: boolean; + handleStatusToggle: (id: string, currentStatus: number) => void; + tableType?: string; } const CustomTable: React.FC = ({ @@ -72,8 +74,9 @@ const CustomTable: React.FC = ({ setRowData, setViewModal, setModalOpen, + handleStatusToggle, + tableType, }) => { - // console.log("columnsss", columns, rows) const dispatch = useDispatch(); const [anchorEl, setAnchorEl] = React.useState(null); const [selectedRow, setSelectedRow] = React.useState(null); @@ -83,6 +86,7 @@ const CustomTable: React.FC = ({ const handleClick = (event: React.MouseEvent, row: Row) => { setAnchorEl(event.currentTarget); setSelectedRow(row); // Ensure the row data is set + setRowData(row); }; const handleClose = () => { @@ -111,57 +115,96 @@ const CustomTable: React.FC = ({ setViewModal(false); }; + const handleToggleStatus = () => { + if (selectedRow) { + // Toggle the opposite of current status + const newStatus = selectedRow.statusValue === 1 ? 0 : 1; + handleStatusToggle(selectedRow.id, newStatus); + } + handleClose(); + }; + return ( - - - - - {columns.map((column) => ( - - {column.label} - - ))} - - - - {rows.map((row, rowIndex) => ( - + + +
+ + {columns.map((column) => ( - {isImage(row[column.id]) ? ( - Row - ) : column.id !== "action" ? ( - row[column.id] - ) : ( - { - handleClick(e, row); - setRowData(row); // Store the selected row - }} - > - - - )} + {column.label} ))} - - ))} - -
+ + + + {rows.map((row, rowIndex) => ( + + {columns.map((column) => ( + + {isImage(row[column.id]) ? ( + Row + ) : column.id !== "action" ? ( + row[column.id] + ) : ( + { + handleClick(e, row); + setRowData(row); // Store the selected row + }} + > + + + )} + + ))} + + ))} + + +
+ + {/* Menu Actions */} {open && ( = ({ Edit + {tableType === "roleList" && ( + + )} + )} - + ); }; diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx index 3806829..4c3f027 100644 --- a/src/components/Header/index.tsx +++ b/src/components/Header/index.tsx @@ -12,10 +12,10 @@ import ColorModeIconDropdown from "../../shared-theme/ColorModeIconDropdown"; import NotificationsRoundedIcon from "@mui/icons-material/NotificationsRounded"; export default function Header() { - const [showNotifications, setShowNotifications] = React.useState(false); - const toggleNotifications = () => { - setShowNotifications((prev) => !prev); - }; + const [showNotifications, setShowNotifications] = React.useState(false); + const toggleNotifications = () => { + setShowNotifications((prev) => !prev); + }; return ( @@ -35,13 +36,18 @@ export default function Header() { spacing={3} alignItems="center" justifyContent="flex-end" + sx={{ + width: "100%", + justifyContent: { xs: "center", sm: "flex-end" }, + marginTop: { xs: 2, sm: 0 }, + }} > {/* Search Bar */} - + {/* Notification and Profile Section */} - + {/* Custom Bell Icon */} - + - + Momah {/* Dropdown Icon */} - + {/* */} {showNotifications && ( {/* diff --git a/src/components/LineChartCard/index.tsx b/src/components/LineChartCard/index.tsx index a8acfa0..069869c 100644 --- a/src/components/LineChartCard/index.tsx +++ b/src/components/LineChartCard/index.tsx @@ -43,20 +43,20 @@ export default function LineChartCard() { return (
Weekly - +
diff --git a/src/components/MainGrid/index.tsx b/src/components/MainGrid/index.tsx index d117c1d..b522cc8 100644 --- a/src/components/MainGrid/index.tsx +++ b/src/components/MainGrid/index.tsx @@ -47,6 +47,7 @@ export default function MainGrid() { container spacing={2} columns={12} + sx={{ mb: (theme) => theme.spacing(2) }} > {data.map((card, index) => ( diff --git a/src/components/MenuContent/index.tsx b/src/components/MenuContent/index.tsx index 5d58f3c..794a33b 100644 --- a/src/components/MenuContent/index.tsx +++ b/src/components/MenuContent/index.tsx @@ -13,35 +13,10 @@ import { RootState } from "../../redux/store/store"; import DashboardOutlinedIcon from "@mui/icons-material/DashboardOutlined"; import ManageAccountsOutlinedIcon from "@mui/icons-material/ManageAccountsOutlined"; -const baseMenuItems = [ - { - text: "Dashboard", - icon: , - url: "/panel/dashboard", - }, - { - text: "Admins", - icon: , - url: "/panel/admin-list", - }, - { - text: "Users", - icon: , - url: "/panel/user-list", - }, - { - text: "Roles", - icon: , - url: "/panel/role-list", - }, -]; - //Eknoor singh and Jaanvi //date:- 12-Feb-2025 //Made a different variable for super admin to access all the details. - - type PropType = { hidden: boolean; }; @@ -51,24 +26,34 @@ export default function MenuContent({ hidden }: PropType) { const userRole = useSelector( (state: RootState) => state.profileReducer.user?.userType ); + const baseMenuItems = [ + { + text: "Dashboard", + icon: , + url: "/panel/dashboard", + }, + userRole === "superadmin" && { + text: "Admins", + icon: , + url: "/panel/admin-list", + }, + userRole === "admin" && { + text: "Users", + icon: , + url: "/panel/user-list", + }, + userRole === "superadmin" && { + text: "Roles", + icon: , + url: "/panel/role-list", + }, + ]; - - // const mainListItems = [ - // ...baseMenuItems, - // // ...(userRole === "superadmin" - // // ? [ - // // // { - // // // text: "Admin List", - // // // icon: , - // // // url: "/panel/admin-list", - // // // }, - // // ] - // // : []), - // ]; + const filteredMenuItems = baseMenuItems.filter(Boolean); return ( - {baseMenuItems.map((item, index) => ( + {filteredMenuItems.map((item, index) => ( - + Resources @@ -83,7 +93,7 @@ export default function ResourcePieChart() { borderRadius: "50%", }} /> - + {entry.title} diff --git a/src/components/SessionsChart/index.tsx b/src/components/SessionsChart/index.tsx index e47febc..22c7ddb 100644 --- a/src/components/SessionsChart/index.tsx +++ b/src/components/SessionsChart/index.tsx @@ -11,16 +11,16 @@ export default function SessionsChart() { variant="outlined" sx={{ width: "100%", - height: "90%", - backgroundColor: "#202020", - p: 2, + height: "100%", + backgroundColor: "#F2F2F2", + p: 2, }} > Delhi NCR EV Station - +
{/* Grid container for the four boxes */} @@ -86,8 +86,8 @@ export default function SessionsChart() { height: "84px", borderRadius: "8px", p: "12px 16px", - backgroundColor: "#3B3B3B", - color: "#F2F2F2", + backgroundColor: "#FFFFFF", + color: "#202020", }} > {title} @@ -32,7 +32,7 @@ export default function StatCard({ title, value }: StatCardProps) {
+ variant="h6" + align="left" + color="#202020" + sx={{ + fontFamily: "Gilroy", + fontWeight: 500, + fontSize: "18px", + lineHeight: "24px", + }} + > Charge Stats Monthly - +
diff --git a/src/layouts/DashboardLayout/index.tsx b/src/layouts/DashboardLayout/index.tsx index 165d39e..6d2ad96 100644 --- a/src/layouts/DashboardLayout/index.tsx +++ b/src/layouts/DashboardLayout/index.tsx @@ -1,56 +1,72 @@ -// src/common/components/Layout - -import * as React from 'react'; -import { Box, Stack } from '@mui/material'; -import { Outlet } from 'react-router-dom'; -import SideMenu from '../../components/SideMenu'; -import AppNavbar from '../../components/AppNavbar'; -import Header from '../../components/Header'; -import AppTheme from '../../shared-theme/AppTheme'; +import * as React from "react"; +import { Box, Stack } from "@mui/material"; +import { Outlet } from "react-router-dom"; +import SideMenu from "../../components/SideMenu"; +import AppNavbar from "../../components/AppNavbar"; +import Header from "../../components/Header"; +import AppTheme from "../../shared-theme/AppTheme"; interface LayoutProps { - customStyles?: React.CSSProperties; + customStyles?: React.CSSProperties; } const DashboardLayout: React.FC = ({ customStyles }) => { - return ( - - - - - ({ - display: "flex", - height: '100vh', - flexGrow: 1, - backgroundColor: theme.vars - ? `rgba(${theme.vars.palette.background.defaultChannel} / 1)` - : theme.palette.background.default, - overflow: 'auto', - ...customStyles, - })} - > - + + {/* SideMenu - Responsive, shown only on large screens */} + + + - alignItems: 'center', - mx: 3, - pb: 5, - mt: { xs: 8, md: 0 }, - }} - > -
- - - - - - ); + {/* Navbar - Always visible */} + + + ({ + display: "flex", + flexDirection: "column", + height: "100vh", + flexGrow: 1, + backgroundColor: theme.vars + ? `rgba(${theme.vars.palette.background.defaultChannel} / 1)` + : theme.palette.background.default, + overflow: "auto", + ...customStyles, + mt: { xs: 8, md: 0 }, + })} + > + +
+ + + + + + ); }; export default DashboardLayout; diff --git a/src/pages/AddEditRolePage/index.tsx b/src/pages/AddEditRolePage/index.tsx new file mode 100644 index 0000000..3ee8144 --- /dev/null +++ b/src/pages/AddEditRolePage/index.tsx @@ -0,0 +1,281 @@ +import React, { useState } from "react"; +import { + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, + Checkbox, + Typography, + Box, + Grid, + FormControlLabel, + Button, + TextField, + Snackbar, +} from "@mui/material"; +import { useNavigate } from "react-router-dom"; // Import useNavigate +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"; +// Define the data structure for permission +interface Permission { + module: string; + list: boolean; + add: boolean; + edit: boolean; + view: boolean; + delete: boolean; +} + +// Initial sample data +const initialPermissions: Permission[] = [ + { + module: "User Management", + list: false, + add: false, + edit: false, + view: false, + delete: false, + }, + { + module: "Role Management", + list: false, + add: false, + edit: false, + view: false, + delete: false, + }, + // Add other modules as needed +]; + +// Table component +const AddEditRolePage: React.FC = () => { + const [permissions, setPermissions] = + useState(initialPermissions); + const [roleName, setRoleName] = useState(""); + const [openSnackbar, setOpenSnackbar] = useState(false); // For snackbar (success message) + const navigate = useNavigate(); // Initialize useNavigate + const dispatch = useDispatch(); // Type the dispatch function with AppDispatch + + const { loading } = useSelector( + (state: RootState) => state.roleReducer.roles + ); + + // Handle checkbox change + const handleCheckboxChange = (module: string, action: keyof Permission) => { + setPermissions((prevPermissions) => + prevPermissions.map((perm) => + perm.module === module + ? { ...perm, [action]: !perm[action] } + : perm + ) + ); + }; + + // Handle role name input change + const handleRoleNameChange = ( + event: React.ChangeEvent + ) => { + setRoleName(event.target.value); + }; + + // Handle Back Navigation + const handleBack = () => { + navigate("/panel/role-list"); // Navigate back to Role List + }; + + // Handle form submission (adding role) + const handleSubmit = async () => { + if (!roleName.trim()) { + alert("Role name is required"); + return; + } + + const newRole = { + name: roleName, + resource: permissions.map((perm) => ({ + moduleName: perm.module, + permissions: [ + perm.list ? "list" : "", + perm.add ? "add" : "", + perm.edit ? "edit" : "", + perm.view ? "view" : "", + perm.delete ? "delete" : "", + ].filter(Boolean), + moduleId: perm.module, // Assuming the module name can serve as the moduleId or use a unique id + })), + }; + + try { + // Dispatch the createRole action to create the new role + await dispatch(createRole(newRole)); + + // Show success message + toast.success("Role created successfully!"); + setOpenSnackbar(true); + + // Reset the form + setRoleName(""); + setPermissions(initialPermissions); + navigate("/panel/role-list"); + } catch (error) { + console.error("Error creating role:", error); + toast.error("Error creating role. Please try again."); + } + }; + return ( + + {/* Title & Back Button Section */} + + + Role Permissions + + + + + {/* Role Name Input */} + + + + + + {/* Table Container */} + + + {/* Table Head */} + + + + Module Name + + + Actions + + + + + {/* Table Body */} + + {permissions.map((row, index) => ( + + + {row.module} + + + + {[ + "list", + "add", + "edit", + "view", + "delete", + ].map((action) => ( + + + handleCheckboxChange( + row.module, + action as keyof Permission + ) + } + sx={{ + color: "#1976D2", + }} + /> + } + label={ + action + .charAt(0) + .toUpperCase() + + action.slice(1) + } + /> + + ))} + + + + ))} + +
+
+ + {/* Submit Button */} + + + + + {/* Snackbar for success message */} + setOpenSnackbar(false)} + message="Role added successfully!" + /> +
+ ); +}; + +export default AddEditRolePage; diff --git a/src/pages/AdminList/index.tsx b/src/pages/AdminList/index.tsx index ccc620f..f579f75 100644 --- a/src/pages/AdminList/index.tsx +++ b/src/pages/AdminList/index.tsx @@ -1,360 +1,5 @@ -// import { useEffect, useState } from "react"; -// import { -// Box, -// Button, -// Typography, -// TextField, -// InputAdornment, -// Paper, -// Table, -// TableBody, -// TableCell, -// TableContainer, -// TableHead, -// TableRow, -// Pagination, -// IconButton, -// } from "@mui/material"; -// import SearchIcon from "@mui/icons-material/Search"; -// import MoreHorizIcon from "@mui/icons-material/MoreHoriz"; -// import TuneIcon from "@mui/icons-material/Tune"; -// import { useDispatch, useSelector } from "react-redux"; -// import { adminList } from "../../redux/slices/adminSlice"; -// import { AppDispatch, RootState } from "../../redux/store/store"; - -// export default function AdminList() { -// const [searchQuery, setSearchQuery] = useState(""); -// const [currentPage, setCurrentPage] = useState(1); -// const adminsPerPage = 10; - -// const dispatch = useDispatch(); -// const admins = useSelector((state: RootState) => state.adminReducer.admins); - -// useEffect(() => { -// dispatch(adminList()); -// }, [dispatch]); - -// const staticAdmins = [ -// { -// name: "John Doe", -// location: "New York", -// managerAssigned: "Alice Johnson", -// vehicle: "Tesla Model S", -// phone: "+1 234 567 8901", -// }, -// { -// name: "Jane Smith", -// location: "Los Angeles", -// managerAssigned: "Bob Brown", -// vehicle: "Ford F-150", -// phone: "+1 987 654 3210", -// }, -// { -// name: "Michael Brown", -// location: "Chicago", -// managerAssigned: "Sarah Lee", -// vehicle: "Chevrolet Bolt", -// phone: "+1 312 555 7890", -// }, -// { -// name: "Emily Davis", -// location: "Houston", -// managerAssigned: "Tom Wilson", -// vehicle: "Nissan Leaf", -// phone: "+1 713 444 5678", -// }, -// { -// name: "Daniel Martinez", -// location: "Phoenix", -// managerAssigned: "Jessica White", -// vehicle: "BMW i3", -// phone: "+1 602 999 4321", -// }, -// { -// name: "Sophia Miller", -// location: "Philadelphia", -// managerAssigned: "Mark Adams", -// vehicle: "Audi e-tron", -// phone: "+1 215 777 6543", -// }, -// { -// name: "James Anderson", -// location: "San Antonio", -// managerAssigned: "Emma Thomas", -// vehicle: "Hyundai Kona EV", -// phone: "+1 210 321 8765", -// }, -// { -// name: "James Anderson", -// location: "San Antonio", -// managerAssigned: "Emma Thomas", -// vehicle: "Hyundai Kona EV", -// phone: "+1 210 321 8765", -// }, -// ]; - -// const adminData = admins.length ? admins : staticAdmins; - -// const filteredAdmins = adminData.filter((admin) => -// admin.name.toLowerCase().includes(searchQuery.toLowerCase()) -// ); - -// const indexOfLastAdmin = currentPage * adminsPerPage; -// const indexOfFirstAdmin = indexOfLastAdmin - adminsPerPage; -// const currentAdmins = filteredAdmins.slice( -// indexOfFirstAdmin, -// indexOfLastAdmin -// ); - -// const handlePageChange = (event, value) => { -// setCurrentPage(value); -// }; - -// return ( -// -// -// Charge stations -// - -// {/* Search & Buttons Section */} -// -// -// -// -// ), -// }} -// value={searchQuery} -// onChange={(e) => setSearchQuery(e.target.value)} -// /> -// -// -// - -// -// -// -// - -// {/* Table Section */} -// -// -// -// -// {[ -// "Name", -// "Location", -// "Manager Assigned", -// "Vehicle", -// "Phone Number", -// "Action", -// ].map((header) => ( -// -// {header} -// -// ))} -// -// -// -// {currentAdmins.map((admin, index) => ( -// -// -// {admin.name} -// -// -// {admin.location || "N/A"} -// -// -// {admin.managerAssigned || "N/A"} -// -// -// {admin.vehicle || "N/A"}{" "} -// -// +6 more -// -// -// -// {admin.phone} -// -// -// -// -// -// -// -// ))} -// -//
-//
- -// {/* Pagination */} -// -// -// Page Number : -// -// -// -//
-// ); -// } import React, { useEffect, useState } from "react"; -import { Box, Button, Typography } from "@mui/material"; +import { Box, Button, TextField, Typography } from "@mui/material"; import AddEditCategoryModal from "../../components/AddEditCategoryModal"; import { useForm } from "react-hook-form"; import CustomTable, { Column } from "../../components/CustomTable"; @@ -365,6 +10,7 @@ import { createAdmin, } from "../../redux/slices/adminSlice"; import { AppDispatch, RootState } from "../../redux/store/store"; +import SearchIcon from "@mui/icons-material/Search"; export default function AdminList() { const [modalOpen, setModalOpen] = useState(false); @@ -377,7 +23,7 @@ export default function AdminList() { const dispatch = useDispatch(); const admins = useSelector((state: RootState) => state.adminReducer.admins); - + const [searchTerm, setSearchTerm] = useState(""); useEffect(() => { dispatch(adminList()); }, [dispatch]); @@ -439,9 +85,18 @@ 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?.length - ? admins?.map( + const categoryRows = filteredAdmins?.length + ? filteredAdmins?.map( ( admin: { id: string; @@ -464,28 +119,55 @@ export default function AdminList() { return ( <> + + Admins + + - - Admins - + setSearchTerm(e.target.value)} + sx={{ + width: { xs: "100%", sm: "30%" }, + marginBottom: { xs: 2, sm: 0 }, + }} + InputProps={{ + startAdornment: ( + + ), + }} + /> + - - {/* or - - - Don't have an account?{" "} - - Sign up - - - */} diff --git a/src/pages/ProfilePage/index.tsx b/src/pages/ProfilePage/index.tsx index 86488a8..1bebb78 100644 --- a/src/pages/ProfilePage/index.tsx +++ b/src/pages/ProfilePage/index.tsx @@ -56,9 +56,7 @@ const ProfilePage = () => { { Phone: {user?.phone || "N/A"} - Role: {user?.role || "N/A"} + Role: {user?.userType || "N/A"} diff --git a/src/pages/RoleList/index.tsx b/src/pages/RoleList/index.tsx index c85a68c..464afdd 100644 --- a/src/pages/RoleList/index.tsx +++ b/src/pages/RoleList/index.tsx @@ -5,9 +5,15 @@ import PermissionsTable from "../../pages/PermissionTable"; import { useForm } from "react-hook-form"; import CustomTable, { Column } from "../../components/CustomTable"; import { useDispatch, useSelector } from "react-redux"; -import { createRole, roleList } from "../../redux/slices/roleSlice"; +import { + createRole, + roleList, + toggleStatus, +} from "../../redux/slices/roleSlice"; import { AppDispatch, RootState } from "../../redux/store/store"; import { useNavigate } from "react-router-dom"; +import AddEditRolePage from "../AddEditRolePage"; +import SearchIcon from "@mui/icons-material/Search"; export default function RoleList() { const [modalOpen, setModalOpen] = useState(false); @@ -38,6 +44,10 @@ export default function RoleList() { reset(); }; + const handleStatusToggle = (id: string, newStatus: number) => { + dispatch(toggleStatus({ id, status: newStatus })); + }; + const handleCreate = async (data: { name: string; resource: { @@ -62,23 +72,31 @@ export default function RoleList() { { id: "action", label: "Action", align: "center" }, ]; - const categoryRows = roles?.map((role: Role, index: number) => ({ - id: role.id, - srno: index + 1, - name: role.name, - status: ( - - ), - })); - + const filterRoles = roles?.filter((role) => + role.name.toLocaleLowerCase().includes(searchTerm.toLowerCase()) + ); - - console.log("Category Rows:", categoryRows); + const categoryRows = filterRoles?.length + ? filterRoles?.map((role: Role, index: number) => ({ + id: role.id, + srno: index + 1, + name: role.name, + status: ( + + ), + statusValue: role.status, + })) + : []; return ( <> @@ -86,9 +104,10 @@ export default function RoleList() { sx={{ width: "100%", display: "flex", + flexDirection: { xs: "column", sm: "row" }, justifyContent: "space-between", alignItems: "center", - mb: 2, + mb: 2, }} > setSearchTerm(e.target.value)} - sx={{ width: "30%" }} + sx={{ + width: { xs: "100%", sm: "30%" }, + marginBottom: { xs: 2, sm: 0 }, + }} + InputProps={{ + startAdornment: ( + + ), + }} /> - {showPermissions ? ( - + ) : ( )} - - ); } diff --git a/src/pages/UserList/index.tsx b/src/pages/UserList/index.tsx index c6e284e..476bd59 100644 --- a/src/pages/UserList/index.tsx +++ b/src/pages/UserList/index.tsx @@ -761,7 +761,7 @@ export default function UserList() { id: string; name: string; email: string; - + phone: string; // location?: string; // managerAssigned?: string; diff --git a/src/redux/slices/roleSlice.ts b/src/redux/slices/roleSlice.ts index bc1cef5..9c20808 100644 --- a/src/redux/slices/roleSlice.ts +++ b/src/redux/slices/roleSlice.ts @@ -5,13 +5,14 @@ import { toast } from "sonner"; // Define TypeScript types interface Role { - id: any; + id: string; name: string; resource: { moduleName: string; moduleId: string; permissions: string[]; }[]; + status: number; } interface RoleState { @@ -27,7 +28,7 @@ const initialState: RoleState = { error: null, }; -export const roleList = createAsyncThunk( +export const roleList = createAsyncThunk( "fetchRoles", async (_, { rejectWithValue }) => { try { @@ -36,11 +37,12 @@ export const roleList = createAsyncThunk( const response = await http.get("get"); - if (!response.data?.data) throw new Error("Invalid API response"); + if (!response.data) throw new Error("Invalid API response"); - return response.data.data; + // Return the full response to handle in the reducer + return response.data; } catch (error: any) { - toast.error("Error Fetching Roles" + error); + toast.error("Error Fetching Roles: " + error.message); return rejectWithValue( error?.response?.data?.message || "An error occurred" ); @@ -50,7 +52,7 @@ export const roleList = createAsyncThunk( // Create Role export const createRole = createAsyncThunk< - Role, + any, { name: string; resource: { @@ -60,19 +62,57 @@ export const createRole = createAsyncThunk< }[]; }, { rejectValue: string } ->("/CreateRole", async (data, { rejectWithValue }) => { +>("role/createRole", async (data, { rejectWithValue }) => { try { const response = await http.post("create", data); + toast.success("Role created successfully"); return response.data; } catch (error: any) { + toast.error( + "Failed to create role: " + + (error.response?.data?.message || "Unknown error") + ); return rejectWithValue( error.response?.data?.message || "An error occurred" ); } }); +export const toggleStatus = createAsyncThunk< + any, + { id: string; status: number }, + { rejectValue: string } +>("role/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 roleSlice = createSlice({ - name: "fetchRoles", + name: "roles", initialState, reducers: {}, extraReducers: (builder) => { @@ -85,7 +125,11 @@ const roleSlice = createSlice({ roleList.fulfilled, (state, action: PayloadAction) => { state.loading = false; - state.roles = action.payload.results; // Extract results from response + // Properly extract roles from the response data structure + state.roles = + action.payload.data?.results || + action.payload.data || + []; } ) .addCase(roleList.rejected, (state, action) => { @@ -97,9 +141,12 @@ const roleSlice = createSlice({ }) .addCase( createRole.fulfilled, - (state, action: PayloadAction) => { + (state, action: PayloadAction) => { state.loading = false; - state.roles.push(action.payload); + // Add the newly created role to the state if it exists in the response + if (action.payload.data) { + state.roles.push(action.payload.data); + } } ) .addCase( @@ -108,6 +155,37 @@ const roleSlice = createSlice({ state.loading = false; state.error = action.payload || "Failed to create role"; } + ) + .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 role with the new status + const roleIndex = state.roles.findIndex( + (role) => role.id === id + ); + if (roleIndex !== -1) { + state.roles[roleIndex] = { + ...state.roles[roleIndex], + status: status, + }; + } + } + ) + .addCase( + toggleStatus.rejected, + (state, action: PayloadAction) => { + state.loading = false; + state.error = + action.payload || "Failed to toggle role status"; + } ); }, }); diff --git a/src/router.tsx b/src/router.tsx index b5a4276..d2ce56d 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -3,6 +3,7 @@ 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"; // Page imports const Login = lazy(() => import("./pages/Auth/Login")); @@ -94,12 +95,15 @@ export default function AppRouter() { /> } /> - } />} - /> - - + } + /> + } + />