Manager station Details API integration

This commit is contained in:
jaanvi 2025-04-16 18:41:21 +05:30
parent f9cda402aa
commit 15fc16ffa9
9 changed files with 288 additions and 185 deletions

View file

@ -23,7 +23,7 @@ const AddManagerStationModal = ({ open, handleClose }: any) => {
formState: { errors },
} = useForm();
const [isAvailable, setIsAvailable] = useState<boolean>(true);
const [available, setavailable] = useState<boolean>(true);
const onSubmit = (data: any) => {
const { connectorType, power, price } = data;
@ -32,7 +32,7 @@ const AddManagerStationModal = ({ open, handleClose }: any) => {
connectorType,
power,
price,
isAvailable,
available,
};
dispatch(addStationDetails(payload));
@ -128,11 +128,11 @@ const AddManagerStationModal = ({ open, handleClose }: any) => {
<FormControlLabel
control={
<Switch
checked={isAvailable}
onChange={() => setIsAvailable((prev) => !prev)}
checked={available}
onChange={() => setavailable((prev) => !prev)}
/>
}
label={isAvailable ? "Available" : "Not Available"}
label={available ? "Available" : "Not Available"}
sx={{ mt: 2 }}
/>

View file

@ -106,6 +106,7 @@ interface CustomTableProps {
handleStatusToggle?: (id: string, currentStatus: number) => void;
tableType: string; // Adding tableType prop to change header text dynamically
handleClickOpen?: () => void;
}
const CustomTable: React.FC<CustomTableProps> = ({

View file

@ -5,31 +5,34 @@ import {
Typography,
Modal,
CircularProgress,
FormControlLabel,
Switch,
} 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";
updateStationDetails,
stationDetailList,
} from "../../redux/slices/managerStationSlice"; // <-- Update path if needed
import { CustomIconButton, CustomTextField } from "../AddUserModal/styled.css";
import { AppDispatch } from "../../redux/store/store";
import { toast } from "sonner";
// Defining props for the modal
interface EditManagerStationModalProp {
interface EditStationModalProps {
open: boolean;
handleClose: () => void;
editRow: any; // Slot data including id
}
interface FormData {
startTime: string;
endTime: string;
isAvailable: boolean;
connectorType: string;
power: number; // Changed to number
price: number; // Changed to number
available: boolean;
}
const EditSlotModal: React.FC<EditManagerStationModalProp> = ({
const EditManagerStationModal: React.FC<EditStationModalProps> = ({
open,
handleClose,
editRow,
@ -43,57 +46,56 @@ const EditSlotModal: React.FC<EditManagerStationModalProp> = ({
reset,
} = useForm<FormData>({
defaultValues: {
startTime: "",
endTime: "",
isAvailable: false,
connectorType: "",
power: 0, // Default to 0
price: 0, // Default to 0
available: false,
},
});
const [loading, setLoading] = useState(false);
const [isAvailable, setIsAvailable] = useState<boolean>(
editRow?.isAvailable || false
const [available, setAvailable] = useState<boolean>(
editRow?.available || 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);
setValue("connectorType", editRow.connectorType);
setValue("power", Number(editRow.power));
setValue("price", Number(editRow.price));
setAvailable(editRow.available);
} 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,
console.log("Updating station with data:", data);
const availabilityStatus = available ? true : false;
const response = await dispatch(
updateStationDetails({
id: editRow.id,
connectorType: data.connectorType,
power: Number(data.power),
price: Number(data.price),
available: availabilityStatus,
})
).unwrap();
// Fetch the updated slots after updating the slot
dispatch(fetchManagersSlots());
console.log("Update response:", response);
// Close the modal after successful submission
dispatch(stationDetailList());
handleClose();
reset(); // Reset the form
reset();
toast.success("Station details updated successfully");
} catch (error) {
console.error("Error updating slot:", error);
// Handle error if needed (e.g., show toast notification)
console.error("Update failed", error);
toast.error("Failed to update station details");
} finally {
setLoading(false); // Stop loading state after completion
setLoading(false);
}
}
};
@ -102,12 +104,10 @@ const EditSlotModal: React.FC<EditManagerStationModalProp> = ({
<Modal
open={open}
onClose={(e, reason) => {
if (reason === "backdropClick") {
return;
}
handleClose(); // Close modal when clicking cross or cancel
if (reason === "backdropClick") return;
handleClose();
}}
aria-labelledby="edit-slot-modal"
aria-labelledby="edit-station-modal"
>
<Box
component="form"
@ -132,82 +132,104 @@ const EditSlotModal: React.FC<EditManagerStationModalProp> = ({
alignItems: "center",
}}
>
<Typography variant="h6" fontWeight={600} fontSize={"16px"}>
Edit Slot
<Typography variant="h6" fontWeight={600}>
Edit Station Details
</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%" }}>
{/* Connector Type */}
<Box sx={{ flex: "1 1 100%" }}>
<Typography variant="body2" fontWeight={500} mb={0.5}>
Start Time
Connector Type
</Typography>
<Controller
name="startTime"
name="connectorType"
control={control}
rules={{ required: "Start Time is required" }}
rules={{ required: "Connector type is required" }}
render={({ field }) => (
<CustomTextField
{...field}
type="time"
fullWidth
size="small"
error={!!errors.startTime}
helperText={errors.startTime?.message}
error={!!errors.connectorType}
helperText={errors.connectorType?.message}
/>
)}
/>
</Box>
{/* End Time */}
{/* Power */}
<Box sx={{ flex: "1 1 48%" }}>
<Typography variant="body2" fontWeight={500} mb={0.5}>
End Time
Power
</Typography>
<Controller
name="endTime"
name="power"
control={control}
rules={{ required: "End Time is required" }}
rules={{ required: "Power is required" }}
render={({ field }) => (
<CustomTextField
{...field}
type="time"
type="number"
fullWidth
size="small"
error={!!errors.endTime}
helperText={errors.endTime?.message}
error={!!errors.power}
helperText={errors.power?.message}
/>
)}
/>
</Box>
{/* Availability Toggle */}
{/* Price */}
<Box sx={{ flex: "1 1 48%" }}>
<Typography variant="body2" fontWeight={500} mb={0.5}>
Price
</Typography>
<Controller
name="price"
control={control}
rules={{ required: "Price is required" }}
render={({ field }) => (
<CustomTextField
{...field}
type="number"
fullWidth
size="small"
error={!!errors.price}
helperText={errors.price?.message}
/>
)}
/>
</Box>
{/* Available 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>
<FormControlLabel
control={
<Switch
checked={available}
onChange={() => {
const toggle = !available;
setAvailable(toggle);
setValue("available", toggle);
}}
/>
}
label={available ? "Available" : "Not Available"}
sx={{ mt: 1 }}
/>
</Box>
</Box>
@ -225,12 +247,12 @@ const EditSlotModal: React.FC<EditManagerStationModalProp> = ({
width: "117px",
"&:hover": { backgroundColor: "#439BC1" },
}}
disabled={loading} // Disable the button during loading state
disabled={loading}
>
{loading ? (
<CircularProgress size={24} color="inherit" />
) : (
"Update Slot"
"Update"
)}
</Button>
</Box>
@ -239,4 +261,4 @@ const EditSlotModal: React.FC<EditManagerStationModalProp> = ({
);
};
export default EditSlotModal;
export default EditManagerStationModal;

View file

@ -46,13 +46,19 @@ export default function MainGrid() {
<Box
sx={{
width: "100%",
maxWidth: "1600px",
maxWidth: "1800px",
mx: "auto",
px: { xs: 2, sm: 1, md: 0 },
// background: "#DFECF1",
}}
>
{/* Dashboard Header */}
<Typography component="h2" variant="h6" sx={{ mb: 2 ,fontSize:"30px", fontWeight:"600"}}>
<Typography
component="h2"
variant="h6"
sx={{ mb: 2, fontSize: "30px", fontWeight: "600" }}
>
Dashboard
</Typography>

View file

@ -0,0 +1,72 @@
import React from "react";
import {
Box,
Button,
Dialog,
DialogTitle,
DialogContent,
Tabs,
Tab,
Typography,
} from "@mui/material";
import { useSelector } from "react-redux";
import { RootState } from "../../redux/reducers";
type Slot = {
id: string;
startTime: string;
endTime: string;
isAvailable?: boolean;
};
interface SlotPickerModalProps {
open: boolean;
handleClose: () => void;
selectedSlotId: string | null;
onSelect: (slot: Slot) => any;
}
export default function SlotPickerModal({
open,
handleClose,
selectedSlotId,
onSelect,
}: SlotPickerModalProps) {
const availableSlots = useSelector(
(state: RootState) => state.slotReducer.availableSlots || []
);
// eslint-disable-next-line @typescript-eslint/no-redeclare
return (
<Dialog open={open} onClose={handleClose} maxWidth="md" fullWidth>
<DialogTitle>Select a Time Slot</DialogTitle>
<DialogContent>
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 2 }}>
{availableSlots.map((slot) => {
const isDisabled = !slot?.isAvailable;
const label = `${slot?.startTime} - ${slot?.endTime}`;
return (
<Button
key={slot?.id}
variant={
selectedSlotId === slot?.id
? "contained"
: "outlined"
}
color="primary"
disabled={isDisabled}
onClick={() => onSelect(slot)}
>
{label}
</Button>
);
})}
</Box>
</DialogContent>
</Dialog>
);
}

View file

@ -1,14 +1,7 @@
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,
@ -22,12 +15,11 @@ 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(
const stationDetails = useSelector(
(state: RootState) => state.managerStationReducer.stationDetails
);
@ -47,75 +39,79 @@ export default function ManagerStationDetails() {
reset();
};
const handleAddManager = async (data: {
const handleAddStationDetails = async (data: {
stationId: string;
connectorType: string;
power: string;
price: string;
power: number;
price: number;
available: boolean;
}) => {
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
await dispatch(addStationDetails(data));
await dispatch(stationDetailList()); // Fetch updated station list
handleCloseModal(); // Close 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
power: number,
price: number,
available: boolean
) => {
try {
console.log("Updating station with data:", { id, connectorType, power, price, available })
if (!id) {
console.error("Error: id is missing!");
return;
}
await dispatch(
updateStationDetails({
id,
connectorType,
power,
price,
isAvailable: false,
available,
})
);
await dispatch(stationDetailList());
handleCloseModal();
).unwrap();
await dispatch(stationDetailList()); // Fetch updated station list
handleCloseModal(); // Close modal
} catch (error) {
console.error("Update failed", error);
}
};
// Columns for the table
const categoryColumns: Column[] = [
{ id: "srno", label: "Sr No" },
{ id: "stationName", label: "Station Name" },
{ id: "registeredAddress", label: "Station Location" },
{ id: "stationAddress", 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" },
{ id: "available", label: "Is Available", align: "center" },
{ id: "action", label: "Action", align: "center" }, // Action column for Edit and Delete
];
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",
};
})
: [];
// Rows for the table
const categoryRows = stationDetails.map((station, index) => ({
...station, // Include all station properties
srno: index + 1,
stationName: station.stationName ?? "NA",
stationAddress: station.stationAddress ?? "NA",
connectorType: station.connectorType ?? "NA",
power: station.power ?? "NA",
price: station.price ?? "NA",
available: station.available ? "Yes" : "No",
}));
return (
<>
{/* Custom Table to show manager list */}
{/* Custom Table to show station details */}
<CustomTable
columns={categoryColumns}
rows={categoryRows}
@ -124,22 +120,24 @@ export default function ManagerStationDetails() {
setViewModal={setViewModal}
viewModal={viewModal}
setRowData={setRowData}
setModalOpen={() => setEditModalOpen(true)}
setModalOpen={() => setEditModalOpen(true)} // Open edit modal when row is clicked
tableType="manager-station"
handleClickOpen={handleClickOpen}
handleClickOpen={handleClickOpen} // Open add modal
/>
{/* Add Manager Modal */}
{/* Add Manager Station Modal */}
<AddManagerStationModal
open={addModalOpen}
handleClose={handleCloseModal}
handleAddManager={handleAddManager}
handleAddStationDetails={handleAddStationDetails}
/>
{/* Edit Manager Modal */}
{/* Edit Manager Station Modal */}
<EditManagerStationModal
open={editModalOpen}
handleClose={handleCloseModal}
handleUpdate={handleUpdate}
editRow={rowData}
handleUpdate={handleUpdate} // Pass the update handler
editRow={rowData} // Pass the selected row data for editing
/>
</>
);

View file

@ -1,17 +1,16 @@
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
import http from "../../lib/https";
import { toast } from "sonner";
interface StationDetails {
id: number;
stationAddress: string;
stationName: string;
registeredAddress: string;
id: number;
managerId?: number;
stationId?: number;
connectorType: string;
power: string;
price: string;
isAvailable: boolean;
power: number;
price: number;
available: boolean;
}
interface StationDetailState {
@ -20,7 +19,6 @@ interface StationDetailState {
error: string | null;
}
// Initial state
const initialState: StationDetailState = {
stationDetails: [],
loading: false,
@ -37,11 +35,11 @@ export const stationDetailList = createAsyncThunk<
const token = localStorage?.getItem("authToken");
if (!token) throw new Error("No token found");
const response = await http.get("/manager-station-details");
const response = await http.get("/get-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);
toast.error("Error Fetching Station Details: " + error.message);
return rejectWithValue(
error?.response?.data?.message || "An error occurred"
);
@ -53,21 +51,20 @@ export const addStationDetails = createAsyncThunk<
StationDetails,
{
connectorType: string;
power: string;
price: string;
isAvailable: boolean;
power: number; // Changed to number
price: number; // Changed to number
available: boolean;
},
{ rejectValue: string }
>("addManager", async (data, { rejectWithValue }) => {
try {
const response = await http.post(
"create-manager-station-details",
data
);
const response = await http.post("add-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);
toast.error(
"Error creating Station details: " + error.response?.data?.message
);
return rejectWithValue(
error.response?.data?.message || "An error occurred"
);
@ -80,26 +77,23 @@ export const updateStationDetails = createAsyncThunk<
{
id: number;
connectorType: string;
power: string;
price: string;
isAvailable: boolean;
power: number;
price: number;
available: 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 }
const response = await http.patch(
`update-station-details/${id}`,
managerStationData
);
toast.success("Station Details updated successfully");
return response?.data;
return response.data; // Return the updated data
} catch (error: any) {
toast.error("Error updating manager: " + error.message);
toast.error("Error updating station details: " + error.message);
return rejectWithValue(
error.response?.data?.message || "An error occurred"
);
@ -114,7 +108,7 @@ export const deleteStationDetails = createAsyncThunk<
{ rejectValue: string }
>("deleteManager", async (id, { rejectWithValue }) => {
try {
await http.delete(`/delete-manager/${id}`);
await http.delete(`remove-station-details/${id}`);
toast.success("Station details deleted successfully!");
return id;
} catch (error: any) {
@ -132,7 +126,7 @@ const managerStationSlice = createSlice({
reducers: {},
extraReducers: (builder) => {
builder
// Fetch Managers
// Fetch Station Details
.addCase(stationDetailList.pending, (state) => {
state.loading = true;
state.error = null;
@ -146,10 +140,11 @@ const managerStationSlice = createSlice({
)
.addCase(stationDetailList.rejected, (state, action) => {
state.loading = false;
state.error = action.payload || "Failed to fetch managers";
state.error =
action.payload || "Failed to fetch station details";
})
// Add Station details
// Add Station Details
.addCase(addStationDetails.pending, (state) => {
state.loading = true;
})
@ -162,30 +157,39 @@ const managerStationSlice = createSlice({
)
.addCase(addStationDetails.rejected, (state, action) => {
state.loading = false;
state.error = action.payload || "Failed to add manager";
state.error = action.payload || "Failed to add station details";
})
// Update Manager
// Update Station Details
.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.fulfilled,
(state, action: PayloadAction<StationDetails>) => {
state.loading = false;
// Update the station details in the array
const index = state.stationDetails.findIndex(
(station) => station.id === action.payload.id
);
if (index !== -1) {
// Only update the fields that have changed
state.stationDetails[index] = {
...state.stationDetails[index],
...action.payload, // Merge the updated fields
};
}
}
})
)
.addCase(updateStationDetails.rejected, (state, action) => {
state.loading = false;
state.error = action.payload || "Failed to update manager";
state.error =
action.payload || "Failed to update station details";
})
// Delete Manager
// Delete Station Details
.addCase(deleteStationDetails.pending, (state) => {
state.loading = true;
})
@ -198,7 +202,8 @@ const managerStationSlice = createSlice({
})
.addCase(deleteStationDetails.rejected, (state, action) => {
state.loading = false;
state.error = action.payload || "Failed to delete manager";
state.error =
action.payload || "Failed to delete station details";
});
},
});

View file

@ -23,6 +23,7 @@ export default function AppTheme(props: AppThemeProps) {
palette: {
mode: "dark",
background: {
// default: "#ECF4FA",
default: "#111111",
paper: "#1e1e1e",
},
@ -33,7 +34,6 @@ export default function AppTheme(props: AppThemeProps) {
},
typography: {
fontFamily: "Gilroy",
},
cssVariables: {
colorSchemeSelector: "data-mui-color-scheme",
@ -49,7 +49,6 @@ export default function AppTheme(props: AppThemeProps) {
...surfacesCustomizations,
...themeComponents,
},
});
}, [disableCustomTheme, themeComponents]);