dev-jaanvi #1

Open
jaanvi wants to merge 155 commits from dev-jaanvi into main
8 changed files with 738 additions and 513 deletions
Showing only changes of commit dcc963eb93 - Show all commits

View file

@ -9,6 +9,7 @@ import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow"; import TableRow from "@mui/material/TableRow";
import Paper, { paperClasses } from "@mui/material/Paper"; import Paper, { paperClasses } from "@mui/material/Paper";
import { adminList, deleteAdmin } from "../../redux/slices/adminSlice"; import { adminList, deleteAdmin } from "../../redux/slices/adminSlice";
import { deleteManager } from "../../redux/slices/managerSlice";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { import {
Box, Box,
@ -72,6 +73,7 @@ interface CustomTableProps {
handleStatusToggle: (id: string, currentStatus: number) => void; handleStatusToggle: (id: string, currentStatus: number) => void;
tableType: string; // Adding tableType prop to change header text dynamically tableType: string; // Adding tableType prop to change header text dynamically
handleClickOpen: () => void; handleClickOpen: () => void;
handleDeleteButton: (id: string | number | undefined) => void;
} }
const CustomTable: React.FC<CustomTableProps> = ({ const CustomTable: React.FC<CustomTableProps> = ({
@ -98,10 +100,26 @@ const CustomTable: React.FC<CustomTableProps> = ({
const open = Boolean(anchorEl); const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLElement>, row: Row) => { const handleClick = (event: React.MouseEvent<HTMLElement>, row: Row) => {
setAnchorEl(event.currentTarget); setAnchorEl(event.currentTarget);
setSelectedRow(row); // Ensure the row data is set setSelectedRow(row);
setRowData(row); setRowData(row);
}; };
const handleViewButton = (id: string | undefined) => {
if (!id) {
console.error("ID not found for viewing.");
return;
}
setViewModal(true);
};
const handleClose = () => { const handleClose = () => {
setAnchorEl(null); setAnchorEl(null);
@ -115,37 +133,22 @@ const CustomTable: React.FC<CustomTableProps> = ({
}; };
const handleDeleteButton = (id: string | undefined) => { const handleDeleteButton = (id: string | undefined) => {
if (!id) console.error("ID not found", id); if (!id) {
console.error("ID not found for viewing.");
dispatch(deleteAdmin(id || "")); return;
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);
} }
handleClose(); setViewModal(true);
}; };
const filteredRows = rows.filter( const filteredRows = rows.filter(
(row) => (row) =>
(row.name && (row.name &&
row.name.toLowerCase().includes(searchQuery.toLowerCase())) || row.name.toLowerCase().includes(searchQuery.toLowerCase())) ||
false false
); );
const indexOfLastRow = currentPage * usersPerPage; const indexOfLastRow = currentPage * usersPerPage;
const indexOfFirstRow = indexOfLastRow - usersPerPage; const indexOfFirstRow = indexOfLastRow - usersPerPage;
const currentRows = filteredRows.slice(indexOfFirstRow, indexOfLastRow); const currentRows = filteredRows.slice(indexOfFirstRow, indexOfLastRow);
@ -249,8 +252,8 @@ const filteredRows = rows.filter(
? "Role" ? "Role"
: tableType === "user" : tableType === "user"
? "User" ? "User"
: tableType === "manager" : tableType === "managers"
? "Manager" ? "Managers"
: tableType === "vehicle" : tableType === "vehicle"
? "Vehicle" ? "Vehicle"
: "Item"} : "Item"}
@ -421,6 +424,7 @@ const filteredRows = rows.filter(
variant="text" variant="text"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
// setSelectedRow(row);
setViewModal(true); setViewModal(true);
}} }}
color="primary" color="primary"
@ -434,15 +438,15 @@ const filteredRows = rows.filter(
View View
</Button> </Button>
{viewModal && ( {viewModal && (
<ViewModal <ViewModal
handleView={() => handleView={() => handleViewButton(selectedRow?.id)}
handleViewButton(selectedRow?.id) open={viewModal}
} setViewModal={setViewModal}
open={viewModal} id={selectedRow?.id}
setViewModal={setViewModal} tableType={tableType}
id={selectedRow?.id} />
/> )}
)}
<Button <Button
variant="text" variant="text"
@ -481,6 +485,7 @@ const filteredRows = rows.filter(
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
setDeleteModal(true); setDeleteModal(true);
}} }}
color="error" color="error"
sx={{ sx={{
@ -498,13 +503,16 @@ const filteredRows = rows.filter(
{/* Modals */} {/* Modals */}
{deleteModal && ( {deleteModal && (
<DeleteModal <DeleteModal
handleDelete={() => handleDeleteButton(selectedRow?.id)} handleDelete={() => handleDeleteButton(selectedRow?.id)}
open={deleteModal} open={deleteModal}
setDeleteModal={setDeleteModal} setDeleteModal={setDeleteModal}
id={selectedRow?.id} id={selectedRow?.id}
/> tableType={tableType}
)} />
)}
</Box> </Box>
); );
}; };

View file

@ -1,98 +1,108 @@
import { Box, Button, Modal, Typography } from "@mui/material"; import { Box, Modal, Typography, Divider, Button } from "@mui/material";
import CloseIcon from "@mui/icons-material/Close"; import { useEffect, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { RootState, AppDispatch } from "../../../redux/store/store";
import { deleteManager, fetchManagerList } from "../../../redux/slices/managerSlice";
type User = {
id: number;
name: string;
email?: string;
phone: string;
registeredAddress?: string;
};
type Props = { type Props = {
open: boolean; open: boolean;
setDeleteModal: Function; setDeleteModal: (value: boolean) => void;
handleDelete: (id: string | undefined) => void; id?: number;
id?: string | undefined; tableType?: "admin" | "manager";
}; };
const style = { const style = {
position: "absolute", position: "absolute",
top: "50%", top: "50%",
left: "50%", left: "50%",
transform: "translate(-50%, -50%)", transform: "translate(-50%, -50%)",
width: 330, width: 400,
bgcolor: "background.paper", bgcolor: "background.paper",
borderRadius: 1.5, borderRadius: 2,
boxShadow: 24, boxShadow: "0px 4px 20px rgba(0, 0, 0, 0.15)",
p: 3, p: 4,
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 2,
}; };
const btnStyle = { py: 1, px: 5, width: "50%", textTransform: "capitalize" }; export default function DeleteModal({ open, setDeleteModal, id, tableType }: Props) {
const dispatch = useDispatch<AppDispatch>();
const adminList = useSelector((state: RootState) => state.adminReducer.admins) || [];
const managerList = useSelector((state: RootState) => state.managerReducer.managers) || [];
export default function DeleteModal({ const [selectedItem, setSelectedItem] = useState<User | null>(null);
open, const [loading, setLoading] = useState(false);
setDeleteModal,
handleDelete,
id,
}: Props) {
// console.log("DeleteModal opened with ID:", id)
return ( useEffect(() => {
<Modal if (!id || !tableType) return;
open={open}
aria-labelledby="modal-modal-title" const dataList: User[] = tableType === "admin" ? adminList : managerList;
aria-describedby="modal-modal-description" const item = dataList.find((user) => user.id === id);
> setSelectedItem(item || null);
<Box sx={style}> }, [id, tableType, adminList, managerList]);
<Typography
id="modal-modal-title"
variant="h6"
component="h2"
align="center"
sx={{ flexGrow: 1 }} // This ensures the title takes up available space const handleDelete = async () => {
> if (!id || !tableType) {
Delete Record return;
</Typography> }
<Box
onClick={() => setDeleteModal(false)} setLoading(true);
sx={{ try {
cursor: "pointer", let deleteResult;
display: "flex", if (tableType === "managers") {
alignItems: "center", deleteResult = await dispatch(deleteManager(id)).unwrap();
justifyContent: "flex-end", // Aligns the close icon to the right }
marginTop: -3.5
}} if (deleteResult) {
> await dispatch(fetchManagerList()); // Refresh list only if deletion is successful
<CloseIcon /> }
</Box> } catch (error) {
<Typography console.error("❌ Error while deleting:", error);
id="modal-modal-description" } finally {
sx={{ mt: 2 }} setLoading(false);
align="center" setDeleteModal(false);
> }
Are you sure you want to delete this record? };
</Typography>
<Box
sx={{ return (
display: "flex", <Modal open={open} onClose={() => setDeleteModal(false)} aria-labelledby="modal-title">
justifyContent: "space-between", <Box sx={style}>
mt: 4, <Typography variant="h5" fontWeight="bold" sx={{ width: "100%", textAlign: "center" }}>
gap: 2, Delete {tableType ? tableType.charAt(0).toUpperCase() + tableType.slice(1) : "User"}
}} </Typography>
>
<Button <Divider sx={{ width: "100%" }} />
variant="contained"
color="error" {selectedItem ? (
type="button" <Typography align="center">
sx={btnStyle} Are you sure you want to delete <b>{selectedItem.name}</b>?
onClick={() => setDeleteModal(false)} </Typography>
> ) : (
Cancel <Typography align="center">No {tableType} found with this ID</Typography>
</Button> )}
<Button
variant="contained" <Box sx={{ display: "flex", gap: 2 }}>
type="button" <Button variant="contained" color="error" onClick={handleDelete} disabled={loading}>
color="primary" {loading ? "Deleting..." : "Delete"}
sx={btnStyle} </Button>
onClick={() => handleDelete(id || "")} <Button variant="outlined" onClick={() => setDeleteModal(false)}>Cancel</Button>
> </Box>
Delete </Box>
</Button> </Modal>
</Box> );
</Box>
</Modal>
);
} }

View file

@ -1,126 +1,95 @@
import { Box, Button, Modal, Typography, Divider } from "@mui/material"; import { Box, Modal, Typography, Divider } from "@mui/material";
import { RootState } from "../../../redux/store/store";
import { useSelector } from "react-redux";
import { useEffect, useState } from "react";
import CloseIcon from "@mui/icons-material/Close"; 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 = { type Props = {
open: boolean; open: boolean;
setViewModal: Function; setViewModal: (value: boolean) => void;
id?: string; id?: number;
tableType?: "admin" | "manager";
}; };
const style = { const style = {
position: "absolute", position: "absolute",
top: "50%", top: "50%",
left: "50%", left: "50%",
transform: "translate(-50%, -50%)", transform: "translate(-50%, -50%)",
width: 400, width: 400,
bgcolor: "background.paper", bgcolor: "background.paper",
borderRadius: 2, borderRadius: 2,
boxShadow: "0px 4px 20px rgba(0, 0, 0, 0.15)", boxShadow: "0px 4px 20px rgba(0, 0, 0, 0.15)",
p: 4, p: 4,
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
alignItems: "center", alignItems: "center",
gap: 2, gap: 2,
}; };
const btnStyle = { export default function ViewModal({ open, setViewModal, id, tableType }: Props) {
mt: 2, const adminList = useSelector((state: RootState) => state.adminReducer.admins) || [];
px: 5, const managerList = useSelector((state: RootState) => state.managerReducer.managers) || [];
py: 1.2,
width: "100%",
textTransform: "capitalize",
};
export default function ViewModal({ open, setViewModal, id }: Props) { const [selectedItem, setSelectedItem] = useState<User | null>(null);
const { admins } = useSelector((state: RootState) => state.adminReducer);
const [selectedAdmin, setSelectedAdmin] = useState<any>(null);
useEffect(() => { useEffect(() => {
if (id) { if (!id || !tableType) return;
const admin = admins.find((admin) => admin.id === id);
setSelectedAdmin(admin);
}
}, [id, admins]);
return ( const dataList: User[] = tableType === "admin" ? adminList : managerList;
<Modal const item = dataList.find((user) => user.id === id);
open={open}
aria-labelledby="modal-title"
aria-describedby="modal-description"
>
<Box sx={style}>
<Typography
id="modal-title"
variant="h5"
fontWeight="bold"
sx={{ width: "100%" }}
>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
width: "100%",
}}
>
<Box sx={{ flex: 1, textAlign: "center" }}>
{selectedAdmin?.name}'s Details
</Box>
<Box
onClick={() => setViewModal(false)}
sx={{
cursor: "pointer",
display: "flex",
alignItems: "center",
}}
>
<CloseIcon />
</Box>
</Box>
</Typography>
<Divider sx={{ width: "100%" }} /> if (item) {
setSelectedItem(item); // ✅ Updating selected item properly
}
}, [id, tableType, adminList, managerList]);
{selectedAdmin ? ( const formattedType = tableType ? tableType.charAt(0).toUpperCase() + tableType.slice(1) : "User";
<Box
sx={{
width: "80%",
textAlign: "left",
display: "flex",
flexDirection: "column",
gap: 1.5,
whiteSpace: "pre-wrap", // the text wraps properly
wordBreak: "break-word",
overflowWrap: "break-word",
}}
>
<Typography variant="body1">
<b>Name:</b> {selectedAdmin.name}
</Typography>
<Typography variant="body1">
<strong>Email:</strong> {selectedAdmin.email}
</Typography>
<Typography variant="body1">
<strong>Phone:</strong> {selectedAdmin.phone}
</Typography>
<Typography variant="body1">
<strong>Address:</strong>{" "}
{selectedAdmin.registeredAddress ?? "N/A"}
</Typography>
</Box>
) : (
<Typography align="center">
No admin found with this ID
</Typography>
)}
{/* <Button variant="contained" color="error" sx={btnStyle} onClick={() => setViewModal(false)}> return (
Close <Modal open={open} onClose={() => setViewModal(false)} aria-labelledby="modal-title">
</Button> */} <Box sx={style}>
</Box> <Typography variant="h5" fontWeight="bold" sx={{ width: "100%" }}>
</Modal> <Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
); <Box sx={{ flex: 1, textAlign: "center" }}>
{selectedItem?.name ? `${selectedItem.name}'s Details` : `${formattedType} Details`}
</Box>
<Box onClick={() => setViewModal(false)} sx={{ cursor: "pointer", display: "flex" }}>
<CloseIcon />
</Box>
</Box>
</Typography>
<Divider sx={{ width: "100%" }} />
{selectedItem ? (
<Box sx={{ width: "80%", textAlign: "left", display: "flex", flexDirection: "column", gap: 1.5 }}>
<Typography variant="body1">
<b>Name:</b> {selectedItem.name || "N/A"}
</Typography>
<Typography variant="body1">
<b>Email:</b> {selectedItem.email || "N/A"}
</Typography>
<Typography variant="body1">
<b>Phone:</b> {selectedItem.phone || "N/A"}
</Typography>
<Typography variant="body1">
<b>Address:</b> {selectedItem.registeredAddress || "N/A"}
</Typography>
</Box>
) : (
<Typography align="center">No {formattedType} found with this ID</Typography>
)}
</Box>
</Modal>
);
} }

