APi integration for slot maangement and add DigiEvLogo

This commit is contained in:
jaanvi 2025-03-19 18:30:53 +05:30
parent 25f4563484
commit 80f603703c
12 changed files with 714 additions and 145 deletions

BIN
public/DigiEVLogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
public/Digilogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View file

@ -0,0 +1,316 @@
import React, { useEffect, useState } from "react";
import {
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Button,
Typography,
IconButton,
Box,
Snackbar,
Stack,
Tabs,
Tab,
Card,
} from "@mui/material";
import { CustomTextField } from "../../components/AddEditUserModel/styled.css.tsx";
import AddCircleIcon from "@mui/icons-material/AddCircle";
import DeleteIcon from "@mui/icons-material/Delete";
import CalendarTodayRoundedIcon from "@mui/icons-material/CalendarTodayRounded";
import { LocalizationProvider, DatePicker } from "@mui/x-date-pickers";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import dayjs from "dayjs";
import { useDispatch, useSelector } from "react-redux";
import {
fetchAvailableSlots,
createSlot,
deleteSlot,
} from "../../redux/slices/slotSlice.ts";
const days = [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
];
interface AddSlotModalProps {
open: boolean;
onClose: () => void;
}
export default function AddSlotModal({ open, onClose }: AddSlotModalProps) {
const [selectedDay, setSelectedDay] = useState("Monday");
const [openingTime, setOpeningTime] = useState("");
const [closingTime, setClosingTime] = useState("");
const [breakTime, setBreakTime] = useState<any>([]);
const [error, setError] = useState<string>("");
const [successMessage, setSuccessMessage] = useState<string | null>(null);
const [selectedDate, setSelectedDate] = useState<dayjs.Dayjs | null>(
dayjs()
);
const dispatch = useDispatch();
// Fetch slots from the Redux state
const {
slots,
loading,
error: apiError,
} = useSelector((state: any) => state.slotReducer.slots);
// useEffect(() => {
// if (selectedDay) {
// dispatch(fetchAvailableSlots(1)); // Replace with actual stationId if needed
// }
// }, [dispatch, selectedDay]);
const addSlot = (start: string, end: string) => {
const selectedDateFormatted = selectedDate.format("YYYY-MM-DD");
const startTime = start;
const endTime = end;
dispatch(
createSlot({
stationId: 1,
date: selectedDateFormatted,
startHour: startTime,
endHour: endTime,
isAvailable: true,
})
);
};
const addBreak = (start: string, end: string) => {
if (!start || !end) {
setError("Break Start and End times are required.");
return;
}
setBreakTime([...breakTime, { start, end }]);
setError("");
};
const deleteSlot = (start: string, end: string) => {
const updatedSlots = slots[selectedDay].filter(
(slot: any) => !(slot.start === start && slot.end === end)
);
setSlots({
...slots,
[selectedDay]: updatedSlots,
});
};
const deleteBreak = (start: string, end: string) => {
const updatedBreaks = breakTime.filter(
(breakItem: any) =>
!(breakItem.start === start && breakItem.end === end)
);
setBreakTime(updatedBreaks);
};
const saveData = () => {
if (!openingTime || !closingTime) {
setError("Operating hours are required.");
return;
}
setSuccessMessage(
`Data for ${selectedDay} has been saved successfully!`
);
setError("");
};
const handleCloseSnackbar = () => {
setSuccessMessage(null);
};
return (
<Dialog open={open} onClose={onClose} maxWidth="md" fullWidth>
<DialogTitle align="center">EV Station Slot Management</DialogTitle>
<DialogContent>
{/* Date Picker */}
<Box
sx={{
display: "flex",
justifyContent: "space-between",
mt: 3,
}}
>
<Typography variant="h4" gutterBottom>
Select Date
</Typography>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DatePicker
value={selectedDate}
onChange={(newDate) => setSelectedDate(newDate)}
renderInput={(props) => (
<CustomTextField
{...props}
fullWidth
label="Select Date"
InputProps={{
startAdornment: (
<CalendarTodayRoundedIcon fontSize="small" />
),
}}
/>
)}
/>
</LocalizationProvider>
</Box>
<Tabs
value={selectedDay}
onChange={(event, newValue) => setSelectedDay(newValue)}
variant="scrollable"
scrollButtons="auto"
sx={{ mt: 3 }}
>
{days.map((day) => (
<Tab
key={day}
label={day}
value={day}
sx={{
fontWeight: "bold",
fontSize: "16px",
"&.Mui-selected": {
backgroundColor: "#52ACDF",
color: "white",
borderRadius: "8px",
},
}}
/>
))}
</Tabs>
{/* Operating Hours */}
<Card sx={{ mt: 2, p: 2 }}>
<Typography variant="h6" gutterBottom>
Set Operating Hours for {selectedDay}
</Typography>
<Box sx={{ display: "flex", gap: 2, mt: 1 }}>
<CustomTextField
type="time"
label="Opening Time"
value={openingTime}
onChange={(e) => setOpeningTime(e.target.value)}
fullWidth
/>
<CustomTextField
type="time"
label="Closing Time"
value={closingTime}
onChange={(e) => setClosingTime(e.target.value)}
fullWidth
/>
</Box>
</Card>
{/* Slots */}
<Card sx={{ mt: 2, p: 2 }}>
<Typography variant="h6">Add Slots</Typography>
<Box sx={{ display: "flex", gap: 2, mt: 1 }}>
<CustomTextField
type="time"
label="Start Time"
id="slotStart"
fullWidth
/>
<CustomTextField
type="time"
label="End Time"
id="slotEnd"
fullWidth
/>
<Button
sx={{
backgroundColor: "#52ACDF",
color: "white",
borderRadius: "8px",
width: "250px",
"&:hover": { backgroundColor: "#439BC1" },
}}
onClick={() =>
addSlot(
document.getElementById("slotStart").value,
document.getElementById("slotEnd").value
)
}
>
<AddCircleIcon sx={{ mr: 1 }} /> Add
</Button>
</Box>
{error && (
<Typography color="error" mt={1}>
{error}
</Typography>
)}
</Card>
{/* Break Time */}
<Card sx={{ mt: 2, p: 2 }}>
<Typography variant="h6">Break Time</Typography>
<Box sx={{ display: "flex", gap: 2, mt: 1 }}>
<CustomTextField
type="time"
label="Break Start"
id="breakStart"
fullWidth
/>
<CustomTextField
type="time"
label="Break End"
id="breakEnd"
fullWidth
/>
<Button
sx={{
backgroundColor: "#52ACDF",
color: "white",
borderRadius: "8px",
width: "250px",
"&:hover": { backgroundColor: "#439BC1" },
}}
onClick={() =>
addBreak(
document.getElementById("breakStart").value,
document.getElementById("breakEnd").value
)
}
>
<AddCircleIcon sx={{ mr: 1 }} /> Add
</Button>
</Box>
</Card>
{/* Slots and Break Times Lists */}
{/* (Content for slots and breaks remains the same as in the original implementation) */}
{/* Success Snackbar */}
<Snackbar
open={!!successMessage}
autoHideDuration={6000}
onClose={handleCloseSnackbar}
message={successMessage}
/>
</DialogContent>
<DialogActions>
<Button onClick={onClose} color="primary">
Cancel
</Button>
<Button onClick={saveData} color="primary">
Save
</Button>
</DialogActions>
</Dialog>
);
}

