dev-jaanvi #1

Open
jaanvi wants to merge 155 commits from dev-jaanvi into main
13 changed files with 904 additions and 32 deletions
Showing only changes of commit f9cda402aa - Show all commits

View file

@ -0,0 +1,162 @@
import React, { useState } from "react";
import {
Box,
Button,
Typography,
Modal,
TextField,
FormControlLabel,
Switch,
} from "@mui/material";
import { useForm } from "react-hook-form";
import { useDispatch } from "react-redux";
import { addStationDetails } from "../../redux/slices/managerStationSlice"; // Updated import
import CloseIcon from "@mui/icons-material/Close";
import { CustomIconButton } from "../AddUserModal/styled.css";
const AddManagerStationModal = ({ open, handleClose }: any) => {
const dispatch = useDispatch();
const {
register,
handleSubmit,
reset,
formState: { errors },
} = useForm();
const [isAvailable, setIsAvailable] = useState<boolean>(true);
const onSubmit = (data: any) => {
const { connectorType, power, price } = data;
const payload = {
connectorType,
power,
price,
isAvailable,
};
dispatch(addStationDetails(payload));
reset();
handleClose();
};
return (
<Modal
open={open}
onClose={(e, reason) => {
if (reason === "backdropClick") return;
handleClose();
}}
aria-labelledby="add-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} fontSize={"16px"}>
Add Manager Station
</Typography>
<CustomIconButton onClick={handleClose}>
<CloseIcon />
</CustomIconButton>
</Box>
{/* Divider */}
<Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
{/* Connector Type */}
<Typography variant="body2" fontWeight={500} mb={0.5}>
Connector Type
</Typography>
<TextField
{...register("connectorType", {
required: "Connector Type is required",
})}
fullWidth
error={!!errors.connectorType}
helperText={errors.connectorType?.message}
/>
{/* Power */}
<Typography variant="body2" fontWeight={500} mb={0.5}>
Power (kW)
</Typography>
<TextField
{...register("power", { required: "Power is required" })}
fullWidth
error={!!errors.power}
helperText={errors.power?.message}
/>
{/* Price */}
<Typography variant="body2" fontWeight={500} mb={0.5}>
Price ()
</Typography>
<TextField
{...register("price", {
required: "Price is required",
pattern: {
value: /^[0-9]+$/,
message: "Price must be a number",
},
})}
fullWidth
error={!!errors.price}
helperText={errors.price?.message}
/>
{/* Availability Switch */}
<FormControlLabel
control={
<Switch
checked={isAvailable}
onChange={() => setIsAvailable((prev) => !prev)}
/>
}
label={isAvailable ? "Available" : "Not Available"}
sx={{ mt: 2 }}
/>
{/* Submit */}
<Box
sx={{ display: "flex", justifyContent: "flex-end", mt: 3 }}
>
<Button
type="submit"
sx={{
backgroundColor: "#52ACDF",
color: "white",
borderRadius: "8px",
fontSize: "16px",
width: "117px",
"&:hover": { backgroundColor: "#439BC1" },
}}
>
Add Station
</Button>
</Box>
</Box>
</Modal>
);
};
export default AddManagerStationModal;

View file

