bulk-email/src/components/CustomTable/customTable.tsx

884 lines
22 KiB
TypeScript

import * as React from "react";
import { styled } from "@mui/material/styles";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell, { tableCellClasses } from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import Paper from "@mui/material/Paper";
import {
Box,
Button,
InputAdornment,
Pagination,
TextField,
Typography,
IconButton,
Menu,
MenuItem,
} from "@mui/material";
import MoreVertIcon from "@mui/icons-material/MoreVert";
import VisibilityIcon from "@mui/icons-material/Visibility";
import EditIcon from "@mui/icons-material/Edit";
import DeleteIcon from "@mui/icons-material/Delete";
import SearchIcon from "@mui/icons-material/Search";
import ArrowUpwardIcon from "@mui/icons-material/ArrowUpward";
import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward";
import { useDispatch, useSelector } from "react-redux";
import { AppDispatch, RootState } from "../../redux/store/store.ts";
import DeleteModal from "../Modals/DeleteModal/index.tsx";
import ViewModal from "../Modals/ViewModal/index.tsx";
import VehicleViewModal from "../Modals/VehicleViewModal/index.tsx";
import ManagerViewModal from "../Modals/ViewManagerModal/";
import UserViewModal from "../Modals/UserViewModal/index.tsx";
import StationViewModal from "../Modals/StationViewModal/index.tsx";
import { adminList, deleteAdmin } from "../../redux/slices/adminSlice.ts";
import { vehicleList, deleteVehicle } from "../../redux/slices/VehicleSlice.ts";
import { deleteManager, managerList } from "../../redux/slices/managerSlice.ts";
import { deleteUser, userList } from "../../redux/slices/userSlice.ts";
import { deleteStation, stationList } from "../../redux/slices/stationSlice.ts";
import {
deleteSlot,
fetchAvailableSlots,
} from "../../redux/slices/slotSlice.ts";
import { bookingList, deleteBooking } from "../../redux/slices/bookSlice.ts";
import {
deleteStationDetails,
stationDetailList,
} from "../../redux/slices/managerStationSlice.ts";
import { CustomIconButton } from "../AddUserModal/styled.css.tsx";
import { autofillFix } from "../../shared-theme/customizations/autoFill.tsx";
// Styled Components
const StyledTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: {
backgroundColor: "#000000",
color: "#D0E1E9",
fontWeight: 600,
fontSize: "16px",
padding: "12px 16px",
borderBottom: "none",
position: "sticky",
top: 0,
zIndex: 10,
transition: "background-color 0.2s ease",
"&:hover": {
backgroundColor: "#2A2A2A",
cursor: "pointer",
},
"&.action-cell": {
right: 0,
zIndex: 11,
boxShadow: "-4px 0 8px rgba(0, 0, 0, 0.1)",
},
},
[`&.${tableCellClasses.body}`]: {
fontSize: "16px",
padding: "12px 16px",
borderBottom: "none",
color: "#333333",
transition: "background-color 0.2s ease",
fontWeight: 500,
"&.action-cell": {
position: "sticky",
right: 0,
zIndex: 2,
backgroundColor: "#DFECF1",
"&:hover": {
backgroundColor: "#D0E1E9",
},
},
},
}));
const StyledTableRow = styled(TableRow)(({ theme }) => ({
backgroundColor: "#DFECF1",
transition: "background-color 0.2s ease",
"&:hover": {
backgroundColor: "#D0E1E9",
},
// "& td, & th": {
// borderColor: "#454545",
// borderWidth: "1px",
// // borderBottom: "1px solid #454545",
// },
}));
const StyledTableContainer = styled(TableContainer)(({ theme }) => ({
borderRadius: "12px",
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.1)",
"&::-webkit-scrollbar": {
height: "6px",
},
"&::-webkit-scrollbar-track": {
background: "#D0E1E9",
},
"&::-webkit-scrollbar-thumb": {
background: "#000000",
borderRadius: "3px",
},
}));
// Interfaces
export interface Column {
id: string;
label: string;
align?: "left" | "center" | "right";
}
interface Row {
[key: string]: any;
}
interface CustomTableProps {
columns: Column[];
rows: Row[];
setDeleteModal: Function;
setRowData: Function;
setModalOpen: Function;
viewModal: boolean;
setViewModal: Function;
deleteModal: boolean;
handleStatusToggle?: (id: string, currentStatus: number | boolean) => void;
tableType: string;
handleClickOpen?: () => void;
statusField?: string;
statusValue?: boolean;
}
// Sorting State Interface
interface SortConfig {
key: string;
direction: "asc" | "desc" | null;
}
const CustomTable: React.FC<CustomTableProps> = ({
columns,
rows,
setDeleteModal,
deleteModal,
viewModal,
setRowData,
setViewModal,
setModalOpen,
handleStatusToggle,
tableType,
handleClickOpen,
}) => {
const dispatch = useDispatch<AppDispatch>();
const [selectedRow, setSelectedRow] = React.useState<Row | null>(null);
const [searchQuery, setSearchQuery] = React.useState("");
const [currentPage, setCurrentPage] = React.useState(1);
const [sortConfig, setSortConfig] = React.useState<SortConfig>({
key: "",
direction: null,
});
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [menuRow, setMenuRow] = React.useState<Row | null>(null);
const usersPerPage = 10;
const { user } = useSelector((state: RootState) => state?.profileReducer);
// Helper Functions
const getTitleByType = (type: string) => {
const titles: { [key: string]: string } = {
admin: "Admins",
role: "Roles",
user: "Users",
manager: "Managers",
"all-managers": "Managers",
vehicle: "Vehicles",
station: "Charging Stations",
"external-station": "Charging Stations",
booking: "Bookings",
slots: "Slots",
"all-available-slots": "Available Slots",
"manager-station": "Station Details",
};
return titles[type] || "List";
};
const getAddButtonLabel = (type: string) => {
const labels: { [key: string]: string } = {
admin: "Admin",
role: "Role",
user: "User",
manager: "Manager",
vehicle: "Vehicle",
station: "Charging Station",
booking: "Booking",
slots: "Slot",
"external-station": "Location",
"manager-station": "Station Details",
};
return labels[type] || "Item";
};
const getEmptyMessage = (type: string) => {
const messages: { [key: string]: string } = {
admin: "No admins found",
role: "No roles found",
user: "No users found",
manager: "No managers found",
vehicle: "No vehicles found",
station: "No charging stations found",
"external-station": "No charging stations found",
booking: "No bookings found",
slots: "No slots found",
"all-available-slots": "No available slots found",
};
return messages[type] || "No data available";
};
const shouldShowAddButton = (userType: string, tableType: string) => {
if (
(userType === "user" && tableType === "all-available-slots") ||
(userType === "superadmin" && tableType === "all-managers") ||
(userType === "superadmin" && tableType === "user")
) {
return false;
}
return true;
};
const isImage = (value: any) => {
if (typeof value === "string") {
return value.startsWith("http") || value.startsWith("data:image");
}
return false;
};
// Sorting Logic
const handleSort = (key: string) => {
setSortConfig((prev) => {
if (prev.key === key) {
if (prev.direction === "asc") return { key, direction: "desc" };
if (prev.direction === "desc") return { key, direction: null };
return { key, direction: "asc" };
}
return { key, direction: "asc" };
});
};
const sortedRows = React.useMemo(() => {
if (!sortConfig.key || !sortConfig.direction) return [...rows];
return [...rows].sort((a, b) => {
const aValue = a[sortConfig.key];
const bValue = b[sortConfig.key];
if (aValue == null && bValue == null) return 0;
if (aValue == null) return sortConfig.direction === "asc" ? 1 : -1;
if (bValue == null) return sortConfig.direction === "asc" ? -1 : 1;
if (typeof aValue === "number" && typeof bValue === "number") {
return sortConfig.direction === "asc"
? aValue - bValue
: bValue - aValue;
}
const aStr = String(aValue).toLowerCase();
const bStr = String(bValue).toLowerCase();
return sortConfig.direction === "asc"
? aStr.localeCompare(bStr)
: bStr.localeCompare(aStr);
});
}, [rows, sortConfig]);
// Filtering Logic
const filteredRows = sortedRows.filter((row) => {
if (!searchQuery.trim()) return true;
const lowerCaseQuery = searchQuery.toLowerCase().trim();
return (
(row.name && row.name.toLowerCase().includes(lowerCaseQuery)) ||
(row.registeredAddress &&
row.registeredAddress.toLowerCase().includes(lowerCaseQuery)) ||
(row.stationName &&
row.stationName.toLowerCase().includes(lowerCaseQuery))
);
});
// Pagination Logic
const indexOfLastRow = currentPage * usersPerPage;
const indexOfFirstRow = indexOfLastRow - usersPerPage;
const currentRows = filteredRows.slice(indexOfFirstRow, indexOfLastRow);
const handlePageChange = (
_event: React.ChangeEvent<unknown>,
value: number
) => {
setCurrentPage(value);
};
// Action Handlers
const handleDeleteButton = (id: string | undefined) => {
if (!id) {
console.error("ID not found", id);
return;
}
switch (tableType) {
case "admin":
dispatch(deleteAdmin(id));
break;
case "vehicle":
dispatch(deleteVehicle(id));
break;
case "manager":
dispatch(deleteManager(id));
break;
case "user":
dispatch(deleteUser(id));
break;
case "station":
dispatch(deleteStation(id));
break;
case "slots":
dispatch(deleteSlot(id));
break;
case "booking":
dispatch(deleteBooking(id));
break;
case "manager-station":
dispatch(deleteStationDetails(id));
break;
default:
console.error("Unknown table type:", tableType);
return;
}
setDeleteModal(false);
};
const handleViewButton = (id: string | undefined) => {
if (!id) console.error("ID not found", id);
switch (tableType) {
case "admin":
dispatch(adminList());
break;
case "vehicle":
dispatch(vehicleList());
break;
case "manager":
dispatch(managerList());
break;
case "user":
dispatch(userList());
break;
case "booking":
dispatch(bookingList());
break;
case "slots":
dispatch(fetchAvailableSlots());
break;
case "station":
dispatch(stationList());
break;
case "manager-station":
dispatch(stationDetailList());
break;
default:
console.error("Unknown table type:", tableType);
return;
}
setViewModal(false);
};
const handleEditButton = (row: Row) => {
setModalOpen(true);
setRowData(row);
};
// Menu Handlers
const handleMenuOpen = (event: React.MouseEvent<HTMLElement>, row: Row) => {
setAnchorEl(event.currentTarget);
setMenuRow(row);
};
const handleMenuClose = () => {
setAnchorEl(null);
setMenuRow(null);
};
const handleMenuAction = (action: string) => {
if (!menuRow) return;
switch (action) {
case "view":
setSelectedRow(menuRow);
setViewModal(true);
break;
case "edit":
handleEditButton(menuRow);
break;
case "delete":
setDeleteModal(true);
setSelectedRow(menuRow);
break;
default:
break;
}
handleMenuClose();
};
return (
<Box
sx={{
width: "100%",
padding: "24px",
backgroundColor: "#D0E1E9",
borderRadius: "12px",
}}
>
{/* Header Section */}
<Box
sx={{
display: "flex",
flexDirection: { xs: "column", md: "row" },
justifyContent: "space-between",
alignItems: { xs: "flex-start", md: "center" },
flexWrap: "wrap",
mb: 3,
gap: 2,
}}
>
<Typography
sx={{
fontWeight: 600,
fontSize: "30px",
letterSpacing: "-0.5px",
}}
>
{getTitleByType(tableType)}
</Typography>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: 2,
flexWrap: "wrap",
}}
>
<TextField
variant="outlined"
placeholder="Search"
sx={{
width: { xs: "100%", sm: "300px" },
...autofillFix,
"& .MuiOutlinedInput-root": {
borderRadius: "8px",
height: "40px",
backgroundColor: "#FFFFFF",
"& fieldset": { borderColor: "#D1D5DB" },
"&:hover fieldset": { borderColor: "#9CA3AF" },
"&.Mui-focused fieldset": {
borderColor: "#3B82F6",
},
},
"& .MuiInputBase-input": {
fontSize: "14px",
color: "#1A1A1A",
},
}}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon sx={{ color: "#6B7280" }} />
</InputAdornment>
),
}}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
{shouldShowAddButton(user?.userType, tableType) && (
<Button
sx={{
backgroundColor: "#000000",
color: "#FFFFFF",
fontWeight: 600,
fontSize: "16px",
padding: "8px 16px",
borderRadius: "8px",
textTransform: "none",
"&:hover": {
backgroundColor: "#454545",
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)",
},
transition: "all 0.2s ease",
}}
onClick={handleClickOpen}
>
Add {getAddButtonLabel(tableType)}
</Button>
)}
</Box>
</Box>
{/* Table Section */}
<StyledTableContainer>
<Table sx={{ minWidth: "750px", tableLayout: "auto" }}>
<TableHead>
<TableRow>
{columns.map((column) => (
<StyledTableCell
key={column.id}
align={column.align}
onClick={
column.id !== "action"
? () => handleSort(column.id)
: undefined
}
sx={{
cursor:
column.id !== "action"
? "pointer"
: "default",
}}
className={
column.id === "action"
? "action-cell"
: undefined
} // Add action-cell class
>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: 1,
}}
>
{column.label}
{column.id !== "action" &&
sortConfig.key === column.id &&
sortConfig.direction &&
(sortConfig.direction === "asc" ? (
<ArrowUpwardIcon
sx={{
fontSize: "16px",
color: "#FFFFFF",
}}
/>
) : (
<ArrowDownwardIcon
sx={{
fontSize: "16px",
color: "#FFFFFF",
}}
/>
))}
</Box>
</StyledTableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{currentRows.length === 0 ? (
<StyledTableRow>
<StyledTableCell
colSpan={columns.length}
sx={{ textAlign: "center", py: 4 }}
>
<Typography
sx={{
color: "#6B7280",
fontSize: "16px",
}}
>
{getEmptyMessage(tableType)}
</Typography>
</StyledTableCell>
</StyledTableRow>
) : (
currentRows.map((row, rowIndex) => (
<StyledTableRow key={rowIndex}>
{columns.map((column) => (
<StyledTableCell
key={column.id}
align={column.align}
className={
column.id === "action"
? "action-cell"
: undefined
} // Add action-cell class
>
{column.id === "action" ? (
<Box>
<CustomIconButton
onClick={(e) =>
handleMenuOpen(
e,
row
)
}
sx={{
color: "#6B7280",
"&:hover": {
color: "#454545",
},
transition:
"color 0.2s ease",
}}
>
<MoreVertIcon />
</CustomIconButton>
<Menu
anchorEl={anchorEl}
open={
Boolean(anchorEl) &&
menuRow === row
}
onClose={
handleMenuClose
}
anchorOrigin={{
vertical: "top",
horizontal: "right",
}}
transformOrigin={{
vertical: "top",
horizontal: "right",
}}
PaperProps={{
sx: {
borderRadius:
"8px",
boxShadow:
"0 2px 8px rgba(0, 0, 0, 0.15)",
},
}}
>
{user?.userType !==
"manager" &&
user?.userType !==
"user" && (
<MenuItem
onClick={() =>
handleMenuAction(
"view"
)
}
sx={{
color: "#000000",
fontSize:
"16px",
display:
"flex",
alignItems:
"center",
gap: 1,
}}
>
<VisibilityIcon
sx={{
fontSize:
"18px",
color: "#454545",
}}
/>
View
</MenuItem>
)}
{ user?.userType !==
"user" && (
<MenuItem
onClick={() =>
handleMenuAction(
"edit"
)
}
sx={{
color: "#000000",
fontSize:
"16px",
display: "flex",
alignItems:
"center",
gap: 1,
}}
>
<EditIcon
sx={{
fontSize:
"18px",
color: "#454545",
}}
/>
Edit
</MenuItem>)}
<MenuItem
onClick={() =>
handleMenuAction(
"delete"
)
}
sx={{
color: "#000000",
fontSize:
"14px",
display: "flex",
alignItems:
"center",
gap: 1,
}}
>
<DeleteIcon
sx={{
fontSize:
"18px",
color: "#454545",
}}
/>
Delete
</MenuItem>
</Menu>
</Box>
) : row.statusField &&
column.id === row.statusField ? (
<Button
variant="outlined"
onClick={(e) => {
e.stopPropagation();
setSelectedRow(row);
handleStatusToggle?.(
row.id,
!row.statusValue
);
}}
sx={{
fontSize: "12px",
fontWeight: 600,
borderRadius: "6px",
color: row.statusValue
? "#10B981"
: "#EF4444",
borderColor:
row.statusValue
? "#10B981"
: "#EF4444",
padding: "4px 12px",
"&:hover": {
backgroundColor:
row.statusValue
? "rgba(16, 185, 129, 0.1)"
: "rgba(239, 68, 68, 0.1)",
},
"&::before": {
content: '""',
display:
"inline-block",
width: "8px",
height: "8px",
borderRadius: "50%",
marginRight: "8px",
backgroundColor:
row.statusValue
? "#10B981"
: "#EF4444",
},
}}
>
{row.statusValue
? "Available"
: "Not Available"}
</Button>
) : isImage(row[column.id]) ? (
<img
src={row[column.id]}
alt="Row"
style={{
width: "40px",
height: "40px",
borderRadius: "50%",
objectFit: "cover",
}}
/>
) : (
row[column.id]
)}
</StyledTableCell>
))}
</StyledTableRow>
))
)}
</TableBody>
</Table>
</StyledTableContainer>
{/* Pagination */}
<Box sx={{ display: "flex", justifyContent: "flex-end", mt: 3 }}>
<Pagination
count={Math.ceil(filteredRows.length / usersPerPage)}
page={currentPage}
onChange={handlePageChange}
sx={{
"& .MuiPaginationItem-root": {
color: "#1A1A1A",
fontSize: "16px",
},
"& .Mui-selected": {
backgroundColor: "#000000",
color: "#FFFFFF",
"&:hover": {
backgroundColor: "#000000",
},
},
}}
/>
</Box>
{/* Modals */}
{viewModal && tableType === "admin" && (
<ViewModal
handleView={() => handleViewButton(selectedRow?.id)}
open={viewModal}
setViewModal={setViewModal}
id={selectedRow?.id}
/>
)}
{viewModal && tableType === "manager" && (
<ManagerViewModal
handleView={() => handleViewButton(selectedRow?.id)}
open={viewModal}
setViewModal={setViewModal}
id={selectedRow?.id}
/>
)}
{viewModal && tableType === "vehicle" && (
<VehicleViewModal
handleView={() => handleViewButton(selectedRow?.id)}
open={viewModal}
setViewModal={setViewModal}
id={selectedRow?.id}
/>
)}
{viewModal && tableType === "station" && (
<StationViewModal
handleView={() => handleViewButton(selectedRow?.id)}
open={viewModal}
setViewModal={setViewModal}
id={selectedRow?.id}
/>
)}
{viewModal && tableType === "user" && (
<UserViewModal
handleView={() => handleViewButton(selectedRow?.id)}
open={viewModal}
setViewModal={setViewModal}
id={selectedRow?.id}
/>
)}
{deleteModal && (
<DeleteModal
handleDelete={() => handleDeleteButton(selectedRow?.id)}
open={deleteModal}
setDeleteModal={setDeleteModal}
id={selectedRow?.id}
/>
)}
</Box>
);
};
export default CustomTable;