View file

@ -36,6 +36,7 @@ import UserViewModal from "../Modals/UserViewModal/index.tsx";
import { deleteUser, userList } from "../../redux/slices/userSlice.ts"; import { deleteUser, userList } from "../../redux/slices/userSlice.ts";
import { deleteStation } from "../../redux/slices/stationSlice.ts"; import { deleteStation } from "../../redux/slices/stationSlice.ts";
import StationViewModal from "../Modals/StationViewModal/index.tsx"; import StationViewModal from "../Modals/StationViewModal/index.tsx";
import { fetchAvailableSlots } from "../../redux/slices/slotSlice.ts";
// Styled components for customization // Styled components for customization
const StyledTableCell = styled(TableCell)(({ theme }) => ({ const StyledTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: { [`&.${tableCellClasses.head}`]: {
@ -159,23 +160,26 @@ const CustomTable: React.FC<CustomTableProps> = ({
const handleViewButton = (id: string | undefined) => { const handleViewButton = (id: string | undefined) => {
if (!id) console.error("ID not found", id); if (!id) console.error("ID not found", id);
// switch (tableType) { switch (tableType) {
// case "admin": case "admin":
// dispatch(adminList()); dispatch(adminList());
// break; break;
// case "vehicle": case "vehicle":
// dispatch(vehicleList()); dispatch(vehicleList());
// break; break;
// case "manager": case "manager":
// dispatch(managerList()); dispatch(managerList());
// break; break;
// case "user": case "user":
// dispatch(userList()); dispatch(userList());
// break; break;
// default: case "slot":
// console.error("Unknown table type:", tableType); dispatch(fetchAvailableSlots(1));
// return; break;
// } default:
console.error("Unknown table type:", tableType);
return;
}
setViewModal(false); setViewModal(false);
}; };

View file

@ -65,6 +65,11 @@ export default function MenuContent({ hidden }: PropType) {
icon: <ManageAccountsOutlinedIcon />, icon: <ManageAccountsOutlinedIcon />,
url: "/panel/EVslots", // Placeholder for now url: "/panel/EVslots", // Placeholder for now
}, },
userRole === "manager" && {
text: "Slot List",
icon: <ManageAccountsOutlinedIcon />,
url: "/panel/slot-list", // Placeholder for now
},
]; ];
const filteredMenuItems = baseMenuItems.filter(Boolean); const filteredMenuItems = baseMenuItems.filter(Boolean);

View file

@ -55,6 +55,52 @@ export default function SideMenu() {
}, },
}} }}
> >
<Box
sx={{
display: "flex",
flexDirection: "row",
alignItems: "center",
pt: 2,
pl: 2,
}}
>
<Avatar
alt="Logo"
src="/Digilogo.png"
sx={{
width: "50px",
height: "50px",
}}
/>
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
pt: 2,
textAlign: "center",
}}
>
{/* Digi EV Text Section */}
<Typography
variant="body2"
color="#D9D8D8"
sx={{
fontWeight: "bold",
fontSize: "16px",
}}
>
Digi EV
</Typography>
<Typography variant="subtitle2" color="#D9D8D8" >
{user?.userType || "N/A"}
</Typography>
</Box>
</Box>
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
@ -69,7 +115,6 @@ export default function SideMenu() {
</Button> </Button>
</Box> </Box>
<MenuContent hidden={open} /> <MenuContent hidden={open} />
</Drawer> </Drawer>
); );
} }