View file

@ -1,126 +1,150 @@
import { useState } from "react"; import React, { useState } from "react";
import { import { Box, Button, Typography, Modal, IconButton, TextField } from "@mui/material";
Box,
Button,
Typography,
TextField,
Modal,
IconButton,
} from "@mui/material";
import CloseIcon from "@mui/icons-material/Close"; 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 }) { interface AddManagerModalProps {
// State for input fields open: boolean;
const [managerName, setManagerName] = useState(""); handleClose: () => void;
const [stationName, setStationName] = useState(""); }
const [stationLocation, setStationLocation] = useState("");
const [phoneNumber, setPhoneNumber] = useState("");
const handleSubmit = () => { const AddManagerModal: React.FC<AddManagerModalProps> = ({ open, handleClose }) => {
if (!managerName || !stationName || !stationLocation || !phoneNumber) { // State for input fields
alert("Please fill all fields"); const [name, setName] = useState("");
return; const [stationLocation, setStationLocation] = useState("");
} const [phone, setPhone] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const newManager = { const roleId = 5; // Fixed role ID
managerName, const roleName = "Peon"; // Required role name
stationName,
stationLocation,
phoneNumber,
};
handleAddManager(newManager); // Add manager to table const dispatch = useDispatch();
handleClose(); // Close modal after adding
// 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
}; };
return ( try {
<Modal open={open} onClose={handleClose} aria-labelledby="add-manager-modal"> const response = await dispatch(addManager(managerData)).unwrap();
<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%" }}> // ✅ Ensure response contains expected data
<Typography variant="body2" fontWeight={500}>Station Name</Typography> if (!response || !response.id) {
<TextField throw new Error("Invalid response from server. ID is missing.");
fullWidth }
placeholder="Enter Station Name"
size="small"
value={stationName}
onChange={(e) => setStationName(e.target.value)}
/>
</Box>
</Box>
{/* Second Row - Two Inputs */} // ✅ Show success message from API if available, fallback if not
<Box sx={{ display: "flex", gap: 2 }}> toast.success(response.message || "Manager added successfully!");
<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%" }}> resetForm();
<Typography variant="body2" fontWeight={500}>Phone Number</Typography> handleClose();
<TextField } catch (error: any) {
fullWidth console.error("API Error:", error); // ✅ Log error for debugging
placeholder="Enter Phone Number"
size="small"
value={phoneNumber}
onChange={(e) => setPhoneNumber(e.target.value)}
/>
</Box>
</Box>
</Box>
{/* Submit Button */} // ✅ Handle both API errors and unexpected errors
<Box sx={{ display: "flex", justifyContent: "flex-end", mt: 3 }}> toast.error(
<Button variant="contained" onClick={handleSubmit}> error?.response?.data?.message || error.message || "Failed to add manager"
Add Manager );
</Button> }
</Box> };
</Box>
</Modal>
);
} // Function to reset form fields
const resetForm = () => {
setName("");
setStationLocation("");
setPhone("");
setEmail("");
setPassword("");
};
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,
}}
>
{/* Modal Header */}
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<Typography variant="h6" fontWeight={600}>
Add Manager
</Typography>
<IconButton onClick={handleClose}>
<CloseIcon />
</IconButton>
</Box>
<Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
{/* Input Fields */}
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
<TextField label="Manager Name" fullWidth size="small" value={name} onChange={(e) => setName(e.target.value)} />
<TextField label="Station Location" fullWidth size="small" value={stationLocation} onChange={(e) => setStationLocation(e.target.value)} />
<TextField label="Phone Number" fullWidth size="small" value={phone} onChange={(e) => setPhone(e.target.value)} />
<TextField label="Email" fullWidth size="small" value={email} onChange={(e) => setEmail(e.target.value)} />
<TextField label="Password" type="password" fullWidth size="small" value={password} onChange={(e) => setPassword(e.target.value)} />
</Box>
{/* Submit Button */}
<Box sx={{ display: "flex", justifyContent: "flex-end", mt: 3 }}>
<Button variant="contained" onClick={handleSubmit}>
Add Manager
</Button>
</Box>
</Box>
</Modal>
);
};
export default AddManagerModal;