@ -43,16 +43,29 @@ import {
import { bookingList, deleteBooking } from "../../redux/slices/bookSlice.ts"; import { bookingList, deleteBooking } from "../../redux/slices/bookSlice.ts";
import AddCircleIcon from "@mui/icons-material/AddCircle"; import AddCircleIcon from "@mui/icons-material/AddCircle";
import { autofillFix } from "../../shared-theme/customizations/autoFill.tsx"; import { autofillFix } from "../../shared-theme/customizations/autoFill.tsx";
import ArrowUpwardIcon from "@mui/icons-material/ArrowUpward";
import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward";
import { deleteStationDetails, stationDetailList } from "../../redux/slices/managerStationSlice.ts";
import ManagerStationDetails from "../../pages/ManagerStationDetails/index.tsx";
// Styled components for customization // Styled components for customization
const StyledTableCell = styled(TableCell)(({ theme }) => ({ const StyledTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: {
backgroundColor: "#454545", // Changed to #272727 for the header
color: theme.palette.common.white,
borderBottom: "none", // Remove any border at the bottom of the header
},
[`&.${tableCellClasses.body}`]: { [`&.${tableCellClasses.body}`]: {
fontSize: "16px", fontSize: "16px",
borderBottom: "1px solid #454545", // Adding border to body cells borderBottom: "1px solid #454545",
},
[`&.${tableCellClasses.head}`]: {
backgroundColor: "#454545",
color: theme.palette.common.white,
borderBottom: "none",
borderRight: "1px solid rgba(141, 135, 135, 0.51)",
// transition: "border-color 0.3s ease",
position: "sticky",
top: 0,
zIndex: 10,
// "&:hover": {
// borderRight: "3px solid rgba(112, 109, 109, 0.8)",
// },p
}, },
})); }));
@ -116,6 +129,9 @@ const CustomTable: React.FC<CustomTableProps> = ({
const usersPerPage = 10; const usersPerPage = 10;
const { user } = useSelector((state: RootState) => state?.profileReducer); const { user } = useSelector((state: RootState) => state?.profileReducer);
const open = Boolean(anchorEl); const open = Boolean(anchorEl);
const [sortOrder, setSortOrder] = React.useState<"asc" | "desc" | null>(
null
);
const handleClick = (event: React.MouseEvent<HTMLElement>, row: Row) => { const handleClick = (event: React.MouseEvent<HTMLElement>, row: Row) => {
setAnchorEl(event.currentTarget); setAnchorEl(event.currentTarget);
setSelectedRow(row); setSelectedRow(row);
@ -161,6 +177,10 @@ const CustomTable: React.FC<CustomTableProps> = ({
case "booking": case "booking":
dispatch(deleteBooking(id || "")); dispatch(deleteBooking(id || ""));
break; break;
case "manager-station":
dispatch(deleteStationDetails(id || ""));
break;
default: default:
console.error("Unknown table type:", tableType); console.error("Unknown table type:", tableType);
return; return;
@ -193,6 +213,9 @@ const CustomTable: React.FC<CustomTableProps> = ({
case "station": case "station":
dispatch(stationList()); dispatch(stationList());
break; break;
case "manager-station":
dispatch(stationDetailList());
break;
default: default:
console.error("Unknown table type:", tableType); console.error("Unknown table type:", tableType);
return; return;
@ -210,15 +233,37 @@ const CustomTable: React.FC<CustomTableProps> = ({
handleClose(); handleClose();
}; };
const filteredRows = rows.filter((row) => { const handleSort = () => {
if (!searchQuery.trim()) return true; // Return all rows if searchQuery is empty or whitespace setSortOrder((prevSortOrder) =>
const lowerCaseQuery = searchQuery.toLowerCase().trim(); prevSortOrder === "asc" ? "desc" : "asc"
return (
(row.name && row.name.toLowerCase().includes(lowerCaseQuery)) ||
(row.registeredAddress &&
row.registeredAddress.toLowerCase().includes(lowerCaseQuery))
); );
}); };
const sortedRows = React.useMemo(() => {
let sorted = [...rows];
if (sortOrder) {
sorted.sort((a, b) => {
if (a.name < b.name) return sortOrder === "asc" ? -1 : 1;
if (a.name > b.name) return sortOrder === "asc" ? 1 : -1;
return 0;
});
}
return sorted;
}, [rows, sortOrder]);
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))
);
});
const indexOfLastRow = currentPage * usersPerPage; const indexOfLastRow = currentPage * usersPerPage;
const indexOfFirstRow = indexOfLastRow - usersPerPage; const indexOfFirstRow = indexOfLastRow - usersPerPage;
@ -278,6 +323,8 @@ const CustomTable: React.FC<CustomTableProps> = ({
return "Slots"; return "Slots";
case "all-available-slots": case "all-available-slots":
return "Available Slots"; return "Available Slots";
case "manager-station":
return "Station Details"
default: default:
return "List"; return "List";
} }
@ -293,7 +340,7 @@ const CustomTable: React.FC<CustomTableProps> = ({
marginTop: "16px", marginTop: "16px",
alignItems: { xs: "stretch", sm: "stretch", md: "center" }, alignItems: { xs: "stretch", sm: "stretch", md: "center" },
width: "100%", width: "100%",
...autofillFix ...autofillFix,
}} }}
> >
<TextField <TextField
@ -358,7 +405,7 @@ const CustomTable: React.FC<CustomTableProps> = ({
maxWidth: "250px", // Optional: limit it from being *too* wide maxWidth: "250px", // Optional: limit it from being *too* wide
marginRight: "16px", marginRight: "16px",
paddingX: "16px", paddingX: "16px",
fontSize:"16px", fontSize: "16px",
whiteSpace: "nowrap", // Prevents text from wrapping whiteSpace: "nowrap", // Prevents text from wrapping
"&:hover": { backgroundColor: "#439BC1" }, "&:hover": { backgroundColor: "#439BC1" },
}} }}
@ -386,6 +433,8 @@ const CustomTable: React.FC<CustomTableProps> = ({
return "Slot"; return "Slot";
case "external-station": case "external-station":
return "Location"; return "Location";
case "manager-station":
return "Station Details"
default: default:
return "Item"; return "Item";
} }
@ -430,6 +479,7 @@ const CustomTable: React.FC<CustomTableProps> = ({
borderRadius: "4px", borderRadius: "4px",
}, },
}} }}
> >
<Table sx={{ minWidth: "750px", tableLayout: "auto" }}> <Table sx={{ minWidth: "750px", tableLayout: "auto" }}>
<TableHead <TableHead
@ -442,6 +492,7 @@ const CustomTable: React.FC<CustomTableProps> = ({
borderBottom: "1px solid #454545", borderBottom: "1px solid #454545",
}, },
}} }}
> >
{" "} {" "}
<TableRow> <TableRow>
@ -464,6 +515,34 @@ const CustomTable: React.FC<CustomTableProps> = ({
}} }}
> >
{column.label} {column.label}
{column.id === "name" && (
<CustomIconButton
onClick={handleSort}
sx={{
marginLeft: "8px",
opacity: 0.4, // Initial transparency
transition:
"0px 0px 0px rgba(255, 255, 255, 0)",
"&:hover": {
opacity: 1,
},
"&:active": {
opacity: 1,
},
}}
>
{sortOrder === "asc" ? (
<ArrowUpwardIcon
sx={{ color: "#FFFFFF" }}
/>
) : (
<ArrowDownwardIcon
sx={{ color: "#FFFFFF" }}
/>
)}
</CustomIconButton>
)}
</StyledTableCell> </StyledTableCell>
))} ))}
</TableRow> </TableRow>
@ -613,7 +692,7 @@ const CustomTable: React.FC<CustomTableProps> = ({
"& .MuiPaginationItem-page.Mui-selected": { "& .MuiPaginationItem-page.Mui-selected": {
backgroundColor: "transparent", backgroundColor: "transparent",
fontWeight: "bold", fontWeight: "bold",
fontSize:"16px", fontSize: "16px",
color: "#FFFFFF", color: "#FFFFFF",
}, },
}} }}

