ui-station-manager

This commit is contained in:
harsh gogia 2025-03-05 18:06:51 +05:30
parent 13ac338091
commit 5cfee36f0b
8 changed files with 563 additions and 93 deletions

View file

@ -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: <AnalyticsRoundedIcon />,
url: "/panel/admin-list",
},
userRole === "admin" && {
text: "Users",
icon: <AnalyticsRoundedIcon />,
url: "/panel/user-list",
},
userRole === "superadmin" && {
text: "Roles",
icon: <AnalyticsRoundedIcon />,
url: "/panel/role-list",
},
userRole === "admin" && {
text: "Users",
icon: <AnalyticsRoundedIcon />,
url: "/panel/user-list",
},
userRole === "admin" && {
text: "Managers",
icon: <ManageAccountsOutlinedIcon />,
url: "/panel/manager-list", // Placeholder for now
},
];
const filteredMenuItems = baseMenuItems.filter(Boolean);
return (
<Stack sx={{ flexGrow: 1, p: 1, justifyContent: "space-between" }}>
<List dense>
@ -59,7 +62,6 @@ export default function MenuContent({ hidden }: PropType) {
disablePadding
sx={{ display: "block", py: 1 }}
>
{/* Wrap ListItemButton with Link to enable routing */}
<ListItemButton
component={Link}
to={item.url}
@ -92,4 +94,4 @@ export default function MenuContent({ hidden }: PropType) {
</List>
</Stack>
);
}
}

View file

@ -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<any>(null);
useEffect(() => {
if (id) {
const admin = admins.find((admin) => admin.id === id);
setSelectedAdmin(admin || null);
}
}, [id, admins]);
return (
<Modal
open={open}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<Typography
id="modal-modal-title"
variant="h6"
component="h2"
align="center"
>
Details of {selectedAdmin?.name}
</Typography>
{selectedAdmin ? (
<>
<Typography align="center">Name: {selectedAdmin?.name}</Typography>
<Typography align="center">Email: {selectedAdmin?.email}</Typography>
<Typography align="center">Phone: {selectedAdmin?.phone}</Typography>
<Typography align="center">Address: {selectedAdmin?.registeredAddress}</Typography>
</>
) : (
<Typography align="center">No admin found with this ID</Typography>
)}
<Box sx={{ display: "flex", justifyContent: "space-between", mt: 4, gap: 2 }}>
<Button
variant="contained"
color="error"
type="button"
sx={btnStyle}
onClick={() => setViewModal(false)}
>
Close
</Button>
</Box>
</Box>
</Modal>
);
}

View file

@ -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 (
<Modal open={open} onClose={handleClose} aria-labelledby="add-manager-modal">
<Box
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: "background.paper",
boxShadow: 24,
p: 3,
borderRadius: 2,
}}
>
{/* Header */}
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<Typography variant="h6" fontWeight={600}>
Add Managers
</Typography>
<IconButton onClick={handleClose}>
<CloseIcon />
</IconButton>
</Box>
{/* Horizontal Line */}
<Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
{/* Input Fields */}
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
{/* First Row - Two Inputs */}
<Box sx={{ display: "flex", gap: 2 }}>
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
<Typography variant="body2" fontWeight={500}>Manager Name</Typography>
<TextField
fullWidth
placeholder="Enter Manager Name"
size="small"
value={managerName}
onChange={(e) => setManagerName(e.target.value)}
/>
</Box>
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
<Typography variant="body2" fontWeight={500}>Station Name</Typography>
<TextField
fullWidth
placeholder="Enter Station Name"
size="small"
value={stationName}
onChange={(e) => setStationName(e.target.value)}
/>
</Box>
</Box>
{/* Second Row - Two Inputs */}
<Box sx={{ display: "flex", gap: 2 }}>
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
<Typography variant="body2" fontWeight={500}>Station Location</Typography>
<TextField
fullWidth
placeholder="Enter Station Location"
size="small"
value={stationLocation}
onChange={(e) => setStationLocation(e.target.value)}
/>
</Box>
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
<Typography variant="body2" fontWeight={500}>Phone Number</Typography>
<TextField
fullWidth
placeholder="Enter Phone Number"
size="small"
value={phoneNumber}
onChange={(e) => setPhoneNumber(e.target.value)}
/>
</Box>
</Box>
</Box>
{/* Submit Button */}
<Box sx={{ display: "flex", justifyContent: "flex-end", mt: 3 }}>
<Button variant="contained" onClick={handleSubmit}>
Add Manager
</Button>
</Box>
</Box>
</Modal>
);
}

View file

