Add Welcome Section of Landing Page and Implements AllMangers and AllAvailableSlots APi Integration

This commit is contained in:
jaanvi 2025-04-04 12:37:04 +05:30
parent 3a103a4b36
commit e39499d574
13 changed files with 473 additions and 53 deletions

BIN
public/tablet-img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

View file

@ -203,17 +203,15 @@ const CustomTable: React.FC<CustomTableProps> = ({
handleClose();
};
const filteredRows = rows.filter((row) => {
if (!searchQuery.trim()) return true; // Return all rows if searchQuery is empty or whitespace
const lowerCaseQuery = searchQuery.toLowerCase().trim();
return (
(row.name && row.name.toLowerCase().includes(lowerCaseQuery)) ||
(row.registeredAddress &&
row.registeredAddress.toLowerCase().includes(lowerCaseQuery))
);
});
const filteredRows = rows.filter((row) => {
if (!searchQuery.trim()) return true; // Return all rows if searchQuery is empty or whitespace
const lowerCaseQuery = searchQuery.toLowerCase().trim();
return (
(row.name && row.name.toLowerCase().includes(lowerCaseQuery)) ||
(row.registeredAddress &&
row.registeredAddress.toLowerCase().includes(lowerCaseQuery))
);
});
const indexOfLastRow = currentPage * usersPerPage;
const indexOfFirstRow = indexOfLastRow - usersPerPage;
@ -248,13 +246,15 @@ const filteredRows = rows.filter((row) => {
{(() => {
switch (tableType) {
case "admin":
return "Admin";
return "Admins";
case "role":
return "Roles";
case "user":
return "Users";
case "manager":
return "Managers";
case "all-managers":
return "Managers";
case "vehicle":
return "Vehicles";
case "station":
@ -264,7 +264,9 @@ const filteredRows = rows.filter((row) => {
case "booking":
return "Booking";
case "slots":
return "Slot";
return "Slots";
case "all-available-slots":
return "Available Slots";
default:
return "List";
}
@ -326,8 +328,13 @@ const filteredRows = rows.filter((row) => {
}}
>
{!(
user?.userType === "user" &&
(tableType === "slots" )) && (
(user?.userType === "user" &&
tableType === "all-available-slots") ||
(user?.userType === "superadmin" &&
tableType === "all-managers") ||
(user?.userType === "superadmin" &&
tableType === "user")
) && (
<Button
sx={{
backgroundColor: "#52ACDF",

View file

@ -38,7 +38,7 @@ export default function MenuContent({ hidden }: PropType) {
userRole === "superadmin" && {
text: "Manager",
icon: <ManageAccountsOutlinedIcon />,
url: "/panel/manager-list",
url: "/panel/all-managers-list",
},
userRole === "superadmin" && {
text: "User",
@ -89,7 +89,7 @@ export default function MenuContent({ hidden }: PropType) {
userRole === "user" && {
text: "Available Slots",
icon: <ChecklistSharpIcon />,
url: "/panel/slot-list", // Placeholder for now
url: "/panel/all-available-slots", // Placeholder for now
},
userRole === "user" && {
text: "Near By Stations",

View file

@ -84,12 +84,13 @@ export default function AdminList() {
{ id: "email", label: "Email" },
{ id: "phone", label: "Phone" },
{ id: "registeredAddress", label: "Address" },
{ id: "userType", label: "Role" },
// { id: "userType", label: "Role" },
{ id: "action", label: "Action", align: "center" },
];
const categoryRows = admins?.map(
const categoryRows = admins
?.filter((admin) => admin?.userType === "admin").map(
(
admin: {
id: string;

View file

@ -0,0 +1,54 @@
import { useEffect } from "react";
import CustomTable, { Column } from "../../components/CustomTable/customTable";
import { useDispatch, useSelector } from "react-redux";
import { RootState, AppDispatch } from "../../redux/store/store";
import { AllmanagerList,} from "../../redux/slices/managerSlice";
export default function ManagerList() {
const dispatch = useDispatch<AppDispatch>();
const managers = useSelector(
(state: RootState) => state.managerReducer.managers
);
// Fetch manager list on component mount
useEffect(() => {
dispatch(AllmanagerList());
}, [dispatch]);
// 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: "stationName", label: "Station Name" },
{ id: "registeredAddress", label: "Station Location" },
];
// Prepare rows for the table
const categoryRows = managers?.length
? managers.map((manager, index) => {
const station = manager?.chargingStation;
return {
id: manager.id,
srno: index + 1,
name: manager.name,
email: manager.email,
phone: manager.phone ?? "NA",
stationName: station?.name ?? "NA",
registeredAddress: station?.registeredAddress ?? "NA",
};
})
: [];
return (
<>
{/* Display manager list */}
<CustomTable
columns={categoryColumns}
rows={categoryRows}
tableType="all-managers"
/>
</>
);
}

View file

@ -0,0 +1,67 @@
import { useEffect, useState } from "react";
import CustomTable, { Column } from "../../components/CustomTable/customTable";
import { useDispatch, useSelector } from "react-redux";
import { RootState, AppDispatch } from "../../redux/store/store";
import {
fetchAvailableSlots,
} from "../../redux/slices/slotSlice";
import dayjs from "dayjs";
export default function EVSlotList() {
const dispatch = useDispatch<AppDispatch>();
const availableSlots = useSelector(
(state: RootState) => state?.slotReducer.availableSlots
);
useEffect(() => {
dispatch(fetchAvailableSlots());
}, [dispatch]);
const slotColumns: Column[] = [
{ id: "srno", label: "Sr No" },
{ id: "name", label: "Station Name" },
{ id: "stationId", label: "Station Id" },
{ id: "date", label: "Date" },
{ id: "startTime", label: "Start Time" },
{ id: "endTime", label: "End Time" },
{ id: "isAvailable", label: "Is Available", align: "center" },
];
const slotRows = availableSlots?.length
? availableSlots.map((slot, index) => {
const formattedDate = dayjs(slot?.date).format("YYYY-MM-DD");
const startTime = dayjs(slot?.startTime).format("HH:mm");
const endTime = dayjs(slot?.endTime).format("HH:mm");
return {
srno: index + 1,
id: slot?.id ?? "NA",
stationId: slot?.stationId ?? "NA",
name: slot?.ChargingStation?.name ?? "NA",
date: formattedDate ?? "NA",
startTime: startTime ?? "NA",
endTime: endTime ?? "NA",
isAvailable: slot?.isAvailable ? "Yes" : "No",
};
})
: [];
return (
<>
<CustomTable
columns={slotColumns}
rows={slotRows}
tableType="all-available-slots"
/>
</>
);
}

View file

@ -5,7 +5,7 @@ import { RootState, AppDispatch } from "../../redux/store/store";
import { useForm } from "react-hook-form";
import {
createSlot,
fetchAvailableSlots,
fetchManagersSlots,
updateSlot,
} from "../../redux/slices/slotSlice";
import AddSlotModal from "../../components/AddSlotModal/addSlotModal";
@ -21,10 +21,10 @@ export default function EVSlotList() {
const dispatch = useDispatch<AppDispatch>();
const availableSlots = useSelector(
(state: RootState) => state?.slotReducer.availableSlots
);
);
const { user } = useSelector((state: RootState) => state?.profileReducer);
useEffect(() => {
dispatch(fetchAvailableSlots());
dispatch(fetchManagersSlots());
}, [dispatch]);
const handleClickOpen = () => {
@ -46,7 +46,7 @@ export default function EVSlotList() {
}) => {
try {
await dispatch(createSlot(data));
await dispatch(fetchAvailableSlots());
await dispatch(fetchManagersSlots());
handleCloseModal();
} catch (error) {
console.error("Error adding slot", error);
@ -74,16 +74,16 @@ export default function EVSlotList() {
})
).unwrap();
await dispatch(fetchAvailableSlots());
await dispatch(fetchManagersSlots());
handleCloseModal();
} catch (error) {
console.error("Update failed", error);
}
};
const slotColumns: Column[] = [
{ id: "srno", label: "Sr No" },
{ id: "name", label: "Station Name" },
{ id: "stationName", label: "Station Name" },
{ id: "stationId", label: "Station Id" },
{ id: "date", label: "Date" },
{ id: "startTime", label: "Start Time" },
@ -104,7 +104,7 @@ export default function EVSlotList() {
srno: index + 1,
id: slot?.id ?? "NA",
stationId: slot?.stationId ?? "NA",
name: slot?.ChargingStation?.name ?? "NA",
stationName: slot?.stationName?? "NA",
date: formattedDate ?? "NA",
startTime: startTime ?? "NA",
endTime: endTime ?? "NA",

View file

@ -1,19 +1,55 @@
import React from "react";
import { Box, Button, Container, Grid, Typography } from "@mui/material";
import {
Box,
Button,
Card,
CardContent,
Container,
Grid,
Typography,
} from "@mui/material";
import { useNavigate } from "react-router-dom"; // Import useNavigate for navigation
import SearchIcon from "@mui/icons-material/Search";
import EvStationIcon from "@mui/icons-material/EvStation";
import BatteryChargingFullIcon from "@mui/icons-material/BatteryChargingFull";
import LocationOnIcon from "@mui/icons-material/LocationOn";
const features = [
{
icon: <EvStationIcon fontSize="large" />,
title: "Seamless Charging Experience",
description:
"Find and book EV charging stations effortlessly with DigiEv's smart platform.",
},
{
icon: <BatteryChargingFullIcon fontSize="large" />,
title: "Real-Time Availability",
description:
"Check live station availability and optimize your route for an efficient charge.",
},
{
icon: <LocationOnIcon fontSize="large" />,
title: "Widespread Network",
description:
"Access a vast network of EV stations across multiple locations with ease.",
},
{
icon: <SearchIcon fontSize="large" />, // New feature icon
title: "Smart Search Functionality",
description:
"Find EV stations nearby with advanced filters for location, availability, and pricing.",
},
];
const LandingPage = () => {
const navigate = useNavigate();
const navigate = useNavigate();
const handleLoginClick = () => {
navigate("/login");
navigate("/login");
};
return (
<Box
sx={{
background: `
linear-gradient(135deg, #0D0D0D 20%, #1C1E22 80%),
radial-gradient(circle at 30%, rgb(241, 201, 119) 100%, rgba(255, 204, 102, 0) 50%)
@ -21,6 +57,8 @@ const LandingPage = () => {
color: "white",
minHeight: "100vh",
fontFamily: "Inter",
//display: "flex", // Ensures the children align correctly
//flexDirection: "column",
}}
>
{/* Navbar */}
@ -57,8 +95,8 @@ const LandingPage = () => {
}}
/>
<Button
type="button" // Changed to "button" to avoid form submission
onClick={handleLoginClick} // Trigger navigation on click
type="button"
onClick={handleLoginClick}
sx={{
backgroundColor: "#52ACDF",
color: "white",
@ -174,15 +212,18 @@ const LandingPage = () => {
<Container
maxWidth="lg"
sx={{
// width: "1320px",
// height: "239px",
width: "100%",
position: "relative",
boxShadow: "0px 8px 20px rgba(166, 210, 235, 0.5)", // Bluish box shadow
boxShadow: "0px 8px 20px rgba(166, 210, 235, 0.2)", // Bluish box shadow
background:
"linear-gradient(135deg, rgba(82, 172, 223, 0.2), rgba(0,0,0,0.3))",
borderRadius: "16px",
py: 4,
px: 3,
"linear-gradient(135deg, rgba(82, 172, 223, 0.2), rgba(0,0,0,0.3))",
borderRadius: "16px",
py: 4,
px: 3,
mt: 15,
}}
>
<Grid container spacing={4} textAlign="center">
@ -193,11 +234,18 @@ const LandingPage = () => {
fontWeight={600}
fontSize={"80px"}
lineHeight={"100px"}
fontFamily={"Inter"}
fontFamily={"Poppins"}
>
50+
</Typography>
<Typography variant="body2" color="#FFFFFF">
<Typography
variant="body2"
color="#FFFFFF"
fontFamily={"Inter"}
fontWeight={400}
fontSize={"16px"}
lineHeight={"22px"}
>
Successful Digital Transformations
</Typography>
</Grid>
@ -208,11 +256,18 @@ const LandingPage = () => {
fontWeight={600}
fontSize={"80px"}
lineHeight={"100px"}
fontFamily={"Inter"}
fontFamily={"Poppins"}
>
100%
</Typography>
<Typography variant="body2" color="#FFFFFF">
<Typography
variant="body2"
color="#FFFFFF"
fontFamily={"Inter"}
fontWeight={400}
fontSize={"16px"}
lineHeight={"22px"}
>
Client Satisfaction
</Typography>
</Grid>
@ -223,11 +278,18 @@ const LandingPage = () => {
fontWeight={600}
fontSize={"80px"}
lineHeight={"100px"}
fontFamily={"Inter"}
fontFamily={"Poppins"}
>
20+
</Typography>
<Typography variant="body2" color="#FFFFFF">
<Typography
variant="body2"
color="#FFFFFF"
fontFamily={"Inter"}
fontWeight={400}
fontSize={"16px"}
lineHeight={"22px"}
>
Global Partnerships
</Typography>
</Grid>
@ -239,16 +301,160 @@ const LandingPage = () => {
fontWeight={600}
fontSize={"80px"}
lineHeight={"100px"}
fontFamily={"Inter"}
fontFamily={"Poppins"}
>
10+
</Typography>
<Typography variant="body2" color="#FFFFFF">
<Typography
variant="body2"
color="#FFFFFF"
fontFamily={"Inter"}
fontWeight={400}
fontSize={"16px"}
lineHeight={"22px"}
>
Years of Innovation
</Typography>
</Grid>
</Grid>
</Container>
<Container
maxWidth="lg"
sx={{
minHeight: "100vh",
textAlign: "center",
// width: "1320px",
// height: "349.82px",
width: "100%",
boxShadow: "0px 8px 20px rgba(166, 210, 235, 0.2)", // Bluish box shadow
background:
"linear-gradient(135deg, rgba(82, 172, 223, 0.2), rgba(0,0,0,0.3))",
borderRadius: "16px",
py: 4,
px: 3,
mt: 10,
}}
>
<Typography
variant="h4"
fontWeight={600}
fontFamily={"Inter"}
fontSize={"40px"}
lineHeight={"100%"}
color="#FFFFFF"
>
Welcome to <span style={{ color: "#52ACDF" }}>DigiEv</span>{" "}
- Your EV Charging Partner
</Typography>
<Typography variant="h6" mt={1} fontWeight={600}>
Simplifying Electric Vehicle Charging
</Typography>
<Typography
variant="body1"
mt={2}
maxWidth="600px"
mx="auto"
fontFamily={"Inter"}
fontSize={"20px"}
fontWeight={400}
lineHeight={"100%"}
color="#D9D8D8"
>
DigiEv helps EV owners locate, book, and manage their
charging needs efficiently. With our intuitive platform, you
can ensure a smooth and hassle-free charging experience.
</Typography>
<Box mt={5}>
<img
src="/tablet-img.png"
alt="DigiEv Dashboard"
style={{
width: "80%",
maxWidth: "800px",
borderRadius: "10px",
}}
/>
</Box>
<Grid
container
spacing={4}
justifyContent="center"
alignItems="center"
mt={2}
>
{features.map((feature, index) => (
<Grid item xs={12} sm={6} md={3} key={index}>
<Card
sx={{
boxShadow:
"0px 8px 20px rgba(166, 210, 235, 0.2)", // Bluish box shadow
background:
"linear-gradient(135deg, rgba(82, 172, 223, 0.2), rgba(0,0,0,0.3))",
borderRadius: "18px",
width: "247px", // Fixed width
height: "200px", // Fixed height
display: "flex", // Flexbox for centering content
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
textAlign: "center",
}}
>
<CardContent
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
height: "100%", // Fills the parent card
textAlign: "center",
}}
>
<Box
sx={{
display: "flex",
justifyContent: "center", // Centers the icon horizontally
alignItems: "center", // Centers the icon vertically
mt: 1,
mb: 2,
color: "#D9D8D8",
}}
>
{feature.icon}
</Box>
<Typography
variant="h6"
sx={{
color: "#52ACDF",
fontFamily: "Inter",
fontWeight: 600,
fontSize: "18px", // Slightly reduced to fit uniformly
lineHeight: "160%",
}}
>
{feature.title}
</Typography>
<Typography
variant="body2"
sx={{
color: "#D9D8D8",
fontFamily: "Inter",
fontWeight: 400,
fontSize: "14px",
lineHeight: "20px",
mt: 1,
}}
>
{feature.description}
</Typography>
</CardContent>
</Card>
</Grid>
))}
</Grid>
</Container>
</Box>
);
};

View file

@ -20,7 +20,7 @@ export default function UserList() {
const dispatch = useDispatch<AppDispatch>();
const users = useSelector((state: RootState) => state.userReducer.users);
const { user } = useSelector((state: RootState) => state?.profileReducer);
useEffect(() => {
dispatch(userList());
}, [dispatch]);
@ -78,7 +78,9 @@ export default function UserList() {
{ id: "email", label: "Email" },
{ id: "phone", label: "Phone" },
{ id: "action", label: "Action", align: "center" },
...(user?.userType === "admin"
? [{ id: "action", label: "Action", align: "center" as const }]
: []),
];
const categoryRows = users?.length
? users.map((user, index) => ({

View file

@ -13,9 +13,10 @@ interface User {
}
interface Admin {
Admins: any;
id: string;
name: string;
role: string;
userType: string;
email: string;
phone: string;
registeredAddress: string;

View file

@ -46,7 +46,25 @@ export const managerList = createAsyncThunk<
);
}
});
export const AllmanagerList = createAsyncThunk<
Manager[],
void,
{ rejectValue: string }
>("fetchAllManagers", async (_, { rejectWithValue }) => {
try {
const token = localStorage?.getItem("authToken");
if (!token) throw new Error("No token found");
const response = await http.get("/all-manager-list");
if (!response.data?.data) throw new Error("Invalid API response");
return response.data.data;
} catch (error: any) {
toast.error("Error Fetching Managers: " + error.message);
return rejectWithValue(
error?.response?.data?.message || "An error occurred"
);
}
});
// Create Manager (Async Thunk)
export const addManager = createAsyncThunk<
Manager,
@ -132,6 +150,21 @@ const managerSlice = createSlice({
state.loading = false;
state.error = action.payload || "Failed to fetch managers";
})
.addCase(AllmanagerList.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(
AllmanagerList.fulfilled,
(state, action: PayloadAction<Manager[]>) => {
state.loading = false;
state.managers = action.payload;
}
)
.addCase(AllmanagerList.rejected, (state, action) => {
state.loading = false;
state.error = action.payload || "Failed to fetch managers";
})
// Add Manager
.addCase(addManager.pending, (state) => {

View file

@ -10,7 +10,7 @@ interface Slot {
startTime: string;
endTime: string;
isAvailable: boolean;
stationName:string;
ChargingStation: { name: string };
}
@ -50,7 +50,27 @@ export const fetchAvailableSlots = createAsyncThunk<
);
}
});
export const fetchManagersSlots = createAsyncThunk<
Slot[],
void,
{ rejectValue: string }
>("fetchManagersSlots", async (_, { rejectWithValue }) => {
try {
const token = localStorage?.getItem("authToken");
if (!token) throw new Error("No token found");
const response = await http.get("/manager-slots");
if (!response.data?.data) throw new Error("Invalid API response");
return response.data.data;
} catch (error: any) {
toast.error("Error Fetching Profile" + error);
return rejectWithValue(
error?.response?.data?.message || "An error occurred"
);
}
});
export const createSlot = createAsyncThunk<
Slot,
{
@ -152,6 +172,22 @@ const slotSlice = createSlice({
state.error =
action.payload || "Failed to fetch available slots";
})
.addCase(fetchManagersSlots.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(
fetchManagersSlots.fulfilled,
(state, action: PayloadAction<Slot[]>) => {
state.loading = false;
state.availableSlots = action.payload;
}
)
.addCase(fetchManagersSlots.rejected, (state, action) => {
state.loading = false;
state.error =
action.payload || "Failed to fetch available slots";
})
.addCase(createSlot.pending, (state) => {
state.loading = true;
})

View file

@ -24,7 +24,8 @@ const EVSlotManagement = lazy(() => import("./pages/EVSlotManagement"));
const BookingList = lazy(() => import("./pages/BookingList"));
const EvSlotList = lazy(() => import("./pages/EvSlotList"));
const ExternalStationList = lazy(() => import("./pages/ExternalStationList/externalStationList.tsx"));
const AllManagersList = lazy(() => import("./pages/AllMangersList"));
const AvailableSlotsList = lazy(() => import("./pages/AvailableSlotsList"));
interface ProtectedRouteProps {
// caps: string[];
component: React.ReactNode;
@ -44,7 +45,7 @@ export default function AppRouter() {
<Suspense fallback={<LoadingComponent />}>
<BaseRoutes>
{/* Default Route */}
<Route path="" element={<LandingPage /> } />
<Route path="" element={<LandingPage />} />
{/* Auth Routes */}
<Route path="">
@ -117,6 +118,18 @@ export default function AppRouter() {
/>
}
/>
<Route
path="all-managers-list"
element={
<ProtectedRoute component={<AllManagersList />} />
}
/>
<Route
path="all-available-slots"
element={
<ProtectedRoute component={<AvailableSlotsList />} />
}
/>
</Route>
{/* Catch-all Route */}