View file

@ -97,6 +97,22 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
height: "auto", height: "auto",
}} }}
> >
<Box
sx={{
textAlign: "center",
marginBottom: "1rem",
}}
>
<img
src="/DigiEVLogo.png"
alt="Logo"
style={{
justifyContent: "center",
width: "250px",
height: "auto",
}}
/>
</Box>
<Typography <Typography
variant="h3" variant="h3"
sx={{ sx={{

View file

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { import {
Tabs, Tabs,
Tab, Tab,
@ -19,6 +19,14 @@ import { LocalizationProvider, DatePicker } from "@mui/x-date-pickers";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import {
fetchAvailableSlots,
createSlot,
deleteSlot,
} from "../../redux/slices/slotSlice.ts";
import { RootState } from "../../redux/reducers.ts";
const days = [ const days = [
"Monday", "Monday",
"Tuesday", "Tuesday",
@ -32,7 +40,6 @@ export default function EVSlotManagement() {
const [selectedDay, setSelectedDay] = useState("Monday"); const [selectedDay, setSelectedDay] = useState("Monday");
const [openingTime, setOpeningTime] = useState(""); const [openingTime, setOpeningTime] = useState("");
const [closingTime, setClosingTime] = useState(""); const [closingTime, setClosingTime] = useState("");
const [slots, setSlots] = useState<any>({});
const [breakTime, setBreakTime] = useState<any>([]); const [breakTime, setBreakTime] = useState<any>([]);
const [error, setError] = useState<string>(""); const [error, setError] = useState<string>("");
const [successMessage, setSuccessMessage] = useState<string | null>(null); const [successMessage, setSuccessMessage] = useState<string | null>(null);
@ -40,39 +47,48 @@ export default function EVSlotManagement() {
dayjs() dayjs()
); );
const navigate = useNavigate(); const navigate = useNavigate();
const dispatch = useDispatch();
// Fetch slots from the Redux state
const { slots, availableSlots } = useSelector(
(state: RootState) => state?.slotReducer
);
const manager = useSelector((state: RootState) => state.managerReducer.managers);
// console.log("availableSlots",availableSlots);
useEffect(() => {
dispatch(fetchAvailableSlots());
}, [dispatch]);
const handleBack = () => { const handleBack = () => {
navigate("/panel/dashboard"); // Navigate back to Role List navigate("/panel/dashboard");
};
// Add slot function with basic validation
const addSlot = (start: string, end: string) => {
if (!start || !end) {
setError("Start and End times are required.");
return;
}
if (slots[selectedDay]) {
// Check if start time is before end time
const newSlot = { start, end };
const overlap = slots[selectedDay].some(
(slot: any) =>
(slot.start <= newSlot.start && slot.end > newSlot.start) ||
(slot.start < newSlot.end && slot.end >= newSlot.end)
);
if (overlap) {
setError("The selected time overlaps with an existing slot.");
return;
}
} else {
slots[selectedDay] = [];
}
slots[selectedDay].push({ start, end });
setSlots({ ...slots });
setError("");
}; };
// Add break time function
const addSlot = (start: string, end: string) => {
const selectedDateFormatted = selectedDate.format("YYYY-MM-DD");
const startTime = start;
const endTime = end;
dispatch(
createSlot({
stationId: manager.stationId,
date: selectedDateFormatted,
startHour: startTime,
endHour: endTime,
isAvailable: true, // Ensure it's available by default
})
);
};
// Delete slot function that dispatches deleteSlot
const addBreak = (start: string, end: string) => { const addBreak = (start: string, end: string) => {
if (!start || !end) { if (!start || !end) {
setError("Break Start and End times are required."); setError("Break Start and End times are required.");
@ -84,16 +100,21 @@ export default function EVSlotManagement() {
}; };
// Delete slot function // Delete slot function
const deleteSlot = (start: string, end: string) => { // const deleteSlot = (start: string, end: string) => {
const updatedSlots = slots[selectedDay].filter( // const updatedSlots = slots[selectedDay].filter(
(slot: any) => !(slot.start === start && slot.end === end) // (slot: any) => !(slot.start === start && slot.end === end)
); // );
setSlots({ // setSlots({
...slots, // ...slots,
[selectedDay]: updatedSlots, // [selectedDay]: updatedSlots,
}); // });
// };
const handleDeleteSlot = (slotId: number) => {
// Call the deleteSlot action with the correct slotId
dispatch(deleteSlot(slotId));
}; };
// Delete break function // Delete break function
const deleteBreak = (start: string, end: string) => { const deleteBreak = (start: string, end: string) => {
const updatedBreaks = breakTime.filter( const updatedBreaks = breakTime.filter(
@ -121,12 +142,13 @@ export default function EVSlotManagement() {
const handleCloseSnackbar = () => { const handleCloseSnackbar = () => {
setSuccessMessage(null); setSuccessMessage(null);
}; };
console.log("Slots: ",slots)
return ( return (
<Box sx={{ maxWidth: 600, mx: "auto", p: 2 }}> <Box sx={{ maxWidth: 600, mx: "auto", p: 2 }}>
<Typography variant="h4" gutterBottom align="center"> <Typography variant="h4" gutterBottom align="center">
EV Station Slot Management EV Station Slot Management
</Typography> </Typography>
{/* Date Picker */} {/* Date Picker */}
<Box <Box
sx={{ display: "flex", justifyContent: "space-between", mt: 3 }} sx={{ display: "flex", justifyContent: "space-between", mt: 3 }}
@ -153,6 +175,7 @@ export default function EVSlotManagement() {
/> />
</LocalizationProvider> </LocalizationProvider>
</Box> </Box>
<Tabs <Tabs
value={selectedDay} value={selectedDay}
onChange={(event, newValue) => setSelectedDay(newValue)} onChange={(event, newValue) => setSelectedDay(newValue)}
@ -199,7 +222,6 @@ export default function EVSlotManagement() {
/> />
</Box> </Box>
</Card> </Card>
<Card sx={{ mt: 2, p: 2 }}> <Card sx={{ mt: 2, p: 2 }}>
<Typography variant="h6">Add Slots</Typography> <Typography variant="h6">Add Slots</Typography>
<Box sx={{ display: "flex", gap: 2, mt: 1 }}> <Box sx={{ display: "flex", gap: 2, mt: 1 }}>
@ -239,7 +261,6 @@ export default function EVSlotManagement() {
</Typography> </Typography>
)} )}
</Card> </Card>
<Card sx={{ mt: 2, p: 2 }}> <Card sx={{ mt: 2, p: 2 }}>
<Typography variant="h6">Break Time</Typography> <Typography variant="h6">Break Time</Typography>
<Box sx={{ display: "flex", gap: 2, mt: 1 }}> <Box sx={{ display: "flex", gap: 2, mt: 1 }}>
@ -274,34 +295,43 @@ export default function EVSlotManagement() {
</Button> </Button>
</Box> </Box>
</Card> </Card>
<Card sx={{ mt: 2, p: 2 }}> <Card sx={{ mt: 2, p: 2 }}>
<Typography variant="h6" gutterBottom> <Typography variant="h6" gutterBottom>
Slots for {selectedDay} Slots for {selectedDay}
</Typography> </Typography>
{slots[selectedDay]?.length ? (
slots[selectedDay].map((slot: any, index: number) => ( {/* Ensure that `slots` is always an array */}
<Box {Array.isArray(availableSlots) && availableSlots.length ? (
key={index} availableSlots
sx={{ .filter(
display: "flex", (slot: any) =>
justifyContent: "space-between", dayjs(slot.date).format("YYYY-MM-DD") ===
alignItems: "center", selectedDate.format("YYYY-MM-DD") &&
mb: 1, dayjs(slot.date).format("dddd") === selectedDay
}} )
> .map((slot: any, index: number) => (
<Typography variant="body1"> <Box
{slot.start} - {slot.end} key={index}
</Typography> sx={{
<IconButton display: "flex",
onClick={() => deleteSlot(slot.start, slot.end)} justifyContent: "space-between",
alignItems: "center",
mb: 1,
}}
> >
<DeleteIcon color="error" /> <Typography variant="body1">
</IconButton> {dayjs(slot.startHour).format("HH:mm")} -{" "}
</Box> {dayjs(slot.endHour).format("HH:mm")}
)) </Typography>
<IconButton
onClick={() => handleDeleteSlot(slot.id)}
>
<DeleteIcon color="error" />
</IconButton>
</Box>
))
) : ( ) : (
<Typography>No slots added</Typography> <Typography>No slots added for {selectedDay}</Typography>
)} )}
</Card> </Card>
@ -351,8 +381,6 @@ export default function EVSlotManagement() {
> >
Back Back
</Button> </Button>
{/* Save Button */}
<Button <Button
type="submit" type="submit"
sx={{ sx={{
@ -367,6 +395,7 @@ export default function EVSlotManagement() {
Save Save
</Button> </Button>
</Box> </Box>
{/* Success Snackbar */} {/* Success Snackbar */}
<Snackbar <Snackbar
open={!!successMessage} open={!!successMessage}

View file

@ -0,0 +1,147 @@
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 {
createSlot,
updateSlot,
fetchAvailableSlots,
deleteSlot,
} from "../../redux/slices/slotSlice";
import EVSlotManagement from "../EVSlotManagement";
import AddSlotModal from "../../components/AddSlotModal";
import dayjs from "dayjs";
// import AddSlotModal from "../../components/AddSlotModal";
// import EditSlotModal from "../../components/EditSlotModal";
export default function EvSlotList() {
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 slots = useSelector((state: RootState) => state.slotReducer.slots);
useEffect(() => {
dispatch(fetchAvailableSlots(1)); // Fetch slots when component mounts
}, [dispatch]);
const handleClickOpen = () => {
setRowData(null); // Reset row data when opening for new slot
setAddModalOpen(true);
};
const handleCloseModal = () => {
setAddModalOpen(false);
setEditModalOpen(false);
setRowData(null);
reset();
};
const handleAddSlot = async (data: {
startHour: string;
endHour: string;
stationId: number;
date: string;
}) => {
try {
await dispatch(createSlot(data)); // Create the slot with all necessary fields
await dispatch(fetchAvailableSlots()); // Refetch updated slots
handleCloseModal(); // Close modal after successful creation
} catch (error) {
console.error("Error adding slot", error);
toast.error("Failed to add slot");
}
};
// const handleUpdateSlot = async (
// id: number,
// startHour: string,
// endHour: string
// ) => {
// try {
// await dispatch(updateSlot({ id, startHour, endHour }));
// await dispatch(fetchAvailableSlots());
// handleCloseModal();
// } catch (error) {
// console.error("Update failed", error);
// }
// };
// const handleDeleteSlot = async (id: number) => {
// try {
// await dispatch(deleteSlot(id));
// await dispatch(fetchAvailableSlots());
// } catch (error) {
// console.error("Delete failed", error);
// }
// };
const slotColumns: Column[] = [
{ id: "srno", label: "Sr No" },
{ id: "stationId", label: "Station Id" },
{ id: "date", label: "Date" },
{ id: "startHour", label: "Start Hour" },
{ id: "endHour", label: "End Hour" },
{ id: "action", label: "Action", align: "center" },
];
const filteredSlots = slots?.filter(
(slot) =>
dayjs(slot.startTime)
.format("HH:mm")
.toLowerCase()
.includes(searchTerm.toLowerCase()) ||
dayjs(slot.endTime)
.format("HH:mm")
.toLowerCase()
.includes(searchTerm.toLowerCase())
);
const slotRows = filteredSlots?.length
? filteredSlots.map((slot: any, index: number) => ({
id: slot?.id,
srno: index + 1,
startHour: dayjs(slot?.startTime).format("HH:mm"), // Format startTime
endHour: dayjs(slot?.endTime).format("HH:mm"), // Format endTime
}))
: [];
return (
<>
<CustomTable
columns={slotColumns}
rows={slotRows}
setDeleteModal={setDeleteModal}
deleteModal={deleteModal}
setViewModal={setViewModal}
viewModal={viewModal}
setRowData={setRowData}
setModalOpen={() => setEditModalOpen(true)}
tableType="slot"
handleClickOpen={handleClickOpen}
// handleDelete={handleDeleteSlot} // Allow deleting
/>
<AddSlotModal
open={addModalOpen}
handleClose={handleCloseModal}
handleAddSlot={handleAddSlot}
/>
{/* <EditSlotModal
open={editModalOpen}
handleClose={handleCloseModal}
handleUpdateSlot={handleUpdateSlot}
editRow={rowData}
/> */}
</>
);
}

View file

@ -5,10 +5,11 @@ import { toast } from "sonner";
// Define TypeScript types // Define TypeScript types
interface Slot { interface Slot {
id: number; id: number;
date: string;
stationId: number; stationId: number;
startTime: Date; startHour: Date;
endTime: Date; endHour: Date;
status: number; isAvailable: boolean;
} }
@ -27,74 +28,73 @@ const initialState: SlotState = {
error: null, error: null,
}; };
// Fetch Available Slots
export const fetchAvailableSlots = createAsyncThunk< export const fetchAvailableSlots = createAsyncThunk<
Slot[], // Return type Slot[],
number, // Argument type (stationId) void,
{ rejectValue: string } { rejectValue: string }
>("slots/fetchAvailableSlots", async (stationId, { rejectWithValue }) => { >("fetchVehicles", async (_, { rejectWithValue }) => {
try {
const response = await http.get("/available", { stationId });
return response.data.data; // Return the available slot data
} catch (error: any) {
toast.error("Error fetching available slots: " + error?.message);
return rejectWithValue(error.response?.data?.message || "An error occurred");
}
});
export const createSlot = createAsyncThunk<
Slot, // Return type (Slot)
{
stationId: number;
startTime: Date;
endTime: Date;
isAvailable: boolean;
}, // Argument type (slot data)
{ rejectValue: string } // Error type
>("slots/createSlot", async (slotData, { rejectWithValue }) => {
try { try {
const { stationId, startTime, endTime, isAvailable } = slotData; const token = localStorage?.getItem("authToken");
if (!token) throw new Error("No token found");
// Ensure that the start and end time are correctly formatted in ISO 8601 const response = await http.get("/available");
const startDateTime = new Date(startTime).toISOString(); // convert to ISO string
const endDateTime = new Date(endTime).toISOString(); // convert to ISO string
// Make sure the formatted times are valid if (!response.data?.data) throw new Error("Invalid API response");
if (
isNaN(new Date(startDateTime).getTime()) ||
isNaN(new Date(endDateTime).getTime())
) {
throw new Error("Invalid date format");
}
const payload = {
stationId,
startTime: startDateTime,
endTime: endDateTime,
isAvailable,
};
// Send the request to create the slot
const response = await http.post("/create-slot", payload);
// Show success message
toast.success("Slot created successfully");
// Return the response data (created slot)
return response.data.data; return response.data.data;
} catch (error: any) { } catch (error: any) {
// Show error message toast.error("Error Fetching Profile" + error);
toast.error("Error creating slot: " + error?.message);
// Return a detailed error message if possible
return rejectWithValue( return rejectWithValue(
error.response?.data?.message || "An error occurred" error?.response?.data?.message || "An error occurred"
); );
} }
}); });
export const createSlot = createAsyncThunk<
Slot,
{
stationId: number;
date: string;
startHour: string;
endHour: string;
isAvailable: boolean;
},
{ rejectValue: string }
>(
"slots/createSlot",
async (
{ stationId, date, startHour, endHour, isAvailable },
{ rejectWithValue }
) => {
try {
const payload = {
stationId,
date, // This should be in the format 'YYYY-MM-DD'
startHour, // This should be in 'HH:mm' format
endHour,
isAvailable,
};
// Send the request to create the slot
const response = await http.post("/create-slot", payload);
// Show success message
toast.success("Slot created successfully");
// Return the response data (created slot)
return response.data.data;
} catch (error: any) {
// Show error message
toast.error("Error creating slot: " + error?.message);
// Return a detailed error message if possible
return rejectWithValue(
error.response?.data?.message || "An error occurred"
);
}
}
);
// Update Slot details // Update Slot details
export const updateSlot = createAsyncThunk< export const updateSlot = createAsyncThunk<
@ -119,7 +119,7 @@ export const deleteSlot = createAsyncThunk<
{ rejectValue: string } { rejectValue: string }
>("slots/deleteSlot", async (id, { rejectWithValue }) => { >("slots/deleteSlot", async (id, { rejectWithValue }) => {
try { try {
const response = await http.delete(`/slots/${id}`); const response = await http.delete(`/delete-slot/${id}`);
toast.success("Slot deleted successfully"); toast.success("Slot deleted successfully");
return id; // Return the id of the deleted slot return id; // Return the id of the deleted slot
} catch (error: any) { } catch (error: any) {

View file

@ -20,6 +20,7 @@ const RoleList = lazy(() => import("./pages/RoleList"));
const ManagerList = lazy(() => import("./pages/ManagerList")); const ManagerList = lazy(() => import("./pages/ManagerList"));
const StationList = lazy(() => import("./pages/StationList")); const StationList = lazy(() => import("./pages/StationList"));
const EVSlotManagement = lazy(() => import("./pages/EVSlotManagement")); const EVSlotManagement = lazy(() => import("./pages/EVSlotManagement"));
const EvSlotList = lazy(() => import("./pages/EvSlotList"));
interface ProtectedRouteProps { interface ProtectedRouteProps {
@ -98,6 +99,12 @@ export default function AppRouter() {
<ProtectedRoute component={<EVSlotManagement />} /> <ProtectedRoute component={<EVSlotManagement />} />
} }
/> />
<Route
path="slot-list"
element={
<ProtectedRoute component={<EvSlotList />} />
}
/>
</Route> </Route>
{/* Catch-all Route */} {/* Catch-all Route */}