@ -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<EditModalProps> = ({
open,
handleClose,
handleCreate,
handleUpdate,
editRow,
}) => {
const {
control,
handleSubmit,
formState: { errors },
setValue,
reset,
} = useForm<FormData>({
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 (
<Modal open={open} onClose={handleClose} aria-labelledby="edit-manager-modal">
<Box
component="form"
onSubmit={handleSubmit(onSubmit)}
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: "background.paper",
boxShadow: 24,
p: 3,
borderRadius: 2,
}}
>
{/* Header */}
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<Typography variant="h6" fontWeight={600}>
{editRow ? "Edit Manager" : "Add Manager"}
</Typography>
<IconButton onClick={handleClose}>
<CloseIcon />
</IconButton>
</Box>
{/* Horizontal Line */}
<Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
{/* Input Fields */}
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
{/* First Row - Two Inputs */}
<Box sx={{ display: "flex", gap: 2 }}>
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
<Typography variant="body2" fontWeight={500} mb={0.5}>Manager Name</Typography>
<Controller
name="managerName"
control={control}
rules={{ required: "Manager Name is required" }}
render={({ field }) => (
<TextField {...field} fullWidth placeholder="Enter Manager Name" size="small" error={!!errors.managerName} helperText={errors.managerName?.message} />
)}
/>
</Box>
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
<Typography variant="body2" fontWeight={500} mb={0.5}>Station Name</Typography>
<Controller
name="stationName"
control={control}
rules={{ required: "Station Name is required" }}
render={({ field }) => (
<TextField {...field} fullWidth placeholder="Enter Station Name" size="small" error={!!errors.stationName} helperText={errors.stationName?.message} />
)}
/>
</Box>
</Box>
{/* Second Row - Two Inputs */}
<Box sx={{ display: "flex", gap: 2 }}>
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
<Typography variant="body2" fontWeight={500} mb={0.5}>Station Location</Typography>
<Controller
name="stationLocation"
control={control}
rules={{ required: "Station Location is required" }}
render={({ field }) => (
<TextField {...field} fullWidth placeholder="Enter Station Location" size="small" error={!!errors.stationLocation} helperText={errors.stationLocation?.message} />
)}
/>
</Box>
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
<Typography variant="body2" fontWeight={500} mb={0.5}>Phone Number</Typography>
<Controller
name="phoneNumber"
control={control}
rules={{
required: "Phone number is required",
pattern: {
value: /^[0-9]+$/,
message: "Only numbers are allowed",
},
}}
render={({ field }) => (
<TextField {...field} fullWidth placeholder="Enter Phone Number" size="small" error={!!errors.phoneNumber} helperText={errors.phoneNumber?.message} />
)}
/>
</Box>
</Box>
</Box>
{/* Submit Button */}
<Box sx={{ display: "flex", justifyContent: "flex-end", mt: 3 }}>
<Button variant="contained" type="submit">
{editRow ? "Update" : "Create"}
</Button>
</Box>
</Box>
</Modal>
);
};
export default EditModal;

View file

@ -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<boolean>(false);
const [deleteModal, setDeleteModal] = React.useState<boolean>(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 (
<>
<Box sx={{ width: "100%", display: "flex", flexDirection: "column", gap: 2, mt: 2 }}>
<Typography component="h2" variant="h6" sx={{ fontWeight: 600 }}>
Managers
</Typography>
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<TextField
variant="outlined"
size="small"
placeholder="Search"
value={search}
onChange={handleSearchChange}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
/>
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
<Button variant="contained" size="medium" onClick={handleClickOpen}>
Add Manager
</Button>
<IconButton>
<EqualizerIcon />
</IconButton>
</Box>
</Box>
</Box>
<CustomTable
columns={managerColumns}
rows={filteredManagers}
setRowData={setRowData}
setModalOpen={setModalOpen}
setViewModal={setViewModal}
viewModal={viewModal}
setDeleteModal={setDeleteModal}
deleteModal={deleteModal}
/>
<EditUserModal
open={modalOpen}
handleClose={handleCloseModal}
handleUpdate={handleUpdate}
editRow={rowData}
/>
<AddManagerModal
open={addButtonModal}
handleClose={handleCloseModal}
initialData={rowData}
handleAddManager={handleAddManager}
/>
</>
);
}

View file

@ -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<Manager[], void, { rejectValue: string }>(
"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<string, string, { rejectValue: string }>(
"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<Manager[]>) => {
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;

View file

@ -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() {
/>
}
/>
<Route
path="manager-list"
element={
<ProtectedRoute
caps={[]}
component={<ManagerList />}
/>
}
/>
<Route
path="role-list"
element={

View file

@ -55,5 +55,6 @@ export default function AppTheme(props: AppThemeProps) {
<ThemeProvider theme={theme} disableTransitionOnChange>
{children}
</ThemeProvider>
);
}