From 5cfee36f0bfa9714e36db59552627e09445d635b Mon Sep 17 00:00:00 2001 From: Harsh Gogia Date: Wed, 5 Mar 2025 18:06:51 +0530 Subject: [PATCH 1/5] ui-station-manager --- src/components/MenuContent/index.tsx | 24 +-- src/components/Modals/ViewModal/ViewModal.tsx | 82 -------- src/pages/AddManagerModal/index.tsx | 126 +++++++++++++ src/pages/EditUserModal/index.tsx | 175 ++++++++++++++++++ src/pages/ManagerList/index.tsx | 131 +++++++++++++ src/redux/slices/managerSlice.ts | 103 +++++++++++ src/router.tsx | 14 ++ src/shared-theme/AppTheme.tsx | 1 + 8 files changed, 563 insertions(+), 93 deletions(-) delete mode 100644 src/components/Modals/ViewModal/ViewModal.tsx create mode 100644 src/pages/AddManagerModal/index.tsx create mode 100644 src/pages/EditUserModal/index.tsx create mode 100644 src/pages/ManagerList/index.tsx create mode 100644 src/redux/slices/managerSlice.ts diff --git a/src/components/MenuContent/index.tsx b/src/components/MenuContent/index.tsx index 794a33b..31207fd 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,19 +34,25 @@ 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: "Users", + icon: , + url: "/panel/user-list", + }, + userRole === "admin" && { + text: "Managers", + icon: , + url: "/panel/manager-list", // Placeholder for now + }, ]; const filteredMenuItems = baseMenuItems.filter(Boolean); + return ( @@ -59,7 +62,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/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/pages/AddManagerModal/index.tsx b/src/pages/AddManagerModal/index.tsx new file mode 100644 index 0000000..b0d32f2 --- /dev/null +++ b/src/pages/AddManagerModal/index.tsx @@ -0,0 +1,126 @@ +import { useState } from "react"; +import { + Box, + Button, + Typography, + TextField, + Modal, + IconButton, +} from "@mui/material"; +import CloseIcon from "@mui/icons-material/Close"; + +export default function AddManagerModal({ open, handleClose, handleAddManager }) { + // State for input fields + const [managerName, setManagerName] = useState(""); + const [stationName, setStationName] = useState(""); + const [stationLocation, setStationLocation] = useState(""); + const [phoneNumber, setPhoneNumber] = useState(""); + + const handleSubmit = () => { + if (!managerName || !stationName || !stationLocation || !phoneNumber) { + alert("Please fill all fields"); + return; + } + + const newManager = { + managerName, + stationName, + stationLocation, + phoneNumber, + }; + + handleAddManager(newManager); // Add manager to table + handleClose(); // Close modal after adding + }; + + return ( + + + {/* Header */} + + + Add Managers + + + + + + + {/* Horizontal Line */} + + + {/* Input Fields */} + + {/* First Row - Two Inputs */} + + + Manager Name + setManagerName(e.target.value)} + /> + + + + Station Name + setStationName(e.target.value)} + /> + + + + {/* Second Row - Two Inputs */} + + + Station Location + setStationLocation(e.target.value)} + /> + + + + Phone Number + setPhoneNumber(e.target.value)} + /> + + + + + {/* Submit Button */} + + + + + + ); +} diff --git a/src/pages/EditUserModal/index.tsx b/src/pages/EditUserModal/index.tsx new file mode 100644 index 0000000..1e2468e --- /dev/null +++ b/src/pages/EditUserModal/index.tsx @@ -0,0 +1,175 @@ +import React, { useEffect } from "react"; +import { + Box, + Button, + Typography, + TextField, + Modal, + IconButton, +} from "@mui/material"; +import CloseIcon from "@mui/icons-material/Close"; +import { useForm, Controller } from "react-hook-form"; + +interface EditModalProps { + open: boolean; + handleClose: () => void; + handleCreate: (data: FormData) => void; + handleUpdate: (id: string, data: FormData) => void; + editRow: any; +} + +interface FormData { + managerName: string; + stationName: string; + stationLocation: string; + phoneNumber: string; +} + +const EditModal: React.FC = ({ + open, + handleClose, + handleCreate, + handleUpdate, + editRow, +}) => { + const { + control, + handleSubmit, + formState: { errors }, + setValue, + reset, + } = useForm({ + defaultValues: { + managerName: "", + stationName: "", + stationLocation: "", + phoneNumber: "", + }, + }); + + useEffect(() => { + if (editRow) { + setValue("managerName", editRow.managerName); + setValue("stationName", editRow.stationName); + setValue("stationLocation", editRow.stationLocation); + setValue("phoneNumber", editRow.phoneNumber); + } else { + reset(); + } + }, [editRow, setValue, reset]); + + const onSubmit = (data: FormData) => { + if (editRow) { + handleUpdate(editRow.id, data); + } else { + handleCreate(data); + } + handleClose(); + reset(); + }; + + return ( + + + {/* Header */} + + + {editRow ? "Edit Manager" : "Add Manager"} + + + + + + + {/* Horizontal Line */} + + + {/* Input Fields */} + + {/* First Row - Two Inputs */} + + + Manager Name + ( + + )} + /> + + + + Station Name + ( + + )} + /> + + + + {/* Second Row - Two Inputs */} + + + Station Location + ( + + )} + /> + + + + Phone Number + ( + + )} + /> + + + + + {/* Submit Button */} + + + + + + ); +}; + +export default EditModal; \ No newline at end of file diff --git a/src/pages/ManagerList/index.tsx b/src/pages/ManagerList/index.tsx new file mode 100644 index 0000000..50ed6dc --- /dev/null +++ b/src/pages/ManagerList/index.tsx @@ -0,0 +1,131 @@ +import React, { useState } from "react"; +import { Box, Button, Typography, TextField, InputAdornment, IconButton } from "@mui/material"; +import SearchIcon from "@mui/icons-material/Search"; +import EqualizerIcon from "@mui/icons-material/Tune"; +import CustomTable, { Column } from "../../components/CustomTable"; +import AddManagerModal from "../../pages/AddManagerModal"; +import EditUserModal from "../EditUserModal"; + +export default function ManagerList() { + const [search, setSearch] = useState(""); + const [addButtonModal, setAddButtonModal] = useState(false); + const [modalOpen, setModalOpen] = useState(false); + const [rowData, setRowData] = useState(null); + const [viewModal, setViewModal] = React.useState(false); + const [deleteModal, setDeleteModal] = React.useState(false); + + + const [managers, setManagers] = useState([ + { id: 1, srno: 1, managerName: "John Doe", stationName: "Station A", stationLocation: "Location X", phoneNumber: "123-456-7890" }, + { id: 2, srno: 2, managerName: "Jane Smith", stationName: "Station B", stationLocation: "Location Y", phoneNumber: "987-654-3210" }, + ]); + + const handleSearchChange = (event) => { + setSearch(event.target.value); + }; + + const handleClickOpen = () => { + setRowData(null); + setAddButtonModal(true); + }; + + const handleCloseModal = () => { + setAddButtonModal(false); + setModalOpen(false); + setRowData(null); + }; + + const handleUpdate = (id, updatedData) => { + setManagers((prevManagers) => + prevManagers.map((manager) => (manager.id === id ? { ...manager, ...updatedData } : manager)) + ); + handleCloseModal(); + }; + const handleAddManager = (newManager) => { + setManagers((prevManagers) => { + const newId = prevManagers.length > 0 ? prevManagers[prevManagers.length - 1].id + 1 : 1; + return [...prevManagers, { ...newManager, id: newId, srno: newId }]; + }); + setAddButtonModal(false); + }; + + + + const managerColumns: Column[] = [ + { id: "srno", label: "Sr No" }, + { id: "managerName", label: "Manager Name" }, + { id: "stationName", label: "Station Name" }, + { id: "stationLocation", label: "Station Location" }, + { id: "phoneNumber", label: "Phone Number" }, + { id: "action", label: "Action", align: "center" }, + ]; + + const filteredManagers = managers.filter((manager) => + Object.values(manager).some((value) => + typeof value === "string" && value.toLowerCase().includes(search.toLowerCase()) + ) + ); + + return ( + <> + + + Managers + + + + + + ), + }} + /> + + + + + + + + + + + + + + + + + ); +} diff --git a/src/redux/slices/managerSlice.ts b/src/redux/slices/managerSlice.ts new file mode 100644 index 0000000..48387c3 --- /dev/null +++ b/src/redux/slices/managerSlice.ts @@ -0,0 +1,103 @@ +import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit"; +import { toast } from "sonner"; + +// Interfaces +interface Manager { + id: string; + name: string; + email: string; + role: string; + phone: string; + registeredAddress: string; +} + +interface ManagerState { + managers: Manager[]; + isLoading: boolean; +} + +// Dummy API function placeholder +const dummyApiCall = async (response: any, delay = 500) => + new Promise((resolve) => setTimeout(() => resolve(response), delay)); + +// Fetch Manager List +export const fetchManagerList = createAsyncThunk( + "fetchManagerList", + async (_, { rejectWithValue }) => { + try { + const response: any = await dummyApiCall({ data: [{ id: "1", name: "John Doe", email: "john@example.com", role: "Manager", phone: "1234567890", registeredAddress: "Somewhere" }] }); + return response.data; + } catch (error: any) { + toast.error("Error fetching manager list: " + error); + return rejectWithValue("An error occurred"); + } + } +); + +// Delete Manager +export const deleteManager = createAsyncThunk( + "deleteManager", + async (id, { rejectWithValue }) => { + try { + await dummyApiCall(null);initialState +); + +// Add Manager +export const addManager = createAsyncThunk< + Manager, + { name: string; email: string; phone: string; registeredAddress: string }, + { rejectValue: string } +>("addManager", async (data, { rejectWithValue }) => { + try { + const response: any = await dummyApiCall({ id: "2", ...data }); + return response; + } catch (error: any) { + return rejectWithValue("An error occurred"); + } +}); + +const initialState: ManagerState = { + managers: [], + isLoading: false, +}; + +const managerSlice = createSlice({ + name: "manager", + initialState, + reducers: {}, + extraReducers: (builder) => { + builder + .addCase(fetchManagerList.pending, (state) => { + state.isLoading = true; + }) + .addCase(fetchManagerList.fulfilled, (state, action: PayloadAction) => { + state.isLoading = false; + state.managers = action.payload; + }) + .addCase(fetchManagerList.rejected, (state) => { + state.isLoading = false; + }) + .addCase(deleteManager.pending, (state) => { + state.isLoading = true; + }) + .addCase(deleteManager.fulfilled, (state, action) => { + state.isLoading = false; + state.managers = state.managers.filter((manager) => manager.id !== action.payload); + }) + .addCase(deleteManager.rejected, (state) => { + state.isLoading = false; + }) + .addCase(addManager.pending, (state) => { + state.isLoading = true; + }) + .addCase(addManager.fulfilled, (state, action) => { + state.isLoading = false; + state.managers.push(action.payload); + }) + .addCase(addManager.rejected, (state) => { + state.isLoading = false; + }); + }, +}); + +export default managerSlice.reducer; diff --git a/src/router.tsx b/src/router.tsx index d2ce56d..feffbfd 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -15,6 +15,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[]; @@ -86,6 +88,18 @@ export default function AppRouter() { /> } /> + + } + /> + } + /> + + {children} + ); } From a0e7c6ec1c8712e41e030ab55b8e19c3c5c5033f Mon Sep 17 00:00:00 2001 From: Harsh Gogia Date: Thu, 6 Mar 2025 17:55:25 +0530 Subject: [PATCH 2/5] uiBugs --- package-lock.json | 132 +++++++++ package.json | 1 + src/App.css | 9 + src/App.tsx | 1 + src/components/Loading/index.tsx | 28 +- src/pages/Auth/Login/index.tsx | 451 ++++++++++++++++--------------- 6 files changed, 397 insertions(+), 225 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0bb098c..3f84ec6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,7 @@ "react-scripts": "5.0.1", "recharts": "^2.15.1", "sonner": "^1.7.4", + "styled-components": "^6.1.15", "web-vitals": "^4.2.4" }, "devDependencies": { @@ -4031,6 +4032,12 @@ "version": "2.0.3", "license": "MIT" }, + "node_modules/@types/stylis": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", + "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==", + "license": "MIT" + }, "node_modules/@types/trusted-types": { "version": "2.0.7", "license": "MIT" @@ -5364,6 +5371,15 @@ "node": ">= 6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-api": { "version": "3.0.0", "license": "MIT", @@ -5821,6 +5837,15 @@ "postcss": "^8.4" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, "node_modules/css-declaration-sorter": { "version": "6.4.1", "license": "ISC", @@ -5954,6 +5979,17 @@ "version": "0.1.1", "license": "MIT" }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/css-tree": { "version": "1.0.0-alpha.37", "license": "MIT", @@ -13806,6 +13842,12 @@ "version": "1.2.0", "license": "ISC" }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "license": "MIT", @@ -14359,6 +14401,95 @@ "webpack": "^5.0.0" } }, + "node_modules/styled-components": { + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.15.tgz", + "integrity": "sha512-PpOTEztW87Ua2xbmLa7yssjNyUF9vE7wdldRfn1I2E6RTkqknkBYpj771OxM/xrvRGinLy2oysa7GOd7NcZZIA==", + "license": "MIT", + "dependencies": { + "@emotion/is-prop-valid": "1.2.2", + "@emotion/unitless": "0.8.1", + "@types/stylis": "4.2.5", + "css-to-react-native": "3.2.0", + "csstype": "3.1.3", + "postcss": "8.4.49", + "shallowequal": "1.1.0", + "stylis": "4.3.2", + "tslib": "2.6.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/styled-components/node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", + "license": "MIT" + }, + "node_modules/styled-components/node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "license": "MIT" + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/styled-components/node_modules/stylis": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==", + "license": "MIT" + }, + "node_modules/styled-components/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "license": "0BSD" + }, "node_modules/stylehacks": { "version": "5.1.1", "license": "MIT", @@ -15041,6 +15172,7 @@ }, "node_modules/typescript": { "version": "5.7.3", + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index 4e06b17..2a9af7e 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "react-scripts": "5.0.1", "recharts": "^2.15.1", "sonner": "^1.7.4", + "styled-components": "^6.1.15", "web-vitals": "^4.2.4" }, "scripts": { 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/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/pages/Auth/Login/index.tsx b/src/pages/Auth/Login/index.tsx index 89f01cb..7c1be76 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, @@ -70,7 +73,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 }} @@ -78,19 +81,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? + + Date: Fri, 7 Mar 2025 11:11:57 +0530 Subject: [PATCH 3/5] uninstalled-styledlibrary --- package-lock.json | 131 ---------------------------------------------- package.json | 1 - 2 files changed, 132 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3f84ec6..6e35ddc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,6 @@ "react-scripts": "5.0.1", "recharts": "^2.15.1", "sonner": "^1.7.4", - "styled-components": "^6.1.15", "web-vitals": "^4.2.4" }, "devDependencies": { @@ -4032,12 +4031,6 @@ "version": "2.0.3", "license": "MIT" }, - "node_modules/@types/stylis": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", - "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==", - "license": "MIT" - }, "node_modules/@types/trusted-types": { "version": "2.0.7", "license": "MIT" @@ -5371,15 +5364,6 @@ "node": ">= 6" } }, - "node_modules/camelize": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", - "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/caniuse-api": { "version": "3.0.0", "license": "MIT", @@ -5837,15 +5821,6 @@ "postcss": "^8.4" } }, - "node_modules/css-color-keywords": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", - "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", - "license": "ISC", - "engines": { - "node": ">=4" - } - }, "node_modules/css-declaration-sorter": { "version": "6.4.1", "license": "ISC", @@ -5979,17 +5954,6 @@ "version": "0.1.1", "license": "MIT" }, - "node_modules/css-to-react-native": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", - "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", - "license": "MIT", - "dependencies": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^4.0.2" - } - }, "node_modules/css-tree": { "version": "1.0.0-alpha.37", "license": "MIT", @@ -13842,12 +13806,6 @@ "version": "1.2.0", "license": "ISC" }, - "node_modules/shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", - "license": "MIT" - }, "node_modules/shebang-command": { "version": "2.0.0", "license": "MIT", @@ -14401,95 +14359,6 @@ "webpack": "^5.0.0" } }, - "node_modules/styled-components": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.15.tgz", - "integrity": "sha512-PpOTEztW87Ua2xbmLa7yssjNyUF9vE7wdldRfn1I2E6RTkqknkBYpj771OxM/xrvRGinLy2oysa7GOd7NcZZIA==", - "license": "MIT", - "dependencies": { - "@emotion/is-prop-valid": "1.2.2", - "@emotion/unitless": "0.8.1", - "@types/stylis": "4.2.5", - "css-to-react-native": "3.2.0", - "csstype": "3.1.3", - "postcss": "8.4.49", - "shallowequal": "1.1.0", - "stylis": "4.3.2", - "tslib": "2.6.2" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/styled-components" - }, - "peerDependencies": { - "react": ">= 16.8.0", - "react-dom": ">= 16.8.0" - } - }, - "node_modules/styled-components/node_modules/@emotion/is-prop-valid": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", - "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", - "license": "MIT", - "dependencies": { - "@emotion/memoize": "^0.8.1" - } - }, - "node_modules/styled-components/node_modules/@emotion/memoize": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", - "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", - "license": "MIT" - }, - "node_modules/styled-components/node_modules/@emotion/unitless": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", - "license": "MIT" - }, - "node_modules/styled-components/node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/styled-components/node_modules/stylis": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", - "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==", - "license": "MIT" - }, - "node_modules/styled-components/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "license": "0BSD" - }, "node_modules/stylehacks": { "version": "5.1.1", "license": "MIT", diff --git a/package.json b/package.json index 2a9af7e..4e06b17 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "react-scripts": "5.0.1", "recharts": "^2.15.1", "sonner": "^1.7.4", - "styled-components": "^6.1.15", "web-vitals": "^4.2.4" }, "scripts": { From 300a97a3c817e92ce2bf67ce3bfe97114e84e695 Mon Sep 17 00:00:00 2001 From: Harsh Gogia Date: Fri, 7 Mar 2025 13:13:01 +0530 Subject: [PATCH 4/5] minor fixes --- src/pages/Auth/Login/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/Auth/Login/index.tsx b/src/pages/Auth/Login/index.tsx index 7c1be76..b169884 100644 --- a/src/pages/Auth/Login/index.tsx +++ b/src/pages/Auth/Login/index.tsx @@ -251,6 +251,7 @@ export default function Login(props: { disableCustomTheme?: boolean }) { height: "50px", fontFamily: "Gilroy, sans-serif", // Apply Gilroy font }, + endAdornment: ( setShowPassword((prev) => !prev)} From dcc963eb93abc20522a3567c6f4995f0875af44b Mon Sep 17 00:00:00 2001 From: Harsh Gogia Date: Mon, 10 Mar 2025 22:15:36 +0530 Subject: [PATCH 5/5] station-manger --- src/components/CustomTable/index.tsx | 100 ++++---- src/components/Modals/DeleteModal/index.tsx | 186 +++++++------- src/components/Modals/ViewModal/index.tsx | 191 ++++++-------- src/pages/AddManagerModal/index.tsx | 270 +++++++++++--------- src/pages/EditUserModal/index.tsx | 36 ++- src/pages/ManagerList/index.tsx | 215 ++++++++++++---- src/redux/reducers.ts | 5 +- src/redux/slices/managerSlice.ts | 248 ++++++++++++------ 8 files changed, 738 insertions(+), 513 deletions(-) diff --git a/src/components/CustomTable/index.tsx b/src/components/CustomTable/index.tsx index 0f24bd7..ec74881 100644 --- a/src/components/CustomTable/index.tsx +++ b/src/components/CustomTable/index.tsx @@ -9,6 +9,7 @@ import TableHead from "@mui/material/TableHead"; import TableRow from "@mui/material/TableRow"; import Paper, { paperClasses } from "@mui/material/Paper"; import { adminList, deleteAdmin } from "../../redux/slices/adminSlice"; +import { deleteManager } from "../../redux/slices/managerSlice"; import { useDispatch } from "react-redux"; import { Box, @@ -72,6 +73,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 = ({ @@ -98,10 +100,26 @@ 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); @@ -114,38 +132,23 @@ const CustomTable: React.FC = ({ return false; }; - const handleDeleteButton = (id: string | undefined) => { - if (!id) console.error("ID not found", id); - - dispatch(deleteAdmin(id || "")); - setDeleteModal(false); // Close the modal only after deletion - handleClose(); - }; - - const handleViewButton = (id: string | undefined) => { - if (!id) console.error("ID not found", id); - - dispatch(adminList()); - setViewModal(false); - }; - - const handleToggleStatus = () => { - if (selectedRow) { - // Toggle the opposite of current status - const newStatus = selectedRow.statusValue === 1 ? 0 : 1; - handleStatusToggle(selectedRow.id, newStatus); + const handleDeleteButton = (id: string | undefined) => { + if (!id) { + console.error("ID not found for viewing."); + return; } - handleClose(); + setViewModal(true); }; - + + + 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); @@ -249,8 +252,8 @@ const filteredRows = rows.filter( ? "Role" : tableType === "user" ? "User" - : tableType === "manager" - ? "Manager" + : tableType === "managers" + ? "Managers" : tableType === "vehicle" ? "Vehicle" : "Item"} @@ -421,6 +424,7 @@ const filteredRows = rows.filter( variant="text" onClick={(e) => { e.stopPropagation(); + // setSelectedRow(row); setViewModal(true); }} color="primary" @@ -434,15 +438,15 @@ const filteredRows = rows.filter( View {viewModal && ( - - handleViewButton(selectedRow?.id) - } - open={viewModal} - setViewModal={setViewModal} - id={selectedRow?.id} - /> - )} + handleViewButton(selectedRow?.id)} + open={viewModal} + setViewModal={setViewModal} + id={selectedRow?.id} + tableType={tableType} + /> +)} + - - - - - ); + useEffect(() => { + if (!id || !tableType) return; + + const dataList: User[] = tableType === "admin" ? adminList : managerList; + const item = dataList.find((user) => user.id === id); + setSelectedItem(item || null); + }, [id, tableType, adminList, managerList]); + + + + + + const handleDelete = async () => { + if (!id || !tableType) { + return; + } + + setLoading(true); + try { + let deleteResult; + if (tableType === "managers") { + deleteResult = await dispatch(deleteManager(id)).unwrap(); + } + + if (deleteResult) { + await dispatch(fetchManagerList()); // Refresh list only if deletion is successful + } + } catch (error) { + console.error("❌ Error while deleting:", error); + } finally { + setLoading(false); + setDeleteModal(false); + } +}; + + + return ( + setDeleteModal(false)} aria-labelledby="modal-title"> + + + Delete {tableType ? tableType.charAt(0).toUpperCase() + tableType.slice(1) : "User"} + + + + + {selectedItem ? ( + + Are you sure you want to delete {selectedItem.name}? + + ) : ( + No {tableType} found with this ID + )} + + + + + + + + ); } diff --git a/src/components/Modals/ViewModal/index.tsx b/src/components/Modals/ViewModal/index.tsx index 4fafe9b..474065d 100644 --- a/src/components/Modals/ViewModal/index.tsx +++ b/src/components/Modals/ViewModal/index.tsx @@ -1,126 +1,95 @@ -import { Box, Button, Modal, Typography, Divider } from "@mui/material"; -import { RootState } from "../../../redux/store/store"; -import { useSelector } from "react-redux"; -import { useEffect, useState } from "react"; +import { Box, Modal, Typography, Divider } from "@mui/material"; import CloseIcon from "@mui/icons-material/Close"; +import { useEffect, useState } from "react"; +import { useSelector } from "react-redux"; +import { RootState } from "../../../redux/store/store"; + +type User = { + id: number; + name: string; + email?: string; + phone: string; + registeredAddress?: string; + action?: any; +}; type Props = { - open: boolean; - setViewModal: Function; - id?: string; + open: boolean; + setViewModal: (value: boolean) => void; + id?: number; + tableType?: "admin" | "manager"; }; 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, + 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, }; -const btnStyle = { - mt: 2, - px: 5, - py: 1.2, - width: "100%", - textTransform: "capitalize", -}; +export default function ViewModal({ open, setViewModal, id, tableType }: Props) { + const adminList = useSelector((state: RootState) => state.adminReducer.admins) || []; + const managerList = useSelector((state: RootState) => state.managerReducer.managers) || []; -export default function ViewModal({ open, setViewModal, id }: Props) { - const { admins } = useSelector((state: RootState) => state.adminReducer); - const [selectedAdmin, setSelectedAdmin] = useState(null); + const [selectedItem, setSelectedItem] = useState(null); - useEffect(() => { - if (id) { - const admin = admins.find((admin) => admin.id === id); - setSelectedAdmin(admin); - } - }, [id, admins]); + useEffect(() => { + if (!id || !tableType) return; - return ( - - - - - - {selectedAdmin?.name}'s Details - - setViewModal(false)} - sx={{ - cursor: "pointer", - display: "flex", - alignItems: "center", - }} - > - - - - + const dataList: User[] = tableType === "admin" ? adminList : managerList; + const item = dataList.find((user) => user.id === id); - + if (item) { + setSelectedItem(item); // ✅ Updating selected item properly + } + }, [id, tableType, adminList, managerList]); - {selectedAdmin ? ( - - - Name: {selectedAdmin.name} - - - Email: {selectedAdmin.email} - - - Phone: {selectedAdmin.phone} - - - Address:{" "} - {selectedAdmin.registeredAddress ?? "N/A"} - - - ) : ( - - No admin found with this ID - - )} + const formattedType = tableType ? tableType.charAt(0).toUpperCase() + tableType.slice(1) : "User"; - {/* */} - - - ); + return ( + setViewModal(false)} aria-labelledby="modal-title"> + + + + + {selectedItem?.name ? `${selectedItem.name}'s Details` : `${formattedType} Details`} + + setViewModal(false)} sx={{ cursor: "pointer", display: "flex" }}> + + + + + + + + {selectedItem ? ( + + + Name: {selectedItem.name || "N/A"} + + + Email: {selectedItem.email || "N/A"} + + + Phone: {selectedItem.phone || "N/A"} + + + Address: {selectedItem.registeredAddress || "N/A"} + + + ) : ( + No {formattedType} found with this ID + )} + + + ); } diff --git a/src/pages/AddManagerModal/index.tsx b/src/pages/AddManagerModal/index.tsx index b0d32f2..64ac133 100644 --- a/src/pages/AddManagerModal/index.tsx +++ b/src/pages/AddManagerModal/index.tsx @@ -1,126 +1,150 @@ -import { useState } from "react"; -import { - Box, - Button, - Typography, - TextField, - Modal, - IconButton, -} from "@mui/material"; +import React, { useState } from "react"; +import { Box, Button, Typography, Modal, IconButton, TextField } from "@mui/material"; import CloseIcon from "@mui/icons-material/Close"; +import { useDispatch } from "react-redux"; +import { toast } from "sonner"; +import { addManager } from "../../redux/slices/managerSlice"; -export default function AddManagerModal({ open, handleClose, handleAddManager }) { - // State for input fields - const [managerName, setManagerName] = useState(""); - const [stationName, setStationName] = useState(""); - const [stationLocation, setStationLocation] = useState(""); - const [phoneNumber, setPhoneNumber] = useState(""); - - const handleSubmit = () => { - if (!managerName || !stationName || !stationLocation || !phoneNumber) { - alert("Please fill all fields"); - return; - } - - const newManager = { - managerName, - stationName, - stationLocation, - phoneNumber, - }; - - handleAddManager(newManager); // Add manager to table - handleClose(); // Close modal after adding - }; - - return ( - - - {/* Header */} - - - Add Managers - - - - - - - {/* Horizontal Line */} - - - {/* Input Fields */} - - {/* First Row - Two Inputs */} - - - Manager Name - setManagerName(e.target.value)} - /> - - - - Station Name - setStationName(e.target.value)} - /> - - - - {/* Second Row - Two Inputs */} - - - Station Location - setStationLocation(e.target.value)} - /> - - - - Phone Number - setPhoneNumber(e.target.value)} - /> - - - - - {/* Submit Button */} - - - - - - ); +interface AddManagerModalProps { + open: boolean; + handleClose: () => void; } + +const AddManagerModal: React.FC = ({ open, handleClose }) => { + // State for input fields + const [name, setName] = useState(""); + const [stationLocation, setStationLocation] = useState(""); + const [phone, setPhone] = useState(""); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + + const roleId = 5; // Fixed role ID + const roleName = "Peon"; // Required role name + + const dispatch = useDispatch(); + + // Function to validate form inputs + const validateInputs = () => { + if (!name || !stationLocation || !phone || !email || !password) { + toast.error("All fields are required."); + return false; + } + + const phoneRegex = /^[0-9]{6,14}$/; + if (!phoneRegex.test(phone)) { + toast.error("Phone number must be between 6 to 14 digits."); + return false; + } + + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + toast.error("Enter a valid email address."); + return false; + } + + if (password.length < 6) { + toast.error("Password must be at least 6 characters long."); + return false; + } + + return true; + }; + + // Handle form submission + const handleSubmit = async () => { + if (!validateInputs()) return; + + const managerData = { + name, + registeredAddress: stationLocation, + phone, + email, + password, + roleId, + roleName, // ✅ Ensure roleName is correctly included + }; + + try { + const response = await dispatch(addManager(managerData)).unwrap(); + + + + // ✅ Ensure response contains expected data + if (!response || !response.id) { + throw new Error("Invalid response from server. ID is missing."); + } + + // ✅ Show success message from API if available, fallback if not + toast.success(response.message || "Manager added successfully!"); + + resetForm(); + handleClose(); + } catch (error: any) { + console.error("API Error:", error); // ✅ Log error for debugging + + // ✅ Handle both API errors and unexpected errors + toast.error( + error?.response?.data?.message || error.message || "Failed to add manager" + ); + } + }; + + + + // Function to reset form fields + const resetForm = () => { + setName(""); + setStationLocation(""); + setPhone(""); + setEmail(""); + setPassword(""); + }; + + return ( + + + {/* Modal Header */} + + + Add Manager + + + + + + + + + {/* Input Fields */} + + setName(e.target.value)} /> + setStationLocation(e.target.value)} /> + setPhone(e.target.value)} /> + setEmail(e.target.value)} /> + setPassword(e.target.value)} /> + + + {/* Submit Button */} + + + + + + ); +}; + +export default AddManagerModal; diff --git a/src/pages/EditUserModal/index.tsx b/src/pages/EditUserModal/index.tsx index 1e2468e..0747aad 100644 --- a/src/pages/EditUserModal/index.tsx +++ b/src/pages/EditUserModal/index.tsx @@ -15,7 +15,7 @@ interface EditModalProps { handleClose: () => void; handleCreate: (data: FormData) => void; handleUpdate: (id: string, data: FormData) => void; - editRow: any; + editRow: any | null; } interface FormData { @@ -47,26 +47,40 @@ const EditModal: React.FC = ({ }, }); + // Populate form fields when `editRow` changes useEffect(() => { if (editRow) { - setValue("managerName", editRow.managerName); - setValue("stationName", editRow.stationName); - setValue("stationLocation", editRow.stationLocation); - setValue("phoneNumber", editRow.phoneNumber); + setValue("managerName", editRow.name || ""); + setValue("stationName", editRow.stationName || ""); + setValue("stationLocation", editRow.registeredAddress || ""); + setValue("phoneNumber", editRow.phone || ""); } else { - reset(); + reset({ // ✅ Ensure default values are reset when adding a new manager + managerName: "", + stationName: "", + stationLocation: "", + phoneNumber: "", + }); } }, [editRow, setValue, reset]); + const onSubmit = (data: FormData) => { if (editRow) { - handleUpdate(editRow.id, data); + handleUpdate({ + id: editRow.id, + managerName: data.managerName, + stationLocation: data.stationLocation, + phoneNumber: data.phoneNumber + }); } else { handleCreate(data); } handleClose(); reset(); }; + + return ( @@ -100,8 +114,8 @@ const EditModal: React.FC = ({ {/* Input Fields */} - {/* First Row - Two Inputs */} + {/* Manager Name */} Manager Name = ({ /> + {/* Station Name */} Station Name = ({ - {/* Second Row - Two Inputs */} + {/* Station Location */} Station Location = ({ /> + {/* Phone Number */} Phone Number = ({ ); }; -export default EditModal; \ No newline at end of file +export default EditModal; diff --git a/src/pages/ManagerList/index.tsx b/src/pages/ManagerList/index.tsx index 50ed6dc..3514684 100644 --- a/src/pages/ManagerList/index.tsx +++ b/src/pages/ManagerList/index.tsx @@ -1,65 +1,143 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { Box, Button, Typography, TextField, InputAdornment, IconButton } from "@mui/material"; import SearchIcon from "@mui/icons-material/Search"; import EqualizerIcon from "@mui/icons-material/Tune"; import CustomTable, { Column } from "../../components/CustomTable"; import AddManagerModal from "../../pages/AddManagerModal"; import EditUserModal from "../EditUserModal"; +import { useDispatch, useSelector } from "react-redux"; +import { fetchManagerList, addManager,updateManager,deleteManager } from "../../redux/slices/managerSlice"; +import { RootState, AppDispatch } from "../../redux/store/store"; + export default function ManagerList() { + const dispatch = useDispatch(); + const { managers, isLoading } = useSelector((state: RootState) => state.managerReducer); + const [search, setSearch] = useState(""); const [addButtonModal, setAddButtonModal] = useState(false); const [modalOpen, setModalOpen] = useState(false); const [rowData, setRowData] = useState(null); - const [viewModal, setViewModal] = React.useState(false); - const [deleteModal, setDeleteModal] = React.useState(false); + const [viewModal, setViewModal] = useState(false); + const [deleteModal, setDeleteModal] = useState(false); + + useEffect(() => { + dispatch(fetchManagerList()); // Fetch data when component mounts + }, [dispatch]); + + + + // Function to handle adding a manager + const handleAddManager = async (newManager: { + name: string; + registeredAddress: string; + phone: string; + email: string; + password: string; + }) => { + await dispatch(addManager(newManager)); + dispatch(fetchManagerList()); // Refresh list after adding + handleCloseModal(); // Close modal after adding + }; - const [managers, setManagers] = useState([ - { id: 1, srno: 1, managerName: "John Doe", stationName: "Station A", stationLocation: "Location X", phoneNumber: "123-456-7890" }, - { id: 2, srno: 2, managerName: "Jane Smith", stationName: "Station B", stationLocation: "Location Y", phoneNumber: "987-654-3210" }, - ]); - const handleSearchChange = (event) => { + // Function to handle updating a + const handleUpdateManager = async (updatedManager) => { + if (!updatedManager || typeof updatedManager !== "object") { + return; + } + + if (!rowData) { + return; + } + + const managerId = rowData.id; + + if (!updatedManager.managerName || !updatedManager.stationLocation || !updatedManager.phoneNumber) { + return; + } + + try { + const response = await dispatch(updateManager({ + id: managerId, + name: updatedManager.managerName, + registeredAddress: updatedManager.stationLocation, + phone: updatedManager.phoneNumber, + })); + + if (response?.payload?.statusCode === 200) { + await dispatch(fetchManagerList()); // ✅ Refresh list after update + } + + handleCloseModal(); + } catch (error) { + console.error("❌ Update failed:", error); + } + }; + + + // Function to handle deleting a manager + + + const handleDeleteManager = async () => { + if (!rowData?.id) { + console.error("❌ No manager ID found for deletion!"); + return; + } + + try { + + const response = await dispatch(deleteManager(rowData.id)); + + if (response?.payload) { + + dispatch(fetchManagerList()); // Refresh list after deletion + } else { + console.error("❌ Deletion failed!", response); + } + } catch (error) { + console.error("❌ Error deleting manager:", error); + } + + setDeleteModal(false); // Close delete modal + + handleCloseModal(); + }; + + + + + + + + // Function to handle search input change + const handleSearchChange = (event: React.ChangeEvent) => { setSearch(event.target.value); }; + // Open the Add Manager Modal const handleClickOpen = () => { - setRowData(null); + setRowData(null); setAddButtonModal(true); }; + // Close all modals const handleCloseModal = () => { setAddButtonModal(false); setModalOpen(false); setRowData(null); }; - const handleUpdate = (id, updatedData) => { - setManagers((prevManagers) => - prevManagers.map((manager) => (manager.id === id ? { ...manager, ...updatedData } : manager)) - ); - handleCloseModal(); - }; - const handleAddManager = (newManager) => { - setManagers((prevManagers) => { - const newId = prevManagers.length > 0 ? prevManagers[prevManagers.length - 1].id + 1 : 1; - return [...prevManagers, { ...newManager, id: newId, srno: newId }]; - }); - setAddButtonModal(false); - }; - - - + // Table columns definition const managerColumns: Column[] = [ - { id: "srno", label: "Sr No" }, - { id: "managerName", label: "Manager Name" }, - { id: "stationName", label: "Station Name" }, - { id: "stationLocation", label: "Station Location" }, - { id: "phoneNumber", label: "Phone Number" }, + { id: "name", label: "Manager Name" }, + { id: "registeredAddress", label: "Station Location" }, + { id: "phone", label: "Phone Number" }, { id: "action", label: "Action", align: "center" }, ]; + // Filtered manager list based on search input const filteredManagers = managers.filter((manager) => Object.values(manager).some((value) => typeof value === "string" && value.toLowerCase().includes(search.toLowerCase()) @@ -68,6 +146,7 @@ export default function ManagerList() { return ( <> + {/* Header Section */} Managers @@ -98,34 +177,64 @@ export default function ManagerList() { - Loading managers... + ) : ( + ({ + id: manager.id, + name: manager.name, + registeredAddress: manager.registeredAddress, + phone: manager.phone, + action: ( + + + + + + ), + + }))} + tableType="managers" + setRowData={setRowData} + setModalOpen={setModalOpen} + setViewModal={setViewModal} + viewModal={viewModal} + setDeleteModal={setDeleteModal} + deleteModal={deleteModal} + handleDeleteButton={handleDeleteManager} + /> + )} - - /> - - - - + + + + + handleAddManager({ + name: data.managerName, + registeredAddress: data.stationLocation, + phone: data.phoneNumber, + email: data.email, + password: data.password, + }) + } + /> ); } 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 index 48387c3..4d1f415 100644 --- a/src/redux/slices/managerSlice.ts +++ b/src/redux/slices/managerSlice.ts @@ -1,103 +1,189 @@ import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit"; +import http from "../../lib/https"; import { toast } from "sonner"; -// Interfaces +// Define TypeScript types interface Manager { - id: string; - name: string; - email: string; - role: string; - phone: string; - registeredAddress: string; + id: string; + name: string; + email: string; + phone?: string; + role: string; } interface ManagerState { - managers: Manager[]; - isLoading: boolean; + managers: Manager[]; + loading: boolean; + error: string | null; } -// Dummy API function placeholder -const dummyApiCall = async (response: any, delay = 500) => - new Promise((resolve) => setTimeout(() => resolve(response), delay)); +// Initial state +const initialState: ManagerState = { + managers: [], + loading: false, + error: null, +}; // Fetch Manager List export const fetchManagerList = createAsyncThunk( - "fetchManagerList", - async (_, { rejectWithValue }) => { - try { - const response: any = await dummyApiCall({ data: [{ id: "1", name: "John Doe", email: "john@example.com", role: "Manager", phone: "1234567890", registeredAddress: "Somewhere" }] }); - return response.data; - } catch (error: any) { - toast.error("Error fetching manager list: " + error); - return rejectWithValue("An error occurred"); - } - } + "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); + return rejectWithValue( + error?.response?.data?.message || "An error occurred" + ); + } + } +); + +// Create Manager +export const addManager = createAsyncThunk( + "addManager", + async (data, { rejectWithValue }) => { + try { + + const response = await http.post("create-manager", data); + toast.success("Manager created successfully"); + + // ✅ Ensure the response contains the expected data + if (!response.data || !response.data.data || !response.data.data.id) { + console.error("❌ ERROR: Missing manager ID in response", response.data); + throw new Error("Invalid API response: Missing manager ID"); + } + + return response.data.data; + } catch (error: any) { + console.error("❌ API Error:", error?.response?.data || error); + return rejectWithValue(error?.response?.data?.message || "An error occurred"); + } + } +); + +export const updateManager = createAsyncThunk< + Manager, + { id: string; name?: string; email?: string; phone?: string; role?: string; registeredAddress?: string }, + { rejectValue: string } +>( + "updateManager", + async ({ id, ...managerData }, { rejectWithValue }) => { + try { + + + const response = await http.put(`${id}/update-manager`, managerData); + + + toast.success("Manager updated successfully!"); + return response.data.data; // ✅ Extracting correct response data + } catch (error: any) { + + toast.error("Error updating manager: " + error); + return rejectWithValue( + error.response?.data?.message || "An error occurred" + ); + } + } ); // Delete Manager -export const deleteManager = createAsyncThunk( - "deleteManager", - async (id, { rejectWithValue }) => { - try { - await dummyApiCall(null);initialState +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; // Return the ID of the deleted manager + } catch (error: any) { + toast.error("Error deleting manager: " + error); + return rejectWithValue( + error.response?.data?.message || "An error occurred" + ); + } + } ); -// Add Manager -export const addManager = createAsyncThunk< - Manager, - { name: string; email: string; phone: string; registeredAddress: string }, - { rejectValue: string } ->("addManager", async (data, { rejectWithValue }) => { - try { - const response: any = await dummyApiCall({ id: "2", ...data }); - return response; - } catch (error: any) { - return rejectWithValue("An error occurred"); - } + + + + +// Create Slice +const managerSlice = createSlice({ + name: "fetchManagers", + initialState, + reducers: {}, + extraReducers: (builder) => { + builder + // Fetch Managers + .addCase(fetchManagerList.pending, (state) => { + state.loading = true; + state.error = null; + }) + .addCase( + fetchManagerList.fulfilled, + (state, action: PayloadAction) => { + state.loading = false; + state.managers = action.payload; + } + ) + .addCase(fetchManagerList.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; + }) + + // 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(m => m.id === updatedManager.id); + if (index !== -1) { + state.managers[index] = { ...state.managers[index], ...updatedManager }; // 🔥 Merge updated fields + } + }) + + .addCase(updateManager.rejected, (state) => { + state.loading = false; + }) + + // Delete 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) => { + state.loading = false; }); -const initialState: ManagerState = { - managers: [], - isLoading: false, -}; - -const managerSlice = createSlice({ - name: "manager", - initialState, - reducers: {}, - extraReducers: (builder) => { - builder - .addCase(fetchManagerList.pending, (state) => { - state.isLoading = true; - }) - .addCase(fetchManagerList.fulfilled, (state, action: PayloadAction) => { - state.isLoading = false; - state.managers = action.payload; - }) - .addCase(fetchManagerList.rejected, (state) => { - state.isLoading = false; - }) - .addCase(deleteManager.pending, (state) => { - state.isLoading = true; - }) - .addCase(deleteManager.fulfilled, (state, action) => { - state.isLoading = false; - state.managers = state.managers.filter((manager) => manager.id !== action.payload); - }) - .addCase(deleteManager.rejected, (state) => { - state.isLoading = false; - }) - .addCase(addManager.pending, (state) => { - state.isLoading = true; - }) - .addCase(addManager.fulfilled, (state, action) => { - state.isLoading = false; - state.managers.push(action.payload); - }) - .addCase(addManager.rejected, (state) => { - state.isLoading = false; - }); - }, + + }, }); export default managerSlice.reducer;