View file

@ -0,0 +1,242 @@
import React, { useEffect, useState } from "react";
import {
Box,
Button,
Typography,
Modal,
CircularProgress,
} from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { useForm, Controller } from "react-hook-form";
import { useDispatch } from "react-redux";
import {
updateSlot,
fetchManagersSlots,
} from "../../redux/slices/slotSlice.ts"; // Update with correct action
import { CustomIconButton, CustomTextField } from "../AddUserModal/styled.css"; // Custom styled components
import { AppDispatch } from "../../redux/store/store.ts";
// Defining props for the modal
interface EditManagerStationModalProp {
open: boolean;
handleClose: () => void;
editRow: any; // Slot data including id
}
interface FormData {
startTime: string;
endTime: string;
isAvailable: boolean;
}
const EditSlotModal: React.FC<EditManagerStationModalProp> = ({
open,
handleClose,
editRow,
}) => {
const dispatch = useDispatch<AppDispatch>();
const {
control,
handleSubmit,
formState: { errors },
setValue,
reset,
} = useForm<FormData>({
defaultValues: {
startTime: "",
endTime: "",
isAvailable: false,
},
});
const [loading, setLoading] = useState(false);
const [isAvailable, setIsAvailable] = useState<boolean>(
editRow?.isAvailable || false
);
// Effect to prepopulate the form when the modal is opened
useEffect(() => {
if (editRow) {
setValue("startTime", editRow.startTime);
setValue("endTime", editRow.endTime);
setIsAvailable(editRow.isAvailable);
} else {
reset();
}
}, [editRow, setValue, reset]);
// Handle form submission
const onSubmit = async (data: FormData) => {
if (editRow) {
setLoading(true);
try {
const availabilityStatus = isAvailable ? true : false;
// Dispatching the update action to the Redux slice
await dispatch(
updateSlot({
id: editRow.id, // Slot ID from the editRow object
startTime: data.startTime,
endTime: data.endTime,
isAvailable: availabilityStatus,
})
).unwrap();
// Fetch the updated slots after updating the slot
dispatch(fetchManagersSlots());
// Close the modal after successful submission
handleClose();
reset(); // Reset the form
} catch (error) {
console.error("Error updating slot:", error);
// Handle error if needed (e.g., show toast notification)
} finally {
setLoading(false); // Stop loading state after completion
}
}
};
return (
<Modal
open={open}
onClose={(e, reason) => {
if (reason === "backdropClick") {
return;
}
handleClose(); // Close modal when clicking cross or cancel
}}
aria-labelledby="edit-slot-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} fontSize={"16px"}>
Edit Slot
</Typography>
<CustomIconButton onClick={handleClose}>
<CloseIcon />
</CustomIconButton>
</Box>
{/* Horizontal Line */}
<Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
{/* Input Fields */}
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 2 }}>
{/* Start Time */}
<Box sx={{ flex: "1 1 48%" }}>
<Typography variant="body2" fontWeight={500} mb={0.5}>
Start Time
</Typography>
<Controller
name="startTime"
control={control}
rules={{ required: "Start Time is required" }}
render={({ field }) => (
<CustomTextField
{...field}
type="time"
fullWidth
size="small"
error={!!errors.startTime}
helperText={errors.startTime?.message}
/>
)}
/>
</Box>
{/* End Time */}
<Box sx={{ flex: "1 1 48%" }}>
<Typography variant="body2" fontWeight={500} mb={0.5}>
End Time
</Typography>
<Controller
name="endTime"
control={control}
rules={{ required: "End Time is required" }}
render={({ field }) => (
<CustomTextField
{...field}
type="time"
fullWidth
size="small"
error={!!errors.endTime}
helperText={errors.endTime?.message}
/>
)}
/>
</Box>
{/* Availability Toggle */}
<Box
display="flex"
alignItems="center"
justifyContent="space-between"
gap={2}
sx={{ flex: "1 1 100%" }}
>
<Button
onClick={() => {
const newAvailability = !isAvailable;
setIsAvailable(newAvailability); // Update local state
setValue("isAvailable", newAvailability); // Update form value for isAvailable
}}
variant={isAvailable ? "contained" : "outlined"}
color="primary"
>
{isAvailable ? "Available" : "Not Available"}
</Button>
</Box>
</Box>
{/* Submit Button */}
<Box
sx={{ display: "flex", justifyContent: "flex-end", mt: 3 }}
>
<Button
type="submit"
sx={{
backgroundColor: "#52ACDF",
color: "white",
borderRadius: "8px",
fontSize: "16px",
width: "117px",
"&:hover": { backgroundColor: "#439BC1" },
}}
disabled={loading} // Disable the button during loading state
>
{loading ? (
<CircularProgress size={24} color="inherit" />
) : (
"Update Slot"
)}
</Button>
</Box>
</Box>
</Modal>
);
};
export default EditSlotModal;

