APi integration for slot maangement and add DigiEvLogo
This commit is contained in:
parent
25f4563484
commit
80f603703c
BIN
public/DigiEVLogo.png
Normal file
BIN
public/DigiEVLogo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2 KiB |
BIN
public/Digilogo.png
Normal file
BIN
public/Digilogo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
BIN
public/digimantra_labs_logo.jpeg
Normal file
BIN
public/digimantra_labs_logo.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
316
src/components/AddSlotModal/index.tsx
Normal file
316
src/components/AddSlotModal/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -36,6 +36,7 @@ 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";
|
||||
import { fetchAvailableSlots } from "../../redux/slices/slotSlice.ts";
|
||||
// Styled components for customization
|
||||
const StyledTableCell = styled(TableCell)(({ theme }) => ({
|
||||
[`&.${tableCellClasses.head}`]: {
|
||||
|
@ -159,23 +160,26 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
|||
|
||||
const handleViewButton = (id: string | undefined) => {
|
||||
if (!id) console.error("ID not found", id);
|
||||
// switch (tableType) {
|
||||
// case "admin":
|
||||
// dispatch(adminList());
|
||||
// break;
|
||||
// case "vehicle":
|
||||
// dispatch(vehicleList());
|
||||
// break;
|
||||
// case "manager":
|
||||
// dispatch(managerList());
|
||||
// break;
|
||||
// case "user":
|
||||
// dispatch(userList());
|
||||
// break;
|
||||
// default:
|
||||
// console.error("Unknown table type:", tableType);
|
||||
// return;
|
||||
// }
|
||||
switch (tableType) {
|
||||
case "admin":
|
||||
dispatch(adminList());
|
||||
break;
|
||||
case "vehicle":
|
||||
dispatch(vehicleList());
|
||||
break;
|
||||
case "manager":
|
||||
dispatch(managerList());
|
||||
break;
|
||||
case "user":
|
||||
dispatch(userList());
|
||||
break;
|
||||
case "slot":
|
||||
dispatch(fetchAvailableSlots(1));
|
||||
break;
|
||||
default:
|
||||
console.error("Unknown table type:", tableType);
|
||||
return;
|
||||
}
|
||||
|
||||
setViewModal(false);
|
||||
};
|
||||
|
|
|
@ -65,6 +65,11 @@ export default function MenuContent({ hidden }: PropType) {
|
|||
icon: <ManageAccountsOutlinedIcon />,
|
||||
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);
|
||||
|
|
|
@ -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
|
||||
sx={{
|
||||
display: "flex",
|
||||
|
@ -69,7 +115,6 @@ export default function SideMenu() {
|
|||
</Button>
|
||||
</Box>
|
||||
<MenuContent hidden={open} />
|
||||
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -97,6 +97,22 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
|
|||
height: "auto",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
textAlign: "center",
|
||||
marginBottom: "1rem",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/DigiEVLogo.png"
|
||||
alt="Logo"
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
width: "250px",
|
||||
height: "auto",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Typography
|
||||
variant="h3"
|
||||
sx={{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
Tabs,
|
||||
Tab,
|
||||
|
@ -19,6 +19,14 @@ import { LocalizationProvider, DatePicker } from "@mui/x-date-pickers";
|
|||
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
|
||||
import dayjs from "dayjs";
|
||||
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 = [
|
||||
"Monday",
|
||||
"Tuesday",
|
||||
|
@ -32,7 +40,6 @@ export default function EVSlotManagement() {
|
|||
const [selectedDay, setSelectedDay] = useState("Monday");
|
||||
const [openingTime, setOpeningTime] = useState("");
|
||||
const [closingTime, setClosingTime] = useState("");
|
||||
const [slots, setSlots] = useState<any>({});
|
||||
const [breakTime, setBreakTime] = useState<any>([]);
|
||||
const [error, setError] = useState<string>("");
|
||||
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
||||
|
@ -40,39 +47,48 @@ export default function EVSlotManagement() {
|
|||
dayjs()
|
||||
);
|
||||
const navigate = useNavigate();
|
||||
const handleBack = () => {
|
||||
navigate("/panel/dashboard"); // Navigate back to Role List
|
||||
};
|
||||
// Add slot function with basic validation
|
||||
const addSlot = (start: string, end: string) => {
|
||||
if (!start || !end) {
|
||||
setError("Start and End times are required.");
|
||||
return;
|
||||
}
|
||||
const dispatch = useDispatch();
|
||||
|
||||
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)
|
||||
// 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);
|
||||
|
||||
if (overlap) {
|
||||
setError("The selected time overlaps with an existing slot.");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
slots[selectedDay] = [];
|
||||
}
|
||||
useEffect(() => {
|
||||
dispatch(fetchAvailableSlots());
|
||||
}, [dispatch]);
|
||||
|
||||
slots[selectedDay].push({ start, end });
|
||||
setSlots({ ...slots });
|
||||
setError("");
|
||||
|
||||
|
||||
const handleBack = () => {
|
||||
navigate("/panel/dashboard");
|
||||
};
|
||||
|
||||
// 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) => {
|
||||
if (!start || !end) {
|
||||
setError("Break Start and End times are required.");
|
||||
|
@ -84,16 +100,21 @@ export default function EVSlotManagement() {
|
|||
};
|
||||
|
||||
// Delete slot function
|
||||
const deleteSlot = (start: string, end: string) => {
|
||||
const updatedSlots = slots[selectedDay].filter(
|
||||
(slot: any) => !(slot.start === start && slot.end === end)
|
||||
);
|
||||
setSlots({
|
||||
...slots,
|
||||
[selectedDay]: updatedSlots,
|
||||
});
|
||||
// const deleteSlot = (start: string, end: string) => {
|
||||
// const updatedSlots = slots[selectedDay].filter(
|
||||
// (slot: any) => !(slot.start === start && slot.end === end)
|
||||
// );
|
||||
// setSlots({
|
||||
// ...slots,
|
||||
// [selectedDay]: updatedSlots,
|
||||
// });
|
||||
// };
|
||||
const handleDeleteSlot = (slotId: number) => {
|
||||
// Call the deleteSlot action with the correct slotId
|
||||
dispatch(deleteSlot(slotId));
|
||||
};
|
||||
|
||||
|
||||
// Delete break function
|
||||
const deleteBreak = (start: string, end: string) => {
|
||||
const updatedBreaks = breakTime.filter(
|
||||
|
@ -121,12 +142,13 @@ export default function EVSlotManagement() {
|
|||
const handleCloseSnackbar = () => {
|
||||
setSuccessMessage(null);
|
||||
};
|
||||
|
||||
console.log("Slots: ",slots)
|
||||
return (
|
||||
<Box sx={{ maxWidth: 600, mx: "auto", p: 2 }}>
|
||||
<Typography variant="h4" gutterBottom align="center">
|
||||
EV Station Slot Management
|
||||
</Typography>
|
||||
|
||||
{/* Date Picker */}
|
||||
<Box
|
||||
sx={{ display: "flex", justifyContent: "space-between", mt: 3 }}
|
||||
|
@ -153,6 +175,7 @@ export default function EVSlotManagement() {
|
|||
/>
|
||||
</LocalizationProvider>
|
||||
</Box>
|
||||
|
||||
<Tabs
|
||||
value={selectedDay}
|
||||
onChange={(event, newValue) => setSelectedDay(newValue)}
|
||||
|
@ -199,7 +222,6 @@ export default function EVSlotManagement() {
|
|||
/>
|
||||
</Box>
|
||||
</Card>
|
||||
|
||||
<Card sx={{ mt: 2, p: 2 }}>
|
||||
<Typography variant="h6">Add Slots</Typography>
|
||||
<Box sx={{ display: "flex", gap: 2, mt: 1 }}>
|
||||
|
@ -239,7 +261,6 @@ export default function EVSlotManagement() {
|
|||
</Typography>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
<Card sx={{ mt: 2, p: 2 }}>
|
||||
<Typography variant="h6">Break Time</Typography>
|
||||
<Box sx={{ display: "flex", gap: 2, mt: 1 }}>
|
||||
|
@ -274,13 +295,21 @@ export default function EVSlotManagement() {
|
|||
</Button>
|
||||
</Box>
|
||||
</Card>
|
||||
|
||||
<Card sx={{ mt: 2, p: 2 }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Slots for {selectedDay}
|
||||
</Typography>
|
||||
{slots[selectedDay]?.length ? (
|
||||
slots[selectedDay].map((slot: any, index: number) => (
|
||||
|
||||
{/* Ensure that `slots` is always an array */}
|
||||
{Array.isArray(availableSlots) && availableSlots.length ? (
|
||||
availableSlots
|
||||
.filter(
|
||||
(slot: any) =>
|
||||
dayjs(slot.date).format("YYYY-MM-DD") ===
|
||||
selectedDate.format("YYYY-MM-DD") &&
|
||||
dayjs(slot.date).format("dddd") === selectedDay
|
||||
)
|
||||
.map((slot: any, index: number) => (
|
||||
<Box
|
||||
key={index}
|
||||
sx={{
|
||||
|
@ -291,17 +320,18 @@ export default function EVSlotManagement() {
|
|||
}}
|
||||
>
|
||||
<Typography variant="body1">
|
||||
{slot.start} - {slot.end}
|
||||
{dayjs(slot.startHour).format("HH:mm")} -{" "}
|
||||
{dayjs(slot.endHour).format("HH:mm")}
|
||||
</Typography>
|
||||
<IconButton
|
||||
onClick={() => deleteSlot(slot.start, slot.end)}
|
||||
onClick={() => handleDeleteSlot(slot.id)}
|
||||
>
|
||||
<DeleteIcon color="error" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
))
|
||||
) : (
|
||||
<Typography>No slots added</Typography>
|
||||
<Typography>No slots added for {selectedDay}</Typography>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
|
@ -351,8 +381,6 @@ export default function EVSlotManagement() {
|
|||
>
|
||||
Back
|
||||
</Button>
|
||||
{/* Save Button */}
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
sx={{
|
||||
|
@ -367,6 +395,7 @@ export default function EVSlotManagement() {
|
|||
Save
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{/* Success Snackbar */}
|
||||
<Snackbar
|
||||
open={!!successMessage}
|
||||
|
|
147
src/pages/EvSlotList/index.tsx
Normal file
147
src/pages/EvSlotList/index.tsx
Normal 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}
|
||||
/> */}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -5,10 +5,11 @@ import { toast } from "sonner";
|
|||
// Define TypeScript types
|
||||
interface Slot {
|
||||
id: number;
|
||||
date: string;
|
||||
stationId: number;
|
||||
startTime: Date;
|
||||
endTime: Date;
|
||||
status: number;
|
||||
startHour: Date;
|
||||
endHour: Date;
|
||||
isAvailable: boolean;
|
||||
}
|
||||
|
||||
|
||||
|
@ -27,52 +28,51 @@ const initialState: SlotState = {
|
|||
error: null,
|
||||
};
|
||||
|
||||
// Fetch Available Slots
|
||||
|
||||
export const fetchAvailableSlots = createAsyncThunk<
|
||||
Slot[], // Return type
|
||||
number, // Argument type (stationId)
|
||||
Slot[],
|
||||
void,
|
||||
{ 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
|
||||
const token = localStorage?.getItem("authToken");
|
||||
if (!token) throw new Error("No token found");
|
||||
|
||||
const response = await http.get("/available");
|
||||
|
||||
if (!response.data?.data) throw new Error("Invalid API response");
|
||||
|
||||
return response.data.data;
|
||||
} catch (error: any) {
|
||||
toast.error("Error fetching available slots: " + error?.message);
|
||||
return rejectWithValue(error.response?.data?.message || "An error occurred");
|
||||
toast.error("Error Fetching Profile" + error);
|
||||
return rejectWithValue(
|
||||
error?.response?.data?.message || "An error occurred"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
export const createSlot = createAsyncThunk<
|
||||
Slot, // Return type (Slot)
|
||||
Slot,
|
||||
{
|
||||
stationId: number;
|
||||
startTime: Date;
|
||||
endTime: Date;
|
||||
date: string;
|
||||
startHour: string;
|
||||
endHour: string;
|
||||
isAvailable: boolean;
|
||||
}, // Argument type (slot data)
|
||||
{ rejectValue: string } // Error type
|
||||
>("slots/createSlot", async (slotData, { rejectWithValue }) => {
|
||||
},
|
||||
{ rejectValue: string }
|
||||
>(
|
||||
"slots/createSlot",
|
||||
async (
|
||||
{ stationId, date, startHour, endHour, isAvailable },
|
||||
{ rejectWithValue }
|
||||
) => {
|
||||
try {
|
||||
const { stationId, startTime, endTime, isAvailable } = slotData;
|
||||
|
||||
// Ensure that the start and end time are correctly formatted in ISO 8601
|
||||
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 (
|
||||
isNaN(new Date(startDateTime).getTime()) ||
|
||||
isNaN(new Date(endDateTime).getTime())
|
||||
) {
|
||||
throw new Error("Invalid date format");
|
||||
}
|
||||
|
||||
const payload = {
|
||||
stationId,
|
||||
startTime: startDateTime,
|
||||
endTime: endDateTime,
|
||||
date, // This should be in the format 'YYYY-MM-DD'
|
||||
startHour, // This should be in 'HH:mm' format
|
||||
endHour,
|
||||
isAvailable,
|
||||
};
|
||||
|
||||
|
@ -93,8 +93,8 @@ export const createSlot = createAsyncThunk<
|
|||
error.response?.data?.message || "An error occurred"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
// Update Slot details
|
||||
export const updateSlot = createAsyncThunk<
|
||||
|
@ -119,7 +119,7 @@ export const deleteSlot = createAsyncThunk<
|
|||
{ rejectValue: string }
|
||||
>("slots/deleteSlot", async (id, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await http.delete(`/slots/${id}`);
|
||||
const response = await http.delete(`/delete-slot/${id}`);
|
||||
toast.success("Slot deleted successfully");
|
||||
return id; // Return the id of the deleted slot
|
||||
} catch (error: any) {
|
||||
|
|
|
@ -20,6 +20,7 @@ const RoleList = lazy(() => import("./pages/RoleList"));
|
|||
const ManagerList = lazy(() => import("./pages/ManagerList"));
|
||||
const StationList = lazy(() => import("./pages/StationList"));
|
||||
const EVSlotManagement = lazy(() => import("./pages/EVSlotManagement"));
|
||||
const EvSlotList = lazy(() => import("./pages/EvSlotList"));
|
||||
|
||||
|
||||
interface ProtectedRouteProps {
|
||||
|
@ -98,6 +99,12 @@ export default function AppRouter() {
|
|||
<ProtectedRoute component={<EVSlotManagement />} />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="slot-list"
|
||||
element={
|
||||
<ProtectedRoute component={<EvSlotList />} />
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
{/* Catch-all Route */}
|
||||
|
|
Loading…
Reference in a new issue