View file

@ -15,7 +15,7 @@ interface EditModalProps {
handleClose: () => void; handleClose: () => void;
handleCreate: (data: FormData) => void; handleCreate: (data: FormData) => void;
handleUpdate: (id: string, data: FormData) => void; handleUpdate: (id: string, data: FormData) => void;
editRow: any; editRow: any | null;
} }
interface FormData { interface FormData {
@ -47,20 +47,32 @@ const EditModal: React.FC<EditModalProps> = ({
}, },
}); });
// Populate form fields when `editRow` changes
useEffect(() => { useEffect(() => {
if (editRow) { if (editRow) {
setValue("managerName", editRow.managerName); setValue("managerName", editRow.name || "");
setValue("stationName", editRow.stationName); setValue("stationName", editRow.stationName || "");
setValue("stationLocation", editRow.stationLocation); setValue("stationLocation", editRow.registeredAddress || "");
setValue("phoneNumber", editRow.phoneNumber); setValue("phoneNumber", editRow.phone || "");
} else { } else {
reset(); reset({ // ✅ Ensure default values are reset when adding a new manager
managerName: "",
stationName: "",
stationLocation: "",
phoneNumber: "",
});
} }
}, [editRow, setValue, reset]); }, [editRow, setValue, reset]);
const onSubmit = (data: FormData) => { const onSubmit = (data: FormData) => {
if (editRow) { if (editRow) {
handleUpdate(editRow.id, data); handleUpdate({
id: editRow.id,
managerName: data.managerName,
stationLocation: data.stationLocation,
phoneNumber: data.phoneNumber
});
} else { } else {
handleCreate(data); handleCreate(data);
} }
@ -68,6 +80,8 @@ const EditModal: React.FC<EditModalProps> = ({
reset(); reset();
}; };
return ( return (
<Modal open={open} onClose={handleClose} aria-labelledby="edit-manager-modal"> <Modal open={open} onClose={handleClose} aria-labelledby="edit-manager-modal">
<Box <Box
@ -100,8 +114,8 @@ const EditModal: React.FC<EditModalProps> = ({
{/* Input Fields */} {/* Input Fields */}
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}> <Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
{/* First Row - Two Inputs */}
<Box sx={{ display: "flex", gap: 2 }}> <Box sx={{ display: "flex", gap: 2 }}>
{/* Manager Name */}
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}> <Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
<Typography variant="body2" fontWeight={500} mb={0.5}>Manager Name</Typography> <Typography variant="body2" fontWeight={500} mb={0.5}>Manager Name</Typography>
<Controller <Controller
@ -114,6 +128,7 @@ const EditModal: React.FC<EditModalProps> = ({
/> />
</Box> </Box>
{/* Station Name */}
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}> <Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
<Typography variant="body2" fontWeight={500} mb={0.5}>Station Name</Typography> <Typography variant="body2" fontWeight={500} mb={0.5}>Station Name</Typography>
<Controller <Controller
@ -127,8 +142,8 @@ const EditModal: React.FC<EditModalProps> = ({
</Box> </Box>
</Box> </Box>
{/* Second Row - Two Inputs */}
<Box sx={{ display: "flex", gap: 2 }}> <Box sx={{ display: "flex", gap: 2 }}>
{/* Station Location */}
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}> <Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
<Typography variant="body2" fontWeight={500} mb={0.5}>Station Location</Typography> <Typography variant="body2" fontWeight={500} mb={0.5}>Station Location</Typography>
<Controller <Controller
@ -141,6 +156,7 @@ const EditModal: React.FC<EditModalProps> = ({
/> />
</Box> </Box>
{/* Phone Number */}
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}> <Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
<Typography variant="body2" fontWeight={500} mb={0.5}>Phone Number</Typography> <Typography variant="body2" fontWeight={500} mb={0.5}>Phone Number</Typography>
<Controller <Controller

View file

@ -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 { Box, Button, Typography, TextField, InputAdornment, IconButton } from "@mui/material";
import SearchIcon from "@mui/icons-material/Search"; import SearchIcon from "@mui/icons-material/Search";
import EqualizerIcon from "@mui/icons-material/Tune"; import EqualizerIcon from "@mui/icons-material/Tune";
import CustomTable, { Column } from "../../components/CustomTable"; import CustomTable, { Column } from "../../components/CustomTable";
import AddManagerModal from "../../pages/AddManagerModal"; import AddManagerModal from "../../pages/AddManagerModal";
import EditUserModal from "../EditUserModal"; 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() { export default function ManagerList() {
const dispatch = useDispatch<AppDispatch>();
const { managers, isLoading } = useSelector((state: RootState) => state.managerReducer);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const [addButtonModal, setAddButtonModal] = useState(false); const [addButtonModal, setAddButtonModal] = useState(false);
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false);
const [rowData, setRowData] = useState(null); const [rowData, setRowData] = useState(null);
const [viewModal, setViewModal] = React.useState<boolean>(false); const [viewModal, setViewModal] = useState(false);
const [deleteModal, setDeleteModal] = React.useState<boolean>(false); const [deleteModal, setDeleteModal] = useState(false);
useEffect(() => {
dispatch(fetchManagerList()); // Fetch data when component mounts
}, [dispatch]);
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 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
};
// 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<HTMLInputElement>) => {
setSearch(event.target.value); setSearch(event.target.value);
}; };
// Open the Add Manager Modal
const handleClickOpen = () => { const handleClickOpen = () => {
setRowData(null); setRowData(null);
setAddButtonModal(true); setAddButtonModal(true);
}; };
// Close all modals
const handleCloseModal = () => { const handleCloseModal = () => {
setAddButtonModal(false); setAddButtonModal(false);
setModalOpen(false); setModalOpen(false);
setRowData(null); setRowData(null);
}; };
const handleUpdate = (id, updatedData) => { // Table columns definition
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[] = [ const managerColumns: Column[] = [
{ id: "srno", label: "Sr No" }, { id: "name", label: "Manager Name" },
{ id: "managerName", label: "Manager Name" }, { id: "registeredAddress", label: "Station Location" },
{ id: "stationName", label: "Station Name" }, { id: "phone", label: "Phone Number" },
{ id: "stationLocation", label: "Station Location" },
{ id: "phoneNumber", label: "Phone Number" },
{ id: "action", label: "Action", align: "center" }, { id: "action", label: "Action", align: "center" },
]; ];
// Filtered manager list based on search input
const filteredManagers = managers.filter((manager) => const filteredManagers = managers.filter((manager) =>
Object.values(manager).some((value) => Object.values(manager).some((value) =>
typeof value === "string" && value.toLowerCase().includes(search.toLowerCase()) typeof value === "string" && value.toLowerCase().includes(search.toLowerCase())
@ -68,6 +146,7 @@ export default function ManagerList() {
return ( return (
<> <>
{/* Header Section */}
<Box sx={{ width: "100%", display: "flex", flexDirection: "column", gap: 2, mt: 2 }}> <Box sx={{ width: "100%", display: "flex", flexDirection: "column", gap: 2, mt: 2 }}>
<Typography component="h2" variant="h6" sx={{ fontWeight: 600 }}> <Typography component="h2" variant="h6" sx={{ fontWeight: 600 }}>
Managers Managers
@ -98,34 +177,64 @@ export default function ManagerList() {
</Box> </Box>
</Box> </Box>
<CustomTable {/* Table Section */}
columns={managerColumns} {isLoading ? (
rows={filteredManagers} <Typography>Loading managers...</Typography>
setRowData={setRowData} ) : (
setModalOpen={setModalOpen} <CustomTable
setViewModal={setViewModal} columns={managerColumns}
viewModal={viewModal} rows={filteredManagers.map((manager) => ({
setDeleteModal={setDeleteModal} id: manager.id,
deleteModal={deleteModal} name: manager.name,
registeredAddress: manager.registeredAddress,
phone: manager.phone,
action: (
<Box sx={{ display: "flex", gap: 1 }}>
<Button onClick={() => { setRowData(manager); setModalOpen(true); }}>Edit</Button>
<Button onClick={() => { setRowData(manager); setViewModal(true); }}>View</Button>
<Button color="error" onClick={() => { setRowData(manager); setDeleteModal(true); }}>
Delete
</Button>
</Box>
),
}))}
tableType="managers"
setRowData={setRowData}
setModalOpen={setModalOpen}
setViewModal={setViewModal}
viewModal={viewModal}
setDeleteModal={setDeleteModal}
deleteModal={deleteModal}
handleDeleteButton={handleDeleteManager}
/>
)}
/> {/* Modals */}
<EditUserModal <EditUserModal
open={modalOpen} open={modalOpen}
handleClose={handleCloseModal}
handleUpdate={handleUpdate}
editRow={rowData}
/>
<AddManagerModal
open={addButtonModal}
handleClose={handleCloseModal} handleClose={handleCloseModal}
initialData={rowData} editRow={rowData}
handleAddManager={handleAddManager} handleUpdate={handleUpdateManager} // Pass function
/> />
<AddManagerModal
open={addButtonModal}
handleClose={handleCloseModal}
handleAddManager={(data) =>
handleAddManager({
name: data.managerName,
registeredAddress: data.stationLocation,
phone: data.phoneNumber,
email: data.email,
password: data.password,
})
}
/>
</> </>
); );
} }

View file

@ -6,6 +6,7 @@ import profileReducer from "./slices/profileSlice";
import userReducer from "./slices/userSlice.ts"; import userReducer from "./slices/userSlice.ts";
import roleReducer from "./slices/roleSlice.ts"; import roleReducer from "./slices/roleSlice.ts";
import vehicleReducer from "./slices/VehicleSlice.ts"; import vehicleReducer from "./slices/VehicleSlice.ts";
import managerReducer from "../redux/slices/managerSlice.ts";
const rootReducer = combineReducers({ const rootReducer = combineReducers({
@ -14,7 +15,9 @@ const rootReducer = combineReducers({
profileReducer, profileReducer,
userReducer, userReducer,
roleReducer, roleReducer,
vehicleReducer vehicleReducer,
managerReducer,
// Add other reducers here...
}); });
export type RootState = ReturnType<typeof rootReducer>; export type RootState = ReturnType<typeof rootReducer>;

View file

@ -1,103 +1,189 @@
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit"; import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
import http from "../../lib/https";
import { toast } from "sonner"; import { toast } from "sonner";
// Interfaces // Define TypeScript types
interface Manager { interface Manager {
id: string; id: string;
name: string; name: string;
email: string; email: string;
role: string; phone?: string;
phone: string; role: string;
registeredAddress: string;
} }
interface ManagerState { interface ManagerState {
managers: Manager[]; managers: Manager[];
isLoading: boolean; loading: boolean;
error: string | null;
} }
// Dummy API function placeholder // Initial state
const dummyApiCall = async (response: any, delay = 500) => const initialState: ManagerState = {
new Promise((resolve) => setTimeout(() => resolve(response), delay)); managers: [],
loading: false,
error: null,
};
// Fetch Manager List // Fetch Manager List
export const fetchManagerList = createAsyncThunk<Manager[], void, { rejectValue: string }>( export const fetchManagerList = createAsyncThunk<Manager[], void, { rejectValue: string }>(
"fetchManagerList", "fetchManagers",
async (_, { rejectWithValue }) => { async (_, { rejectWithValue }) => {
try { try {
const response: any = await dummyApiCall({ data: [{ id: "1", name: "John Doe", email: "john@example.com", role: "Manager", phone: "1234567890", registeredAddress: "Somewhere" }] }); const response = await http.get("manager-list");
return response.data;
} catch (error: any) { if (!response.data?.data) throw new Error("Invalid API response");
toast.error("Error fetching manager list: " + error);
return rejectWithValue("An error occurred"); 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 // Delete Manager
export const deleteManager = createAsyncThunk<string, string, { rejectValue: string }>( export const deleteManager = createAsyncThunk<
"deleteManager", string,
async (id, { rejectWithValue }) => { string,
try { { rejectValue: string }
await dummyApiCall(null);initialState >(
"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 } // Create Slice
>("addManager", async (data, { rejectWithValue }) => { const managerSlice = createSlice({
try { name: "fetchManagers",
const response: any = await dummyApiCall({ id: "2", ...data }); initialState,
return response; reducers: {},
} catch (error: any) { extraReducers: (builder) => {
return rejectWithValue("An error occurred"); builder
} // Fetch Managers
.addCase(fetchManagerList.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(
fetchManagerList.fulfilled,
(state, action: PayloadAction<Manager[]>) => {
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<Manager>) => {
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<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; export default managerSlice.reducer;