View file

@ -59,6 +59,10 @@ export default function Header() {
boxSizing: "border-box", boxSizing: "border-box",
overflowX: "hidden", overflowX: "hidden",
flex: "0 0 84px", flex: "0 0 84px",
position: "fixed",
top: "0",
left: "0",
zIndex: 1000,
}} }}
> >
<Stack <Stack

View file

@ -86,7 +86,11 @@ export default function MenuContent({ hidden }: PropType) {
icon: <ChecklistSharpIcon />, icon: <ChecklistSharpIcon />,
url: "/panel/slot-list", // Placeholder for now url: "/panel/slot-list", // Placeholder for now
}, },
userRole === "manager" && {
text: "Station Details",
icon: <ChecklistSharpIcon />,
url: "/panel/manager-station-details", // Placeholder for now
},
userRole === "user" && { userRole === "user" && {
text: "Available Slots", text: "Available Slots",
icon: <ChecklistSharpIcon />, icon: <ChecklistSharpIcon />,

View file

@ -21,7 +21,7 @@ export default function EVSlotList() {
dispatch(fetchAvailableSlots()); dispatch(fetchAvailableSlots());
}, [dispatch]); }, [dispatch]);
console.log("first", availableSlots);
const slotColumns: Column[] = [ const slotColumns: Column[] = [
{ id: "srno", label: "Sr No" }, { id: "srno", label: "Sr No" },
@ -36,18 +36,33 @@ export default function EVSlotList() {
const slotRows = availableSlots?.length const slotRows = availableSlots?.length
? availableSlots.map((slot, index) => { ? availableSlots.map((slot, index) => {
const formattedDate = dayjs(slot?.date).format("YYYY-MM-DD"); // const formattedDate = dayjs(slot?.date).format("YYYY-MM-DD");
const startTime = dayjs(slot?.startTime).format("HH:mm"); // const startTime = dayjs(
const endTime = dayjs(slot?.endTime).format("HH:mm"); // slot?.startTime,
// "YYYY-MM-DD hh:mm A"
// ).isValid()
// ? dayjs(slot?.startTime, "YYYY-MM-DD hh:mm A").format(
// "hh:mm A"
// )
// : "Invalid";
// const endTime = dayjs(
// slot?.endTime,
// "YYYY-MM-DD hh:mm A"
// ).isValid()
// ? dayjs(slot?.endTime, "YYYY-MM-DD hh:mm A").format(
// "hh:mm A"
// )
// : "Invalid";
return { return {
srno: index + 1, srno: index + 1,
id: slot?.id ?? "NA", id: slot?.id ?? "NA",
stationId: slot?.stationId ?? "NA", stationId: slot?.stationId ?? "NA",
name: slot?.ChargingStation?.name ?? "NA", name: slot?.ChargingStation?.name ?? "NA",
date: formattedDate ?? "NA", date: slot?.date ?? "NA",
startTime: startTime ?? "NA", startTime: slot?.startTime ?? "NA",
endTime: endTime ?? "NA", endTime: slot?.endTime ?? "NA",
isAvailable: slot?.isAvailable ? "Yes" : "No", isAvailable: slot?.isAvailable ? "Yes" : "No",
}; };
}) })

View file

@ -164,7 +164,7 @@ export default function EVSlotList() {
const slotRows = availableSlots?.length const slotRows = availableSlots?.length
? availableSlots.map((slot, index) => { ? availableSlots.map((slot, index) => {
const formattedDate = dayjs(slot?.date).format("YYYY-MM-DD");
const startTime = dayjs( const startTime = dayjs(
slot?.startTime, slot?.startTime,
"YYYY-MM-DD hh:mm A" "YYYY-MM-DD hh:mm A"
@ -187,7 +187,7 @@ export default function EVSlotList() {
srno: index + 1, srno: index + 1,
id: slot?.id ?? "NA", id: slot?.id ?? "NA",
stationName: slot?.stationName ?? "NA", stationName: slot?.stationName ?? "NA",
date: formattedDate ?? "NA", date: slot?.date ?? "NA",
startTime: startTime ?? "NA", startTime: startTime ?? "NA",
endTime: endTime ?? "NA", endTime: endTime ?? "NA",
isAvailable: slot?.isAvailable ? "Yes" : "No", isAvailable: slot?.isAvailable ? "Yes" : "No",

View file

@ -0,0 +1,146 @@
import { useEffect, useState } from "react";
import CustomTable, { Column } from "../../components/CustomTable/customTable";
import AddManagerModal from "../../components/AddManagerModal/addManagerModal";
import EditManagerModal from "../../components/EditManagerModal/editManagerModal";
import { useDispatch, useSelector } from "react-redux";
import { RootState, AppDispatch } from "../../redux/store/store";
import {
managerList,
addManager,
updateManager,
} from "../../redux/slices/managerSlice";
import { useForm } from "react-hook-form";
import {
addStationDetails,
stationDetailList,
updateStationDetails,
} from "../../redux/slices/managerStationSlice";
import AddManagerStationModal from "../../components/AddManagerStationModal/addmanagerStationModal";
import EditManagerStationModal from "../../components/EditManagerStationModal/editManagerStationModal";
export default function ManagerStationDetails() {
const [addModalOpen, setAddModalOpen] = useState<boolean>(false);
const [editModalOpen, setEditModalOpen] = useState<boolean>(false);
const { reset } = useForm();
const [deleteModal, setDeleteModal] = useState<boolean>(false);
const [viewModal, setViewModal] = useState<boolean>(false);
const [rowData, setRowData] = useState<any | null>(null);
const dispatch = useDispatch<AppDispatch>();
const managerStationDetails = useSelector(
(state: RootState) => state.managerStationReducer.stationDetails
);
useEffect(() => {
dispatch(stationDetailList());
}, [dispatch]);
const handleClickOpen = () => {
setRowData(null);
setAddModalOpen(true);
};
const handleCloseModal = () => {
setAddModalOpen(false);
setEditModalOpen(false);
setRowData(null);
reset();
};
const handleAddManager = async (data: {
stationId: string;
connectorType: string;
power: string;
price: string;
}) => {
try {
// Add manager with stationId
await dispatch(addStationDetails(data)); // Dispatch action to add manager
await dispatch(stationDetailList()); // Fetch the updated list
handleCloseModal(); // Close the modal
} catch (error) {
console.error("Error adding manager", error);
}
};
// Handle updating an existing manager
const handleUpdate = async (
id: number,
connectorType: string,
power: string,
price: string
) => {
try {
await dispatch(
updateStationDetails({
id,
connectorType,
power,
price,
isAvailable: false,
})
);
await dispatch(stationDetailList());
handleCloseModal();
} catch (error) {
console.error("Update failed", error);
}
};
const categoryColumns: Column[] = [
{ id: "srno", label: "Sr No" },
{ id: "stationName", label: "Station Name" },
{ id: "registeredAddress", label: "Station Location" },
{ id: "connectorType", label: "Connector Type" },
{ id: "power", label: "Max Power(KW)" },
{ id: "price", label: "Price" },
{ id: "isAvailable", label: "Is Available", align: "center" },
{ id: "action", label: "Action", align: "center" },
];
const categoryRows = managerStationDetails?.length
? managerStationDetails.map((managerStation, index) => {
return {
id: managerStation.id,
srno: index + 1,
stationName: managerStation?.stationName ?? "NA",
registeredAddress: managerStation.registeredAddress,
connectorType: managerStation.connectorType,
power: managerStation.power,
price: managerStation.price ?? "NA",
isAvailable: managerStation?.isAvailable ? "Yes" : "No",
};
})
: [];
return (
<>
{/* Custom Table to show manager list */}
<CustomTable
columns={categoryColumns}
rows={categoryRows}
setDeleteModal={setDeleteModal}
deleteModal={deleteModal}
setViewModal={setViewModal}
viewModal={viewModal}
setRowData={setRowData}
setModalOpen={() => setEditModalOpen(true)}
tableType="manager-station"
handleClickOpen={handleClickOpen}
/>
{/* Add Manager Modal */}
<AddManagerStationModal
open={addModalOpen}
handleClose={handleCloseModal}
handleAddManager={handleAddManager}
/>
{/* Edit Manager Modal */}
<EditManagerStationModal
open={editModalOpen}
handleClose={handleCloseModal}
handleUpdate={handleUpdate}
editRow={rowData}
/>
</>
);
}

View file

@ -11,6 +11,8 @@ import stationReducer from "../redux/slices/stationSlice.ts";
import slotReducer from "../redux/slices/slotSlice.ts"; import slotReducer from "../redux/slices/slotSlice.ts";
import bookReducer from "../redux/slices/bookSlice.ts"; import bookReducer from "../redux/slices/bookSlice.ts";
import dashboardReducer from "../redux/slices/dashboardSlice.ts"; import dashboardReducer from "../redux/slices/dashboardSlice.ts";
import managerStationReducer from "../redux/slices/managerStationSlice.ts";
const rootReducer = combineReducers({ const rootReducer = combineReducers({
authReducer, authReducer,
@ -23,8 +25,8 @@ const rootReducer = combineReducers({
stationReducer, stationReducer,
slotReducer, slotReducer,
bookReducer, bookReducer,
dashboardReducer, dashboardReducer,
managerStationReducer,
}); });
export type RootState = ReturnType<typeof rootReducer>; export type RootState = ReturnType<typeof rootReducer>;

View file

@ -0,0 +1,206 @@
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
import http from "../../lib/https";
import { toast } from "sonner";
interface StationDetails {
id: number;
stationName: string;
registeredAddress: string;
connectorType: string;
power: string;
price: string;
isAvailable: boolean;
}
interface StationDetailState {
stationDetails: StationDetails[];
loading: boolean;
error: string | null;
}
// Initial state
const initialState: StationDetailState = {
stationDetails: [],
loading: false,
error: null,
};
// Fetch Manager List (Async Thunk)
export const stationDetailList = createAsyncThunk<
StationDetails[],
void,
{ rejectValue: string }
>("fetchStationsDetails", async (_, { rejectWithValue }) => {
try {
const token = localStorage?.getItem("authToken");
if (!token) throw new Error("No token found");
const response = await http.get("/manager-station-details");
if (!response.data?.data) throw new Error("Invalid API response");
return response.data.data;
} catch (error: any) {
toast.error("Error Fetching Managers: " + error.message);
return rejectWithValue(
error?.response?.data?.message || "An error occurred"
);
}
});
// Create Manager (Async Thunk)
export const addStationDetails = createAsyncThunk<
StationDetails,
{
connectorType: string;
power: string;
price: string;
isAvailable: boolean;
},
{ rejectValue: string }
>("addManager", async (data, { rejectWithValue }) => {
try {
const response = await http.post(
"create-manager-station-details",
data
);
toast.success("Station details created successfully");
return response.data?.data;
} catch (error: any) {
toast.error("Error creating manager: " + error.response?.data?.message);
return rejectWithValue(
error.response?.data?.message || "An error occurred"
);
}
});
// Update Manager (Async Thunk)
export const updateStationDetails = createAsyncThunk<
StationDetails,
{
id: number;
connectorType: string;
power: string;
price: string;
isAvailable: boolean;
},
{ rejectValue: string }
>(
"updateManagerStationDetails",
async ({ id, ...managerStationData }, { rejectWithValue }) => {
if (!id) {
return rejectWithValue("Manager ID is required.");
}
try {
const response = await http.put(
`/update-manager-station-details/${id}`,
{ ...managerStationData }
);
toast.success("Station Details updated successfully");
return response?.data;
} catch (error: any) {
toast.error("Error updating manager: " + error.message);
return rejectWithValue(
error.response?.data?.message || "An error occurred"
);
}
}
);
// Delete Manager (Async Thunk)
export const deleteStationDetails = createAsyncThunk<
string,
string,
{ rejectValue: string }
>("deleteManager", async (id, { rejectWithValue }) => {
try {
await http.delete(`/delete-manager/${id}`);
toast.success("Station details deleted successfully!");
return id;
} catch (error: any) {
toast.error("Error deleting manager: " + error.message);
return rejectWithValue(
error.response?.data?.message || "An error occurred"
);
}
});
// Create Slice
const managerStationSlice = createSlice({
name: "manager-station",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
// Fetch Managers
.addCase(stationDetailList.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(
stationDetailList.fulfilled,
(state, action: PayloadAction<StationDetails[]>) => {
state.loading = false;
state.stationDetails = action.payload;
}
)
.addCase(stationDetailList.rejected, (state, action) => {
state.loading = false;
state.error = action.payload || "Failed to fetch managers";
})
// Add Station details
.addCase(addStationDetails.pending, (state) => {
state.loading = true;
})
.addCase(
addStationDetails.fulfilled,
(state, action: PayloadAction<StationDetails>) => {
state.loading = false;
state.stationDetails.push(action.payload);
}
)
.addCase(addStationDetails.rejected, (state, action) => {
state.loading = false;
state.error = action.payload || "Failed to add manager";
})
// Update Manager
.addCase(updateStationDetails.pending, (state) => {
state.loading = true;
})
.addCase(updateStationDetails.fulfilled, (state, action) => {
state.loading = false;
const updateStationDetail = action.payload;
const index = state.stationDetails.findIndex(
(stationDetail) =>
stationDetail.id === updateStationDetail.id
);
if (index !== -1) {
state.stationDetails[index] = updateStationDetail; // Update the manager station in the state
}
})
.addCase(updateStationDetails.rejected, (state, action) => {
state.loading = false;
state.error = action.payload || "Failed to update manager";
})
// Delete Manager
.addCase(deleteStationDetails.pending, (state) => {
state.loading = true;
})
.addCase(deleteStationDetails.fulfilled, (state, action) => {
state.loading = false;
state.stationDetails = state.stationDetails.filter(
(stationDetail) =>
String(stationDetail.id) !== String(action.payload)
);
})
.addCase(deleteStationDetails.rejected, (state, action) => {
state.loading = false;
state.error = action.payload || "Failed to delete manager";
});
},
});
export default managerStationSlice.reducer;

View file

@ -28,6 +28,9 @@ const ExternalStationList = lazy(
); );
const AllManagersList = lazy(() => import("./pages/AllMangersList")); const AllManagersList = lazy(() => import("./pages/AllMangersList"));
const AvailableSlotsList = lazy(() => import("./pages/AvailableSlotsList")); const AvailableSlotsList = lazy(() => import("./pages/AvailableSlotsList"));
const ManagerStationDetails = lazy(
() => import("./pages/ManagerStationDetails")
);
interface ProtectedRouteProps { interface ProtectedRouteProps {
@ -136,7 +139,14 @@ export default function AppRouter() {
/> />
} }
/> />
<Route
path="manager-station-details"
element={
<ProtectedRoute
component={<ManagerStationDetails />}
/>
}
/>
</Route> </Route>
{/* Catch-all Route */} {/* Catch-all Route */}

View file

@ -40,6 +40,7 @@ export const dataDisplayCustomizations = {
'&.Mui-selected': { '&.Mui-selected': {
opacity: 1, opacity: 1,
backgroundColor: alpha(theme.palette.action.selected, 0.3), backgroundColor: alpha(theme.palette.action.selected, 0.3),
[`& .${svgIconClasses.root}`]: { [`& .${svgIconClasses.root}`]: {
color: (theme.vars || theme).palette.text.primary, color: (theme.vars || theme).palette.text.primary,
}, },

View file

@ -39,6 +39,7 @@ export const dataDisplayCustomizations: Components<Theme> = {
'&.Mui-selected': { '&.Mui-selected': {
opacity: 1, opacity: 1,
backgroundColor: alpha(theme.palette.action.selected, 0.3), backgroundColor: alpha(theme.palette.action.selected, 0.3),
[`& .${svgIconClasses.root}`]: { [`& .${svgIconClasses.root}`]: {
color: (theme.vars || theme).palette.text.primary, color: (theme.vars || theme).palette.text.primary,
}, },