Create Charging Station Ui and Updation in Managers
This commit is contained in:
parent
1e922f1ac5
commit
690af22199
|
@ -31,7 +31,6 @@ export default function AddManagerModal({
|
|||
reset,
|
||||
} = useForm();
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
|
||||
// Handle form submission
|
||||
const onSubmit = async (data: any) => {
|
||||
|
@ -41,6 +40,7 @@ export default function AddManagerModal({
|
|||
phone: data.phone,
|
||||
registeredAddress: data.registeredAddress,
|
||||
password: data.password,
|
||||
stationId: data.stationId, // Send stationId here
|
||||
roleName: "Manager", // You can replace this with dynamic role if needed
|
||||
};
|
||||
|
||||
|
@ -55,11 +55,10 @@ export default function AddManagerModal({
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
const togglePasswordVisibility = (e: React.MouseEvent) => {
|
||||
const togglePasswordVisibility = (e: React.MouseEvent) => {
|
||||
e.preventDefault(); // Prevent focus loss
|
||||
setShowPassword((prev) => !prev);
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
@ -107,37 +106,59 @@ export default function AddManagerModal({
|
|||
{/* Form */}
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{/* Manager Name */}
|
||||
<Box
|
||||
sx={{ display: "flex", flexDirection: "column", mb: 2 }}
|
||||
>
|
||||
<Typography variant="body2" fontWeight={500}>
|
||||
Manager Name
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
placeholder="Enter Manager Name"
|
||||
size="small"
|
||||
error={!!errors.name}
|
||||
helperText={errors.name ? errors.name.message : ""}
|
||||
{...register("name", {
|
||||
required: "Manager Name is required",
|
||||
minLength: {
|
||||
value: 3,
|
||||
message: "Minimum 3 characters required",
|
||||
},
|
||||
maxLength: {
|
||||
value: 30,
|
||||
message: "Maximum 30 characters allowed",
|
||||
},
|
||||
pattern: {
|
||||
value: /^[A-Za-z\s]+$/, // Only letters and spaces are allowed
|
||||
message:
|
||||
"Manager Name must only contain letters and spaces",
|
||||
},
|
||||
})}
|
||||
/>
|
||||
<Box sx={{ display: "flex", gap: 2, mb: 2 }}>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="body2" fontWeight={500}>
|
||||
Manager Name
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
placeholder="Enter Manager Name"
|
||||
size="small"
|
||||
error={!!errors.name}
|
||||
helperText={
|
||||
errors.name ? errors.name.message : ""
|
||||
}
|
||||
{...register("name", {
|
||||
required: "Manager Name is required",
|
||||
minLength: {
|
||||
value: 3,
|
||||
message:
|
||||
"Minimum 3 characters required",
|
||||
},
|
||||
maxLength: {
|
||||
value: 30,
|
||||
message:
|
||||
"Maximum 30 characters allowed",
|
||||
},
|
||||
pattern: {
|
||||
value: /^[A-Za-z\s]+$/, // Only letters and spaces are allowed
|
||||
message:
|
||||
"Manager Name must only contain letters and spaces",
|
||||
},
|
||||
})}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="body2" fontWeight={500}>
|
||||
Station Id
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
placeholder="Enter Station Id"
|
||||
size="small"
|
||||
error={!!errors.stationId}
|
||||
helperText={
|
||||
errors.stationId
|
||||
? errors.stationId.message
|
||||
: ""
|
||||
}
|
||||
{...register("stationId", {
|
||||
required: "Station Id is required",
|
||||
})}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Email and Password */}
|
||||
<Box sx={{ display: "flex", gap: 2, mb: 2 }}>
|
||||
{/* Email */}
|
||||
|
@ -256,7 +277,7 @@ export default function AddManagerModal({
|
|||
{/* Registered Address */}
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="body2" fontWeight={500}>
|
||||
Registered Address
|
||||
Station Location
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
|
|
205
src/components/AddStationModal/index.tsx
Normal file
205
src/components/AddStationModal/index.tsx
Normal file
|
@ -0,0 +1,205 @@
|
|||
import { useForm } from "react-hook-form";
|
||||
import { Box, Button, Typography, Modal } from "@mui/material";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import {
|
||||
CustomIconButton,
|
||||
CustomTextField,
|
||||
} from "../AddUserModel/styled.css.tsx"; // Assuming custom styled components
|
||||
|
||||
export default function AddStationModal({
|
||||
open,
|
||||
handleClose,
|
||||
handleAddStation,
|
||||
}) {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
reset,
|
||||
} = useForm();
|
||||
|
||||
const onSubmit = (data: any) => {
|
||||
handleAddStation(data); // Add station to the list
|
||||
handleClose(); // Close modal after adding
|
||||
reset();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={(e, reason) => {
|
||||
if (reason === "backdropClick") {
|
||||
return;
|
||||
}
|
||||
handleClose(); // Close modal when clicking cross or cancel
|
||||
}}
|
||||
aria-labelledby="add-station-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 Charging Station
|
||||
</Typography>
|
||||
<CustomIconButton onClick={handleClose}>
|
||||
<CloseIcon />
|
||||
</CustomIconButton>
|
||||
</Box>
|
||||
|
||||
{/* Horizontal Line */}
|
||||
<Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
|
||||
|
||||
{/* Form */}
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{/* 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}>
|
||||
Station Name
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
placeholder="Enter Station Name"
|
||||
size="small"
|
||||
error={!!errors.name}
|
||||
helperText={
|
||||
errors.name ? errors.name.message : ""
|
||||
}
|
||||
{...register("name", {
|
||||
required: "Station Name is required",
|
||||
minLength: {
|
||||
value: 3,
|
||||
message:
|
||||
"Minimum 3 characters required",
|
||||
},
|
||||
maxLength: {
|
||||
value: 30,
|
||||
message:
|
||||
"Maximum 30 characters allowed",
|
||||
},
|
||||
})}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" fontWeight={500}>
|
||||
Station Location
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
placeholder="Enter Registered Address"
|
||||
size="small"
|
||||
error={!!errors.registeredAddress}
|
||||
helperText={
|
||||
errors.registeredAddress
|
||||
? errors.registeredAddress.message
|
||||
: ""
|
||||
}
|
||||
{...register("registeredAddress", {
|
||||
required:
|
||||
"Registered Address is required",
|
||||
})}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Second Row - Total Slots */}
|
||||
<Box sx={{ display: "flex", gap: 2 }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" fontWeight={500}>
|
||||
Total Slots
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
placeholder="Enter Total Slots"
|
||||
size="small"
|
||||
type="number"
|
||||
error={!!errors.totalSlots}
|
||||
helperText={
|
||||
errors.totalSlots
|
||||
? errors.totalSlots.message
|
||||
: ""
|
||||
}
|
||||
{...register("totalSlots", {
|
||||
required: "Total Slots are required",
|
||||
min: {
|
||||
value: 1,
|
||||
message:
|
||||
"At least 1 slot is required",
|
||||
},
|
||||
})}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Submit Button */}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
mt: 3,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
sx={{
|
||||
backgroundColor: "#52ACDF",
|
||||
color: "white",
|
||||
borderRadius: "8px",
|
||||
width: "117px",
|
||||
"&:hover": { backgroundColor: "#439BC1" },
|
||||
}}
|
||||
>
|
||||
Add Station
|
||||
</Button>
|
||||
</Box>
|
||||
</form>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
}
|
|
@ -34,6 +34,8 @@ import { CustomIconButton } from "../AddUserModel/styled.css.tsx";
|
|||
import ManagerViewModal from "../Modals/ViewManagerModal";
|
||||
import UserViewModal from "../Modals/UserViewModal/index.tsx";
|
||||
import { deleteUser, userList } from "../../redux/slices/userSlice.ts";
|
||||
import { deleteStation } from "../../redux/slices/stationSlice.ts";
|
||||
import StationViewModal from "../Modals/StationViewModal/index.tsx";
|
||||
// Styled components for customization
|
||||
const StyledTableCell = styled(TableCell)(({ theme }) => ({
|
||||
[`&.${tableCellClasses.head}`]: {
|
||||
|
@ -144,6 +146,9 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
|||
case "user":
|
||||
dispatch(deleteUser(id || ""));
|
||||
break;
|
||||
case "station":
|
||||
dispatch(deleteStation(id || ""));
|
||||
break;
|
||||
default:
|
||||
console.error("Unknown table type:", tableType);
|
||||
return;
|
||||
|
@ -231,6 +236,8 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
|||
? "Managers"
|
||||
: tableType === "vehicle"
|
||||
? "Vehicles"
|
||||
: tableType === "station"
|
||||
? "Charging Station"
|
||||
: "List"}
|
||||
</Typography>
|
||||
|
||||
|
@ -302,6 +309,8 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
|||
? "Manager"
|
||||
: tableType === "vehicle"
|
||||
? "Vehicle"
|
||||
: tableType === "station"
|
||||
? "Charging Station"
|
||||
: "Item"}
|
||||
</Button>
|
||||
</Box>
|
||||
|
@ -519,6 +528,16 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
|||
id={selectedRow?.id}
|
||||
/>
|
||||
)}
|
||||
{viewModal && tableType === "station" && (
|
||||
<StationViewModal
|
||||
handleView={() =>
|
||||
handleViewButton(selectedRow?.id)
|
||||
}
|
||||
open={viewModal}
|
||||
setViewModal={setViewModal}
|
||||
id={selectedRow?.id}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="text"
|
||||
|
@ -551,6 +570,25 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
|||
: "Activate"}
|
||||
</Button>
|
||||
)}
|
||||
{tableType === "station" && (
|
||||
<Button
|
||||
variant="text"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleToggleStatus();
|
||||
}}
|
||||
color="secondary"
|
||||
sx={{
|
||||
justifyContent: "flex-start",
|
||||
py: 0,
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
{selectedRow?.statusValue === 1
|
||||
? "Not Available"
|
||||
: "Available"}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="text"
|
||||
|
|
|
@ -24,7 +24,6 @@ interface EditManagerModalProps {
|
|||
interface FormData {
|
||||
name: string;
|
||||
email: string;
|
||||
registeredAddress: string;
|
||||
phone: string;
|
||||
}
|
||||
|
||||
|
@ -44,7 +43,6 @@ const EditManagerModal: React.FC<EditManagerModalProps> = ({
|
|||
defaultValues: {
|
||||
name: "",
|
||||
email: "",
|
||||
registeredAddress: "",
|
||||
phone: "",
|
||||
},
|
||||
});
|
||||
|
@ -56,7 +54,6 @@ const EditManagerModal: React.FC<EditManagerModalProps> = ({
|
|||
if (editRow) {
|
||||
setValue("name", editRow.name);
|
||||
setValue("email", editRow.email);
|
||||
setValue("registeredAddress", editRow.registeredAddress);
|
||||
setValue("phone", editRow.phone);
|
||||
} else {
|
||||
reset();
|
||||
|
@ -72,12 +69,11 @@ const EditManagerModal: React.FC<EditManagerModalProps> = ({
|
|||
managerData: {
|
||||
name: data.name,
|
||||
email: data.email,
|
||||
registeredAddress: data.registeredAddress,
|
||||
phone: data.phone,
|
||||
},
|
||||
})
|
||||
).unwrap(); // Ensure that it throws an error if the update fails
|
||||
dispatch(managerList());
|
||||
dispatch(managerList());
|
||||
handleClose(); // Close modal on success
|
||||
reset(); // Reset form fields after submit
|
||||
} catch (error) {
|
||||
|
@ -134,9 +130,9 @@ const EditManagerModal: React.FC<EditManagerModalProps> = ({
|
|||
{/* Horizontal Line */}
|
||||
<Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
|
||||
|
||||
{/* Input Fields (2 inputs per row) */}
|
||||
{/* Input Fields (Name, Email, and Phone) */}
|
||||
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 2 }}>
|
||||
{/* Manager Name and Email in one row */}
|
||||
{/* Manager Name */}
|
||||
<Box sx={{ flex: "1 1 48%" }}>
|
||||
<Typography variant="body2" fontWeight={500} mb={0.5}>
|
||||
Manager Name
|
||||
|
@ -145,24 +141,22 @@ const EditManagerModal: React.FC<EditManagerModalProps> = ({
|
|||
name="name"
|
||||
control={control}
|
||||
rules={{
|
||||
required: "Manager Name is required",
|
||||
minLength: {
|
||||
value: 3,
|
||||
message:
|
||||
"Minimum 3 characters required",
|
||||
},
|
||||
maxLength: {
|
||||
value: 30,
|
||||
message:
|
||||
"Maximum 30 characters allowed",
|
||||
},
|
||||
pattern: {
|
||||
value: /^[A-Za-z\s]+$/, // Only letters and spaces are allowed
|
||||
message:
|
||||
"Manager Name must only contain letters and spaces",
|
||||
},
|
||||
}}
|
||||
render={({ field }) => (
|
||||
required: "Manager Name is required",
|
||||
minLength: {
|
||||
value: 3,
|
||||
message: "Minimum 3 characters required",
|
||||
},
|
||||
maxLength: {
|
||||
value: 30,
|
||||
message: "Maximum 30 characters allowed",
|
||||
},
|
||||
pattern: {
|
||||
value: /^[A-Za-z\s]+$/, // Only letters and spaces are allowed
|
||||
message:
|
||||
"Manager Name must only contain letters and spaces",
|
||||
},
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<CustomTextField
|
||||
{...field}
|
||||
fullWidth
|
||||
|
@ -170,12 +164,12 @@ const EditManagerModal: React.FC<EditManagerModalProps> = ({
|
|||
size="small"
|
||||
error={!!errors.name}
|
||||
helperText={errors.name?.message}
|
||||
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Email */}
|
||||
<Box sx={{ flex: "1 1 48%" }}>
|
||||
<Typography variant="body2" fontWeight={500} mb={0.5}>
|
||||
Email
|
||||
|
@ -197,32 +191,7 @@ const EditManagerModal: React.FC<EditManagerModalProps> = ({
|
|||
/>
|
||||
</Box>
|
||||
|
||||
{/* Registered Address and Phone Number in one row */}
|
||||
<Box sx={{ flex: "1 1 48%" }}>
|
||||
<Typography variant="body2" fontWeight={500} mb={0.5}>
|
||||
Registered Address
|
||||
</Typography>
|
||||
<Controller
|
||||
name="registeredAddress"
|
||||
control={control}
|
||||
rules={{
|
||||
required: "Registered Address is required",
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<CustomTextField
|
||||
{...field}
|
||||
fullWidth
|
||||
placeholder="Enter Registered Address"
|
||||
size="small"
|
||||
error={!!errors.registeredAddress}
|
||||
helperText={
|
||||
errors.registeredAddress?.message
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Phone Number */}
|
||||
<Box sx={{ flex: "1 1 48%" }}>
|
||||
<Typography variant="body2" fontWeight={500} mb={0.5}>
|
||||
Phone Number
|
||||
|
|
270
src/components/EditStationModal/index.tsx
Normal file
270
src/components/EditStationModal/index.tsx
Normal file
|
@ -0,0 +1,270 @@
|
|||
import React, { useEffect } from "react";
|
||||
import { Box, Button, Typography, Modal } from "@mui/material";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import {
|
||||
CustomIconButton,
|
||||
CustomTextField,
|
||||
} from "../AddUserModel/styled.css.tsx";
|
||||
|
||||
interface EditStationModalProps {
|
||||
open: boolean;
|
||||
handleClose: () => void;
|
||||
handleUpdate: (
|
||||
id: string,
|
||||
name: string,
|
||||
registeredAddress: string,
|
||||
totalSlots: number,
|
||||
imageUrl: string
|
||||
) => void;
|
||||
editRow: any;
|
||||
}
|
||||
|
||||
interface FormData {
|
||||
name: string;
|
||||
registeredAddress: string;
|
||||
totalSlots: number;
|
||||
imageUrl: string;
|
||||
}
|
||||
|
||||
const EditStationModal: React.FC<EditStationModalProps> = ({
|
||||
open,
|
||||
handleClose,
|
||||
handleUpdate,
|
||||
editRow,
|
||||
}) => {
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
setValue,
|
||||
reset,
|
||||
} = useForm<FormData>({
|
||||
defaultValues: {
|
||||
name: "",
|
||||
registeredAddress: "",
|
||||
totalSlots: 0,
|
||||
imageUrl: "",
|
||||
},
|
||||
});
|
||||
|
||||
// Set values if editRow is provided
|
||||
useEffect(() => {
|
||||
if (editRow) {
|
||||
setValue("name", editRow.name);
|
||||
setValue("registeredAddress", editRow.registeredAddress);
|
||||
setValue("totalSlots", editRow.totalSlots);
|
||||
setValue("imageUrl", editRow.imageUrl);
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
}, [editRow, setValue, reset]);
|
||||
|
||||
const onSubmit = (data: FormData) => {
|
||||
if (editRow) {
|
||||
handleUpdate(
|
||||
editRow.id,
|
||||
data.name,
|
||||
data.registeredAddress,
|
||||
data.totalSlots,
|
||||
data.imageUrl
|
||||
);
|
||||
}
|
||||
handleClose(); // Close the modal
|
||||
reset(); // Reset the form fields
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={(e, reason) => {
|
||||
if (reason === "backdropClick") {
|
||||
return;
|
||||
}
|
||||
handleClose(); // Close modal when clicking cross or cancel
|
||||
}}
|
||||
aria-labelledby="edit-station-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}>
|
||||
Edit Charging Station
|
||||
</Typography>
|
||||
<CustomIconButton onClick={handleClose}>
|
||||
<CloseIcon />
|
||||
</CustomIconButton>
|
||||
</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}
|
||||
>
|
||||
Station Name
|
||||
</Typography>
|
||||
<Controller
|
||||
name="name"
|
||||
control={control}
|
||||
rules={{
|
||||
required: "Station Name is required",
|
||||
minLength: {
|
||||
value: 3,
|
||||
message:
|
||||
"Minimum 3 characters required",
|
||||
},
|
||||
maxLength: {
|
||||
value: 30,
|
||||
message:
|
||||
"Maximum 30 characters allowed",
|
||||
},
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<CustomTextField
|
||||
{...field}
|
||||
fullWidth
|
||||
placeholder="Enter Station Name"
|
||||
size="small"
|
||||
error={!!errors.name}
|
||||
helperText={errors.name?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight={500}
|
||||
mb={0.5}
|
||||
>
|
||||
Station Location
|
||||
</Typography>
|
||||
<Controller
|
||||
name="registeredAddress"
|
||||
control={control}
|
||||
rules={{
|
||||
required: "Registered Address is required",
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<CustomTextField
|
||||
{...field}
|
||||
fullWidth
|
||||
placeholder="Enter Registered Address"
|
||||
size="small"
|
||||
error={!!errors.registeredAddress}
|
||||
helperText={
|
||||
errors.registeredAddress?.message
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Second Row - Total Slots */}
|
||||
<Box sx={{ display: "flex", gap: 2 }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight={500}
|
||||
mb={0.5}
|
||||
>
|
||||
Total Slots
|
||||
</Typography>
|
||||
<Controller
|
||||
name="totalSlots"
|
||||
control={control}
|
||||
rules={{
|
||||
required: "Total Slots are required",
|
||||
min: {
|
||||
value: 1,
|
||||
message: "At least 1 slot is required",
|
||||
},
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<CustomTextField
|
||||
{...field}
|
||||
fullWidth
|
||||
placeholder="Enter Total Slots"
|
||||
size="small"
|
||||
type="number"
|
||||
error={!!errors.totalSlots}
|
||||
helperText={errors.totalSlots?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
|
||||
{/* Submit Button */}
|
||||
<Box
|
||||
sx={{ display: "flex", justifyContent: "flex-end", mt: 3 }}
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
sx={{
|
||||
backgroundColor: "#52ACDF",
|
||||
color: "white",
|
||||
borderRadius: "8px",
|
||||
width: "117px",
|
||||
"&:hover": { backgroundColor: "#439BC1" },
|
||||
}}
|
||||
>
|
||||
Update Charging Station
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditStationModal;
|
|
@ -43,17 +43,24 @@ export default function MenuContent({ hidden }: PropType) {
|
|||
text: "Users",
|
||||
icon: <AnalyticsRoundedIcon />,
|
||||
url: "/panel/user-list",
|
||||
},
|
||||
userRole === "admin" && {
|
||||
text: "Charging Stations",
|
||||
icon: <ManageAccountsOutlinedIcon />,
|
||||
url: "/panel/station-list", // Placeholder for now
|
||||
},
|
||||
userRole === "admin" && {
|
||||
text: "Managers",
|
||||
icon: <ManageAccountsOutlinedIcon />,
|
||||
url: "/panel/manager-list", // Placeholder for now
|
||||
},
|
||||
|
||||
userRole === "admin" && {
|
||||
text: "Vehicles",
|
||||
icon: <ManageAccountsOutlinedIcon />,
|
||||
url: "/panel/vehicle-list", // Placeholder for now
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
const filteredMenuItems = baseMenuItems.filter(Boolean);
|
||||
|
@ -99,4 +106,4 @@ export default function MenuContent({ hidden }: PropType) {
|
|||
</List>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
127
src/components/Modals/StationViewModal/index.tsx
Normal file
127
src/components/Modals/StationViewModal/index.tsx
Normal file
|
@ -0,0 +1,127 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Box, Modal, Typography, Divider, Grid } from "@mui/material";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "../../../redux/reducers";
|
||||
|
||||
type Props = {
|
||||
open: boolean;
|
||||
setViewModal: Function;
|
||||
handleView: (id: string | undefined) => void;
|
||||
id?: number | undefined;
|
||||
};
|
||||
|
||||
const style = {
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
width: 400,
|
||||
bgcolor: "background.paper",
|
||||
borderRadius: 2,
|
||||
boxShadow: "0px 4px 20px rgba(0, 0, 0, 0.15)",
|
||||
p: 4,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: 2,
|
||||
};
|
||||
|
||||
export default function StationViewModal({ open, setViewModal, id }: Props) {
|
||||
const { stations } = useSelector(
|
||||
(state: RootState) => state.stationReducer
|
||||
);
|
||||
const [selectedStation, setSelectedStation] = useState<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
const station = stations.find((station) => station.id === id);
|
||||
setSelectedStation(station || null);
|
||||
}
|
||||
}, [id, stations]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
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" }}>
|
||||
{selectedStation?.name || "Station"}'s Details
|
||||
</Box>
|
||||
<Box
|
||||
onClick={() => setViewModal(false)}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</Box>
|
||||
</Box>
|
||||
</Typography>
|
||||
|
||||
<Divider sx={{ width: "100%" }} />
|
||||
|
||||
{selectedStation ? (
|
||||
<Grid container spacing={2} sx={{ width: "80%" }}>
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="body1">
|
||||
<strong>Station Name:</strong>{" "}
|
||||
<Typography variant="body2">
|
||||
{selectedStation.name}
|
||||
</Typography>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="body1">
|
||||
<strong>Station Location:</strong>{" "}
|
||||
<Typography variant="body2">
|
||||
{selectedStation.registeredAddress}
|
||||
</Typography>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="body1">
|
||||
<strong>Total Slots:</strong>
|
||||
<Typography variant="body2">
|
||||
{selectedStation.totalSlots}
|
||||
</Typography>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="body1">
|
||||
<strong>Status:</strong>
|
||||
<Typography variant="body2">
|
||||
{selectedStation.status === "available"
|
||||
? "Available"
|
||||
: "Not Available"}
|
||||
</Typography>
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
) : (
|
||||
<Typography align="center">
|
||||
No station found with this ID
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
}
|
|
@ -29,15 +29,18 @@ export default function ManagerList() {
|
|||
(state: RootState) => state.managerReducer.managers
|
||||
);
|
||||
|
||||
// Fetch manager list on component mount
|
||||
useEffect(() => {
|
||||
dispatch(managerList());
|
||||
}, [dispatch]);
|
||||
|
||||
// Open Add Manager Modal
|
||||
const handleClickOpen = () => {
|
||||
setRowData(null); // Reset row data when opening for new manager
|
||||
setAddModalOpen(true);
|
||||
};
|
||||
|
||||
// Close all modals
|
||||
const handleCloseModal = () => {
|
||||
setAddModalOpen(false);
|
||||
setEditModalOpen(false);
|
||||
|
@ -45,13 +48,16 @@ export default function ManagerList() {
|
|||
reset();
|
||||
};
|
||||
|
||||
// Handle adding a new manager
|
||||
const handleAddManager = async (data: {
|
||||
name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
registeredAddress: string;
|
||||
stationId: string;
|
||||
}) => {
|
||||
try {
|
||||
// Add manager with stationId
|
||||
await dispatch(addManager(data)); // Dispatch action to add manager
|
||||
await dispatch(managerList()); // Fetch the updated list
|
||||
handleCloseModal(); // Close the modal
|
||||
|
@ -60,14 +66,17 @@ export default function ManagerList() {
|
|||
}
|
||||
};
|
||||
|
||||
// Handle updating an existing manager
|
||||
const handleUpdate = async (
|
||||
id: number,
|
||||
name: string,
|
||||
email: string,
|
||||
phone: string,
|
||||
registeredAddress: string
|
||||
registeredAddress: string,
|
||||
stationId: string
|
||||
) => {
|
||||
try {
|
||||
// Update manager with stationId
|
||||
await dispatch(
|
||||
updateManager({
|
||||
id,
|
||||
|
@ -75,6 +84,7 @@ export default function ManagerList() {
|
|||
email,
|
||||
phone,
|
||||
registeredAddress,
|
||||
stationId,
|
||||
})
|
||||
);
|
||||
await dispatch(managerList()); // Refresh the list after update
|
||||
|
@ -84,16 +94,18 @@ export default function ManagerList() {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
// Columns for the manager table
|
||||
const categoryColumns: Column[] = [
|
||||
{ id: "srno", label: "Sr No" },
|
||||
{ id: "name", label: "Name" },
|
||||
{ id: "email", label: "Email" },
|
||||
{ id: "phone", label: "Phone" },
|
||||
{ id: "registeredAddress", label: "Station Location" },
|
||||
{ id: "stationName", label: "Station Name" },
|
||||
{ id: "action", label: "Action", align: "center" },
|
||||
];
|
||||
|
||||
// Filter managers based on search term
|
||||
const filteredManagers = managers?.filter(
|
||||
(manager) =>
|
||||
manager.name?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
|
@ -104,6 +116,7 @@ export default function ManagerList() {
|
|||
.includes(searchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
// Format rows to display manager details
|
||||
const categoryRows = filteredManagers?.length
|
||||
? filteredManagers?.map(
|
||||
(
|
||||
|
@ -113,6 +126,7 @@ export default function ManagerList() {
|
|||
email: string;
|
||||
phone: string;
|
||||
registeredAddress: string;
|
||||
stationName: string;
|
||||
},
|
||||
index: number
|
||||
) => ({
|
||||
|
@ -122,12 +136,14 @@ export default function ManagerList() {
|
|||
email: manager?.email,
|
||||
phone: manager.phone ?? "NA",
|
||||
registeredAddress: manager?.registeredAddress ?? "NA",
|
||||
stationName: manager?.stationName ?? "NA",
|
||||
})
|
||||
)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Custom Table to show manager list */}
|
||||
<CustomTable
|
||||
columns={categoryColumns}
|
||||
rows={categoryRows}
|
||||
|
@ -140,11 +156,13 @@ export default function ManagerList() {
|
|||
tableType="manager"
|
||||
handleClickOpen={handleClickOpen}
|
||||
/>
|
||||
{/* Add Manager Modal */}
|
||||
<AddManagerModal
|
||||
open={addModalOpen}
|
||||
handleClose={handleCloseModal}
|
||||
handleAddManager={handleAddManager}
|
||||
/>
|
||||
{/* Edit Manager Modal */}
|
||||
<EditManagerModal
|
||||
open={editModalOpen}
|
||||
handleClose={handleCloseModal}
|
||||
|
|
192
src/pages/StationList/index.tsx
Normal file
192
src/pages/StationList/index.tsx
Normal file
|
@ -0,0 +1,192 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import CustomTable, { Column } from "../../components/CustomTable";
|
||||
import { RootState } from "../../redux/reducers";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { AppDispatch } from "../../redux/store/store";
|
||||
import {
|
||||
addVehicle,
|
||||
updateVehicle,
|
||||
vehicleList,
|
||||
} from "../../redux/slices/VehicleSlice";
|
||||
import AddStationModal from "../../components/AddStationModal";
|
||||
import EditStationModal from "../../components/EditStationModal";
|
||||
import {
|
||||
createStation,
|
||||
stationList,
|
||||
updateStation,
|
||||
} from "../../redux/slices/stationSlice";
|
||||
import { Chip, Switch } from "@mui/material";
|
||||
|
||||
export default function StationList() {
|
||||
const [addModalOpen, setAddModalOpen] = useState<boolean>(false);
|
||||
const [editModalOpen, setEditModalOpen] = useState<boolean>(false);
|
||||
const [editRow, setEditRow] = useState<any>(null);
|
||||
const { reset } = useForm();
|
||||
|
||||
const [deleteModal, setDeleteModal] = useState<boolean>(false);
|
||||
const [viewModal, setViewModal] = useState<boolean>(false);
|
||||
const [rowData, setRowData] = useState<any | null>(null);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
const stations = useSelector(
|
||||
(state: RootState) => state.stationReducer.stations
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(stationList());
|
||||
}, [dispatch]);
|
||||
|
||||
const handleClickOpen = () => {
|
||||
setRowData(null); // Reset row data when opening for new admin
|
||||
setAddModalOpen(true);
|
||||
};
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setAddModalOpen(false);
|
||||
setEditModalOpen(false);
|
||||
setRowData(null);
|
||||
reset();
|
||||
};
|
||||
|
||||
const handleAddStation = async (data: {
|
||||
name: string;
|
||||
registeredAddress: string;
|
||||
totalSlots: string;
|
||||
}) => {
|
||||
try {
|
||||
await dispatch(createStation(data)); // Dispatch action to add Station
|
||||
await dispatch(stationList()); // Fetch the updated list
|
||||
handleCloseModal(); // Close the modal
|
||||
} catch (error) {
|
||||
console.error("Error adding Station", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdate = async (
|
||||
id: string,
|
||||
name: string,
|
||||
registeredAddress: string,
|
||||
totalSlots: string
|
||||
) => {
|
||||
try {
|
||||
await dispatch(
|
||||
updateStation({
|
||||
id,
|
||||
name,
|
||||
registeredAddress,
|
||||
totalSlots,
|
||||
})
|
||||
);
|
||||
await dispatch(stationList());
|
||||
handleCloseModal();
|
||||
} catch (error) {
|
||||
console.error("Update failed", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Toggle station status
|
||||
// const handleStatusToggle = async (id: string, currentStatus: number) => {
|
||||
// try {
|
||||
// const newStatus = currentStatus === 1 ? 0 : 1; // Toggle between Active(1) and Inactive(0)
|
||||
// await dispatch(updateStation({
|
||||
// id, status: newStatus,
|
||||
// name: "",
|
||||
// registeredAddress: "",
|
||||
// totalSlots: ""
|
||||
// }));
|
||||
// await dispatch(stationList());
|
||||
// } catch (error) {
|
||||
// console.error("Error toggling status", error);
|
||||
// }
|
||||
// };
|
||||
const filterStations = stations?.filter((station) =>
|
||||
station.name.toLocaleLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
const categoryRows = filterStations?.length
|
||||
? filterStations?.map((station: Station, index: number) => ({
|
||||
id: station.id,
|
||||
srno: index + 1,
|
||||
name: station.name,
|
||||
status: (
|
||||
<Chip
|
||||
label={
|
||||
station.status === 1 ? "Available" : "Not Available"
|
||||
}
|
||||
color={station.status === 1 ? "primary" : "default"}
|
||||
variant="outlined"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
width: "80px",
|
||||
textAlign: "center",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
/>
|
||||
),
|
||||
statusValue: station.status,
|
||||
}))
|
||||
: [];
|
||||
|
||||
const categoryColumns: Column[] = [
|
||||
{ id: "srno", label: "Sr No" },
|
||||
{ id: "name", label: "Station Name" },
|
||||
{ id: "registeredAddress", label: "Station Location" },
|
||||
{ id: "totalSlots", label: "Total Slots" },
|
||||
{ id: "status", label: "Status" },
|
||||
{ id: "action", label: "Action", align: "center" },
|
||||
];
|
||||
|
||||
// Filter stations based on search term
|
||||
// const filterStations = stations?.filter((station) =>
|
||||
// station.name.toLocaleLowerCase().includes(searchTerm.toLowerCase())
|
||||
// );
|
||||
|
||||
// // Prepare categoryRows with toggle switch for status
|
||||
// const categoryRows = filterStations?.length
|
||||
// ? filterStations?.map((station: any, index: number) => ({
|
||||
// id: station.id,
|
||||
// srno: index + 1,
|
||||
// name: station.name,
|
||||
// status: (
|
||||
// <Switch
|
||||
// checked={station.status === 1}
|
||||
// onChange={() =>
|
||||
// handleStatusToggle(station.id, station.status)
|
||||
// }
|
||||
// color="primary"
|
||||
// inputProps={{ "aria-label": "station-status-toggle" }}
|
||||
// />
|
||||
// ),
|
||||
// statusValue: station.status,
|
||||
// }))
|
||||
// : [];
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomTable
|
||||
columns={categoryColumns}
|
||||
rows={categoryRows}
|
||||
setDeleteModal={setDeleteModal}
|
||||
deleteModal={deleteModal}
|
||||
setViewModal={setViewModal}
|
||||
viewModal={viewModal}
|
||||
setRowData={setRowData}
|
||||
setModalOpen={() => setEditModalOpen(true)}
|
||||
tableType="station"
|
||||
handleClickOpen={handleClickOpen}
|
||||
/>
|
||||
<AddStationModal
|
||||
open={addModalOpen}
|
||||
handleClose={handleCloseModal}
|
||||
handleAddStation={handleAddStation}
|
||||
/>
|
||||
<EditStationModal
|
||||
open={editModalOpen}
|
||||
handleClose={handleCloseModal}
|
||||
handleUpdate={handleUpdate}
|
||||
editRow={rowData}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -7,6 +7,7 @@ import userReducer from "./slices/userSlice.ts";
|
|||
import roleReducer from "./slices/roleSlice.ts";
|
||||
import vehicleReducer from "./slices/VehicleSlice.ts";
|
||||
import managerReducer from "../redux/slices/managerSlice.ts";
|
||||
import stationReducer from "../redux/slices/stationSlice.ts";
|
||||
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
|
@ -17,6 +18,7 @@ const rootReducer = combineReducers({
|
|||
roleReducer,
|
||||
vehicleReducer,
|
||||
managerReducer,
|
||||
stationReducer
|
||||
// Add other reducers here...
|
||||
});
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ export const updateVehicle = createAsyncThunk(
|
|||
async ({ id, ...vehicleData }: Vehicle, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await http.patch(
|
||||
`${id}/update-vehicle`,
|
||||
`/update-vehicle/${id}`,
|
||||
vehicleData
|
||||
);
|
||||
toast.success("Vehicle Deatils updated successfully");
|
||||
|
@ -90,7 +90,7 @@ export const deleteVehicle = createAsyncThunk<
|
|||
{ rejectValue: string }
|
||||
>("deleteVehicle", async (id, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await http.delete(`/${id}/delete-vehicle`);
|
||||
const response = await http.delete(`/delete-vehicle/${id}`);
|
||||
toast.success(response.data?.message);
|
||||
return id;
|
||||
} catch (error: any) {
|
||||
|
|
|
@ -55,7 +55,7 @@ export const deleteAdmin = createAsyncThunk<
|
|||
{ rejectValue: string }
|
||||
>("deleteAdmin", async (id, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await http.delete(`/${id}/delete-admin`);
|
||||
const response = await http.delete(`/delete-admin/${id}`);
|
||||
toast.success(response.data?.message);
|
||||
return id;
|
||||
} catch (error: any) {
|
||||
|
@ -92,7 +92,7 @@ export const updateAdmin = createAsyncThunk(
|
|||
"updateAdmin",
|
||||
async ({ id, ...userData }: User, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await http.put(`/${id}/update-admin`, userData);
|
||||
const response = await http.put(`/update-admin/${id}`, userData);
|
||||
toast.success("Admin updated successfully");
|
||||
return response?.data;
|
||||
} catch (error: any) {
|
||||
|
|
|
@ -10,7 +10,7 @@ interface Manager {
|
|||
email: string;
|
||||
phone: string;
|
||||
registeredAddress: string;
|
||||
roleId: number;
|
||||
stationId: string;
|
||||
}
|
||||
|
||||
interface ManagerState {
|
||||
|
@ -74,7 +74,7 @@ export const updateManager = createAsyncThunk<
|
|||
return rejectWithValue("Manager ID is required.");
|
||||
}
|
||||
try {
|
||||
const response = await http.put(`/${id}/update-manager`, managerData);
|
||||
const response = await http.put(`/update-manager/${id}`, managerData);
|
||||
toast.success("Manager updated successfully");
|
||||
return response?.data;
|
||||
} catch (error: any) {
|
||||
|
@ -92,7 +92,7 @@ export const deleteManager = createAsyncThunk<
|
|||
{ rejectValue: string }
|
||||
>("deleteManager", async (id, { rejectWithValue }) => {
|
||||
try {
|
||||
await http.delete(`/${id}/delete-manager`);
|
||||
await http.delete(`/delete-manager/${id}`);
|
||||
toast.success("Manager deleted successfully!");
|
||||
return id;
|
||||
} catch (error: any) {
|
||||
|
|
246
src/redux/slices/stationSlice.ts
Normal file
246
src/redux/slices/stationSlice.ts
Normal file
|
@ -0,0 +1,246 @@
|
|||
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
|
||||
import axios from "axios";
|
||||
import http from "../../lib/https";
|
||||
import { toast } from "sonner";
|
||||
|
||||
// Define TypeScript types
|
||||
interface Station {
|
||||
id: string;
|
||||
name: string;
|
||||
registeredAddress: string;
|
||||
totalSlots: string;
|
||||
status: number;
|
||||
}
|
||||
|
||||
interface StationState {
|
||||
stations: Station[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
// Initial state
|
||||
const initialState: StationState = {
|
||||
stations: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
};
|
||||
|
||||
export const stationList = createAsyncThunk<any, void, { rejectValue: string }>(
|
||||
"fetchStations",
|
||||
async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
const token = localStorage?.getItem("authToken");
|
||||
if (!token) throw new Error("No token found");
|
||||
|
||||
const response = await http.get("/get-station");
|
||||
|
||||
if (!response.data) throw new Error("Invalid API response");
|
||||
|
||||
// Return the full response to handle in the reducer
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
toast.error("Error Fetching Stations: " + error.message);
|
||||
return rejectWithValue(
|
||||
error?.response?.data?.message || "An error occurred"
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Create Station
|
||||
export const createStation = createAsyncThunk<
|
||||
any,
|
||||
{
|
||||
name: string;
|
||||
registeredAddress: string;
|
||||
totalSlots: string;
|
||||
},
|
||||
{ rejectValue: string }
|
||||
>("Station/createStation", async (data, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await http.post("/create-station", data);
|
||||
toast.success("Station created successfully");
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
toast.error(
|
||||
"Failed to create Station: " +
|
||||
(error.response?.data?.message || "Unknown error")
|
||||
);
|
||||
return rejectWithValue(
|
||||
error.response?.data?.message || "An error occurred"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Update Station details
|
||||
export const updateStation = createAsyncThunk(
|
||||
"updateStation",
|
||||
async ({ id, ...stationData }: Station, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await http.patch(
|
||||
`/update-station/${id}`,
|
||||
stationData
|
||||
);
|
||||
toast.success("Station Deatils updated successfully");
|
||||
return response?.data;
|
||||
} catch (error: any) {
|
||||
toast.error("Error updating the user: " + error);
|
||||
return rejectWithValue(
|
||||
error.response?.data?.message || "An error occurred"
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
export const deleteStation = createAsyncThunk<
|
||||
string,
|
||||
string,
|
||||
{ rejectValue: string }
|
||||
>("deleteStation", async (id, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await http.delete(`/delete-station/${id}`);
|
||||
toast.success(response.data?.message);
|
||||
return id;
|
||||
} catch (error: any) {
|
||||
toast.error("Error deleting the Station" + error);
|
||||
|
||||
return rejectWithValue(
|
||||
error.response?.data?.message || "An error occurred"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export const toggleStatus = createAsyncThunk<
|
||||
any,
|
||||
{ id: string; status: number },
|
||||
{ rejectValue: string }
|
||||
>("Station/toggleStatus", async ({ id, status }, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await http.patch(`${id}`, { status });
|
||||
|
||||
if (response.data.statusCode === 200) {
|
||||
toast.success(
|
||||
response.data.message || "Status updated successfully"
|
||||
);
|
||||
// Return both the response data and the requested status for reliable state updates
|
||||
return {
|
||||
responseData: response.data,
|
||||
id,
|
||||
status,
|
||||
};
|
||||
} else {
|
||||
throw new Error(response.data.message || "Failed to update status");
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(
|
||||
"Error updating status: " + (error.message || "Unknown error")
|
||||
);
|
||||
return rejectWithValue(
|
||||
error.response?.data?.message ||
|
||||
error.message ||
|
||||
"An error occurred"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const stationSlice = createSlice({
|
||||
name: "stations",
|
||||
initialState,
|
||||
reducers: {},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(stationList.pending, (state) => {
|
||||
state.loading = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(
|
||||
stationList.fulfilled,
|
||||
(state, action: PayloadAction<any>) => {
|
||||
state.loading = false;
|
||||
// Properly extract stations from the response data structure
|
||||
state.stations =
|
||||
action.payload.data?.results ||
|
||||
action.payload.data ||
|
||||
[];
|
||||
}
|
||||
)
|
||||
.addCase(stationList.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload || "Failed to fetch stations";
|
||||
})
|
||||
.addCase(createStation.pending, (state) => {
|
||||
state.loading = true;
|
||||
})
|
||||
.addCase(
|
||||
createStation.fulfilled,
|
||||
(state, action: PayloadAction<any>) => {
|
||||
state.loading = false;
|
||||
// Add the newly created station to the state if it exists in the response
|
||||
if (action.payload.data) {
|
||||
state.stations.push(action.payload.data);
|
||||
}
|
||||
}
|
||||
)
|
||||
.addCase(
|
||||
createStation.rejected,
|
||||
(state, action: PayloadAction<string | undefined>) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload || "Failed to create station";
|
||||
}
|
||||
)
|
||||
.addCase(toggleStatus.pending, (state) => {
|
||||
state.loading = true;
|
||||
})
|
||||
.addCase(
|
||||
toggleStatus.fulfilled,
|
||||
(state, action: PayloadAction<any>) => {
|
||||
state.loading = false;
|
||||
|
||||
// Get the id and updated status from the action payload
|
||||
const { id, status } = action.payload;
|
||||
|
||||
// Find and update the station with the new status
|
||||
const stationIndex = state.stations.findIndex(
|
||||
(station) => station.id === id
|
||||
);
|
||||
if (stationIndex !== -1) {
|
||||
state.stations[stationIndex] = {
|
||||
...state.stations[stationIndex],
|
||||
status: status,
|
||||
};
|
||||
}
|
||||
}
|
||||
)
|
||||
.addCase(
|
||||
toggleStatus.rejected,
|
||||
(state, action: PayloadAction<string | undefined>) => {
|
||||
state.loading = false;
|
||||
state.error =
|
||||
action.payload || "Failed to toggle station status";
|
||||
}
|
||||
)
|
||||
.addCase(updateStation.pending, (state) => {
|
||||
state.loading = true;
|
||||
})
|
||||
.addCase(updateStation.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload;
|
||||
})
|
||||
.addCase(updateStation.rejected, (state) => {
|
||||
state.loading = false;
|
||||
})
|
||||
.addCase(deleteStation.pending, (state) => {
|
||||
state.loading = true;
|
||||
})
|
||||
.addCase(deleteStation.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
state.stations = state.stations.filter(
|
||||
(station) => String(station.id) !== String(action.payload)
|
||||
);
|
||||
})
|
||||
.addCase(deleteStation.rejected, (state) => {
|
||||
state.loading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default stationSlice.reducer;
|
|
@ -60,7 +60,7 @@ export const createUser = createAsyncThunk<
|
|||
{ rejectValue: string }
|
||||
>("/CreateUser", async (data, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await http.post("create-user", data);
|
||||
const response = await http.post("/create-user", data);
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(
|
||||
|
@ -73,7 +73,7 @@ export const updateUser = createAsyncThunk(
|
|||
"updateUser",
|
||||
async ({ id, ...userData }: User, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await http.put(`/${id}/update-user`, userData);
|
||||
const response = await http.put(`/update-user/${id}`, userData);
|
||||
toast.success("User updated successfully");
|
||||
return response?.data;
|
||||
} catch (error: any) {
|
||||
|
@ -91,7 +91,7 @@ export const deleteUser = createAsyncThunk<
|
|||
{ rejectValue: string }
|
||||
>("deleteUser", async (id, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await http.delete(`/${id}/delete-user`);
|
||||
const response = await http.delete(`/delete-user/${id}`);
|
||||
toast.success(response.data?.message);
|
||||
return id;
|
||||
} catch (error: any) {
|
||||
|
|
|
@ -18,6 +18,7 @@ const UserList = lazy(() => import("./pages/UserList"));
|
|||
const AddEditRolePage = lazy(() => import("./pages/AddEditRolePage"));
|
||||
const RoleList = lazy(() => import("./pages/RoleList"));
|
||||
const ManagerList = lazy(() => import("./pages/ManagerList"));
|
||||
const StationList = lazy(() => import("./pages/StationList"));
|
||||
|
||||
|
||||
interface ProtectedRouteProps {
|
||||
|
@ -51,79 +52,44 @@ export default function AppRouter() {
|
|||
<Route path="/panel" element={<DashboardLayout />}>
|
||||
<Route
|
||||
path="dashboard"
|
||||
element={
|
||||
<ProtectedRoute
|
||||
|
||||
component={<Dashboard />}
|
||||
/>
|
||||
}
|
||||
element={<ProtectedRoute component={<Dashboard />} />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="admin-list"
|
||||
element={
|
||||
<ProtectedRoute
|
||||
|
||||
component={<AdminList />}
|
||||
/>
|
||||
}
|
||||
element={<ProtectedRoute component={<AdminList />} />}
|
||||
/>
|
||||
<Route
|
||||
path="user-list"
|
||||
element={
|
||||
<ProtectedRoute
|
||||
|
||||
component={<UserList />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="manager-list"
|
||||
element={
|
||||
<ProtectedRoute
|
||||
|
||||
component={<ManagerList />}
|
||||
/>
|
||||
}
|
||||
element={<ProtectedRoute component={<UserList />} />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="manager-list"
|
||||
element={<ProtectedRoute component={<ManagerList />} />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="role-list"
|
||||
element={
|
||||
<ProtectedRoute
|
||||
|
||||
component={<RoleList />}
|
||||
/>
|
||||
}
|
||||
element={<ProtectedRoute component={<RoleList />} />}
|
||||
/>
|
||||
<Route
|
||||
path="vehicle-list"
|
||||
element={
|
||||
<ProtectedRoute
|
||||
|
||||
component={<VehicleList />}
|
||||
/>
|
||||
}
|
||||
element={<ProtectedRoute component={<VehicleList />} />}
|
||||
/>
|
||||
<Route
|
||||
path="permissions"
|
||||
element={
|
||||
<ProtectedRoute
|
||||
|
||||
component={<AddEditRolePage />}
|
||||
/>
|
||||
<ProtectedRoute component={<AddEditRolePage />} />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="station-list"
|
||||
element={<ProtectedRoute component={<StationList />} />}
|
||||
/>
|
||||
<Route
|
||||
path="profile"
|
||||
element={
|
||||
<ProtectedRoute
|
||||
|
||||
component={<ProfilePage />}
|
||||
/>
|
||||
}
|
||||
element={<ProtectedRoute component={<ProfilePage />} />}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
|
|
Loading…
Reference in a new issue