This commit is contained in:
Eknoor Singh 2025-04-07 10:13:07 +05:30
commit d00747dd6f
29 changed files with 1504 additions and 461 deletions

BIN
public/apple_store.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

BIN
public/dev-bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

BIN
public/developer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
public/google_play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

BIN
public/iMockup - iPhone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

BIN
public/tablet-img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

BIN
public/vector-arrows.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View file

@ -219,7 +219,7 @@ export default function AddBookingModal({
{carNames.map((car, index) => (
<MenuItem
key={car.id}
value={car.id}
value={car.name}
>
{car.name}
</MenuItem>

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;
@ -247,13 +245,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":
@ -263,7 +263,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";
}
@ -325,8 +327,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

@ -12,6 +12,7 @@ import { useDispatch } from "react-redux";
import {
updateSlot,
fetchAvailableSlots,
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";
@ -86,7 +87,7 @@ const EditSlotModal: React.FC<EditSlotModalProps> = ({
isAvailable: availabilityStatus,
})
).unwrap();
dispatch(fetchAvailableSlots());
dispatch(fetchManagersSlots());
handleClose(); // Close modal on success
reset(); // Reset form fields after submit
} catch (error) {
@ -145,7 +146,6 @@ const EditSlotModal: React.FC<EditSlotModalProps> = ({
{/* Input Fields */}
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 2 }}>
{/* Start Time */}
<Box sx={{ flex: "1 1 48%" }}>
<Typography variant="body2" fontWeight={500} mb={0.5}>

View file

@ -4,188 +4,194 @@ import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent";
import Typography from "@mui/material/Typography";
import { LineChart } from "@mui/x-charts/LineChart";
import { FormControl, MenuItem, Select } from "@mui/material";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
import useMediaQuery from "@mui/material/useMediaQuery";
import { useDispatch, useSelector } from "react-redux";
import { AppDispatch, RootState } from "../../redux/store/store";
import { fetchDashboardData } from "../../redux/slices/dashboardSlice";
function AreaGradient({ color, id }: { color: string; id: string }) {
return (
<defs>
<linearGradient id={id} x1="50%" y1="0%" x2="50%" y2="100%">
<stop offset="0%" stopColor={color} stopOpacity={0.5} />
<stop offset="100%" stopColor={color} stopOpacity={0} />
</linearGradient>
</defs>
);
}
function getDaysInMonth(month: number, year: number) {
const date = new Date(year, month, 0);
const monthName = date.toLocaleDateString("en-US", {
month: "short",
});
const daysInMonth = date.getDate();
const days = [];
let i = 1;
while (days.length < daysInMonth) {
days.push(`${monthName} ${i}`);
i += 1;
}
return days;
return (
<defs>
<linearGradient id={id} x1="50%" y1="0%" x2="50%" y2="100%">
<stop offset="0%" stopColor={color} stopOpacity={0.5} />
<stop offset="100%" stopColor={color} stopOpacity={0} />
</linearGradient>
</defs>
);
}
export default function LineChartCard() {
const theme = useTheme();
const isXsScreen = useMediaQuery(theme.breakpoints.down("sm"));
const isSmScreen = useMediaQuery(theme.breakpoints.between("sm", "md"));
const data = getDaysInMonth(4, 2024);
const colorPalette = [theme.palette.primary.main];
const [selectedOption, setSelectedOption] = React.useState("Weekly");
const theme = useTheme();
const isXsScreen = useMediaQuery(theme.breakpoints.down("sm"));
const isSmScreen = useMediaQuery(theme.breakpoints.between("sm", "md"));
const handleChange = (event: { target: { value: React.SetStateAction<string> } }) => {
setSelectedOption(event.target.value);
};
const dispatch = useDispatch<AppDispatch>();
const { totalBookings, loading } = useSelector(
(state: RootState) => state.dashboardReducer
);
// Calculate responsive dimensions
const getChartHeight = () => {
if (isXsScreen) return 200;
if (isSmScreen) return 220;
return 250;
};
// States for date range inputs
const [startDateBookings, setStartDateBookings] = React.useState("");
const [endDateBookings, setEndDateBookings] = React.useState("");
const getChartMargin = () => {
if (isXsScreen) return { left: 35, right: 10, top: 15, bottom: 15 };
if (isSmScreen) return { left: 40, right: 15, top: 18, bottom: 18 };
return { left: 50, right: 20, top: 20, bottom: 20 };
};
// Function to fetch data with date filters
const handleFetchData = () => {
dispatch(fetchDashboardData({ startDateBookings, endDateBookings }));
};
return (
<Card
variant="outlined"
sx={{
width: "100%",
height: "auto",
minHeight: { xs: "360px", sm: "400px", md: "444px" },
borderRadius: "16px",
border: "none",
"*:where([data-mui-color-scheme='dark']) &": {
backgroundColor: "#202020",
},
}}
>
<CardContent sx={{ padding: { xs: 2, md: 3 }, "&:last-child": { paddingBottom: { xs: 2, md: 3 } } }}>
<div
style={{
display: "flex",
alignItems: "center",
flexDirection: isXsScreen ? "column" : "row",
color: "#F2F2F2",
marginBottom: isXsScreen ? 16 : 0,
}}
>
<Typography
variant="h6"
align={isXsScreen ? "center" : "left"}
color="#F2F2F2"
sx={{
fontWeight: 500,
fontSize: { xs: "16px", sm: "17px", md: "18px" },
lineHeight: "24px",
marginBottom: isXsScreen ? 2 : 0,
width: isXsScreen ? "100%" : "auto",
}}
>
Sales Stats
</Typography>
<FormControl
sx={{
mt: isXsScreen ? 0 : 2,
mb: isXsScreen ? 0 : 2,
backgroundColor: "#202020",
marginLeft: isXsScreen ? 0 : "auto",
marginRight: isXsScreen ? 0 : "16px",
display: "flex",
justifyContent: "center",
alignItems: "center",
color: "#F2F2F2",
width: { xs: "100%", sm: "140px", md: "149px" },
height: { xs: "40px", md: "44px" },
padding: { xs: "8px 12px", md: "12px 16px" },
gap: "8px",
}}
>
<Select
value={selectedOption}
onChange={handleChange}
sx={{
fontSize: { xs: "14px", md: "16px" },
color: "#D9D8D8",
".MuiSelect-icon": {
color: "#F2F2F2",
},
}}
IconComponent={ExpandMoreIcon}
>
<MenuItem value="Monthly">Monthly</MenuItem>
<MenuItem value="Weekly">Weekly</MenuItem>
<MenuItem value="Daily">Daily</MenuItem>
<MenuItem value="Yearly">Yearly</MenuItem>
</Select>
</FormControl>
</div>
// Generate chart data points (simulated here)
const totalPoints = 10; // Divide total bookings into 10 intervals
const chartData = Array.from({ length: totalPoints }, (_, i) => ({
label: `Day ${i + 1}`,
value: Math.round((totalBookings / totalPoints) * (i + 1)),
}));
<LineChart
colors={colorPalette}
xAxis={[
{
scaleType: "point",
data,
tickInterval: (index, i) => isXsScreen
? (i + 1) % 10 === 0
: isSmScreen
? (i + 1) % 7 === 0
: (i + 1) % 5 === 0,
},
]}
series={[
{
id: "direct",
label: "Direct",
showMark: false,
curve: "linear",
stack: "total",
area: true,
stackOrder: "ascending",
data: [
300, 900, 500, 1200, 1500, 1800, 2400, 2100,
2700, 3000, 1800, 3300, 3600, 3900, 4200, 4500,
3900, 4800, 5100, 5400, 4500, 5700, 6000, 6300,
6600, 6900, 7200, 7500, 7800, 8100,
],
color: "#28ACFF",
},
]}
height={getChartHeight()}
margin={getChartMargin()}
grid={{ horizontal: true }}
sx={{
"& .MuiAreaElement-series-direct": {
fill: "url('#direct')",
},
}}
slotProps={{
legend: {
hidden: true,
},
}}
>
<AreaGradient
color={theme.palette.primary.main}
id="direct"
/>
</LineChart>
</CardContent>
</Card>
);
}
// Calculate responsive chart dimensions
const getChartHeight = () => (isXsScreen ? 200 : isSmScreen ? 220 : 250);
const getChartMargin = () =>
isXsScreen
? { left: 35, right: 10, top: 15, bottom: 15 }
: isSmScreen
? { left: 40, right: 15, top: 18, bottom: 18 }
: { left: 50, right: 20, top: 20 };
return (
<Card
variant="outlined"
sx={{
width: "100%",
height: "auto",
minHeight: { xs: "360px", sm: "400px", md: "444px" },
borderRadius: "16px",
border: "none",
"*:where([data-mui-color-scheme='dark']) &": {
backgroundColor: "#202020",
},
}}
>
<CardContent
sx={{
padding: { xs: 2, md: 3 },
"&:last-child": { paddingBottom: { xs: 2, md: 3 } },
}}
>
<div
style={{
display: "flex",
alignItems: "center",
flexDirection: isXsScreen ? "column" : "row",
color: "#F2F2F2",
marginBottom: isXsScreen ? 16 : 0,
}}
>
<Typography
variant="h6"
align={isXsScreen ? "center" : "left"}
color="#F2F2F2"
sx={{
fontWeight: 500,
fontSize: { xs: "16px", sm: "17px", md: "18px" },
lineHeight: "24px",
marginBottom: isXsScreen ? 2 : 0,
width: isXsScreen ? "100%" : "auto",
}}
>
Total Bookings Stats
</Typography>
</div>
{/* Input fields for start and end date */}
<div
style={{
display: "flex",
flexDirection: isXsScreen ? "column" : "row",
gap: "16px",
marginTop: "16px",
}}
>
<TextField
type="date"
label="Start Date"
variant="outlined"
fullWidth
InputLabelProps={{ shrink: true }}
value={startDateBookings}
onChange={(e) => setStartDateBookings(e.target.value)}
sx={{
backgroundColor: "#202020",
borderRadius: "8px",
"& .MuiInputBase-input": {
color: "#F2F2F2",
},
}}
/>
<TextField
type="date"
label="End Date"
variant="outlined"
fullWidth
InputLabelProps={{ shrink: true }}
value={endDateBookings}
onChange={(e) => setEndDateBookings(e.target.value)}
sx={{
backgroundColor: "#202020",
borderRadius: "8px",
"& .MuiInputBase-input": {
color: "#F2F2F2",
},
}}
/>
<Button
onClick={handleFetchData}
sx={{
backgroundColor: "#52ACDF",
color: "white",
borderRadius: "8px",
padding: "10px 20px",
"&:hover": { backgroundColor: "#3A94C0" },
}}
>
Fetch
</Button>
</div>
{/* Line Chart */}
<LineChart
colors={[theme.palette.primary.main]}
xAxis={[
{
scaleType: "point",
data: chartData.map((data) => data.label), // Use intervals as x-axis labels
},
]}
series={[
{
id: "totalBookings",
showMark: false,
curve: "linear",
area: true,
data: chartData.map((data) => data.value), // Use interval data for y-axis
color: theme.palette.primary.main,
},
]}
height={getChartHeight()}
margin={getChartMargin()}
grid={{ horizontal: true }}
sx={{
"& .MuiAreaElement-series-totalBookings": {
fill: "url('#totalBookings')",
},
}}
>
<AreaGradient
color={theme.palette.primary.main}
id="totalBookings"
/>
</LineChart>
</CardContent>
</Card>
);
}

View file

@ -6,45 +6,85 @@ import StatCard, { StatCardProps } from "../StatCard/statCard";
import ResourcesPieChart from "../ResourcePieChart/resourcePieChart";
import RoundedBarChart from "../barChartCard/barChartCard";
import LineChartCard from "../LineChartCard/lineChartCard";
const data: StatCardProps[] = [
{ title: "Total Charge Stations", value: "86" },
{ title: "Charging Completed", value: "12" },
{ title: "Active Users", value: "24" },
{ title: "Total Energy Consumed", value: "08" },
];
import { AppDispatch, RootState } from "../../redux/store/store";
import { useDispatch, useSelector } from "react-redux";
import { fetchDashboardData } from "../../redux/slices/dashboardSlice";
import { useEffect } from "react";
export default function MainGrid() {
const dispatch = useDispatch<AppDispatch>();
const {
totalAdmins,
totalManagers,
totalUsers,
totalStations,
loading,
error,
} = useSelector((state: RootState) => state.dashboardReducer);
const staticData = {
totalAdmins: 86,
totalManagers: 12,
totalUsers: 24,
totalStations: 8,
};
useEffect(() => {
dispatch(fetchDashboardData());
}, [dispatch]);
const data =
{ totalAdmins, totalManagers, totalUsers, totalStations }
const statCards = [
{ title: "Total Admins", value: data.totalAdmins },
{ title: "Total Managers", value: data.totalManagers },
{ title: "Total Users", value: data.totalUsers },
{ title: "Total Stations", value: data.totalStations },
];
return (
<Box sx={{ width: "100%", maxWidth: "1600px", mx: "auto", px: { xs: 2, sm: 1, md: 0 } }}>
{/* Dashboard Header */}
<Typography component="h2" variant="h6" sx={{ mb: 2 }}>
Dashboard
</Typography>
<Box
sx={{
width: "100%",
maxWidth: "1600px",
mx: "auto",
px: { xs: 2, sm: 1, md: 0 },
}}
>
{/* Dashboard Header */}
<Typography component="h2" variant="h6" sx={{ mb: 2 }}>
Dashboard
</Typography>
{/* Grid Layout */}
<Grid container spacing={3} columns={12}>
{/* Statistic Cards */}
{data.map((card, index) => (
<Grid key={index} item xs={12} sm={6} md={3} lg={3}>
<StatCard {...card} />
</Grid>
))}
{/* Grid Layout */}
<Grid container spacing={3} columns={12}>
{/* Statistic Cards */}
{statCards.map((card, index) => (
<Grid key={index} item xs={12} sm={6} md={3} lg={3}>
<StatCard {...card} />
</Grid>
))}
{/* Charts */}
<Grid item xs={12} sm={12} md={6} lg={6}>
<SessionsChart />
</Grid>
<Grid item xs={12} sm={12} md={6} lg={6}>
<ResourcesPieChart />
</Grid>
<Grid item xs={12} sm={12} md={6} lg={6}>
<RoundedBarChart />
</Grid>
<Grid item xs={12} sm={12} md={6} lg={6}>
<LineChartCard />
</Grid>
</Grid>
</Box>
{/* Charts */}
<Grid item xs={12} sm={12} md={6} lg={6}>
<SessionsChart />
</Grid>
<Grid item xs={12} sm={12} md={6} lg={6}>
<ResourcesPieChart />
</Grid>
<Grid item xs={12} sm={12} md={6} lg={6}>
<RoundedBarChart />
</Grid>
<Grid item xs={12} sm={12} md={6} lg={6}>
<LineChartCard />
</Grid>
</Grid>
</Box>
);
}

View file

@ -36,9 +36,9 @@ export default function MenuContent({ hidden }: PropType) {
url: "/panel/admin-list",
},
userRole === "superadmin" && {
text: "Managers",
text: "Manager",
icon: <PeopleOutlinedIcon />,
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

@ -6,6 +6,10 @@ import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import { useTheme } from "@mui/material/styles";
import useMediaQuery from "@mui/material/useMediaQuery";
import { AppDispatch, RootState } from "../../redux/store/store";
import { useDispatch, useSelector } from "react-redux";
import { useEffect } from "react";
import { fetchDashboardData } from "../../redux/slices/dashboardSlice";
const colorPalette = [
"hsla(202, 69%, 60%, 1)",
@ -14,19 +18,48 @@ const colorPalette = [
"hsl(222, 6.80%, 50.80%)",
];
const data = [
{ title: "Total Resources", value: 50, color: colorPalette[0] },
{ title: "Total Stations", value: 20, color: colorPalette[1] },
{ title: "Station Manager", value: 15, color: colorPalette[2] },
{ title: "Total Booth", value: 15, color: colorPalette[3] },
];
// const data = [
// { title: "Total Resources", value: 50, color: colorPalette[0] },
// { title: "Total Stations", value: 20, color: colorPalette[1] },
// { title: "Station Manager", value: 15, color: colorPalette[2] },
// { title: "Total Booth", value: 15, color: colorPalette[3] },
// ];
export default function ResourcePieChart() {
const theme = useTheme();
const isXsScreen = useMediaQuery(theme.breakpoints.down("sm"));
const isSmScreen = useMediaQuery(theme.breakpoints.between("sm", "md"));
const dispatch = useDispatch<AppDispatch>();
// // Fetch role and carPortCounts from Redux state
// const {user} = useSelector((state: RootState) => state.profileReducer); // Assuming user role is stored in Redux
const { carPortCounts } = useSelector(
(state: RootState) => state.dashboardReducer
);
console.log("first",carPortCounts)
// Static data for non-superadmin roles
// const staticCarPorts = [
// { carPort: "240V", count: 5 },
// { carPort: "120V", count: 3 },
// { carPort: "DCFC", count: 2 },
// { carPort: "Other", count: 7 },
// ];
useEffect(() => {
dispatch(fetchDashboardData());
}, [dispatch]);
// console.log("Raw CarPortCounts from API:", carPortCounts);
const dataToDisplay =carPortCounts
// const dataToDisplay =
// user?.userType === "superadmin"
// ? carPortCounts.filter((entry) => entry.count > 0) // Exclude zero counts
// : staticCarPorts.filter((entry) => entry.count > 0);
// console.log("Filtered Data to Display:", dataToDisplay);
const getChartDimensions = () => {
if (isXsScreen) {
return {
@ -58,106 +91,121 @@ export default function ResourcePieChart() {
const dimensions = getChartDimensions();
return (
<Card
variant="outlined"
sx={{
display: "flex",
flexDirection: "column",
gap: { xs: "8px", sm: "10px", md: "12px" },
flexGrow: 1,
width: "100%",
height: "auto",
minHeight: { xs: "320px", sm: "340px", md: "360px" },
padding: { xs: "12px", sm: "14px", md: "16px" },
borderRadius: "16px",
border: "none",
"*:where([data-mui-color-scheme='dark']) &": {
backgroundColor: "#202020",
},
}}
>
<CardContent sx={{ padding: 0, "&:last-child": { paddingBottom: 0 } }}>
<Typography
component="h2"
variant="subtitle2"
color="#F2F2F2"
sx={{
fontWeight: 500,
fontSize: { xs: "16px", sm: "17px", md: "18px" },
lineHeight: "24px",
color: "#FFFFFF",
marginBottom: { xs: 1, sm: 1.5, md: 2 },
}}
>
Resources
</Typography>
<Box
sx={{
display: "flex",
flexDirection: { xs: "column", sm: "row" },
alignItems: "center",
justifyContent: "center"
}}
>
<PieChart
colors={colorPalette}
margin={dimensions.margin}
series={[
{
data,
innerRadius: dimensions.innerRadius,
outerRadius: dimensions.outerRadius,
paddingAngle: 2,
cornerRadius: 8,
highlightScope: {
faded: "global",
highlighted: "item",
},
},
]}
height={dimensions.height}
width={dimensions.width}
slotProps={{
legend: { hidden: true },
}}
/>
<Card
variant="outlined"
sx={{
display: "flex",
flexDirection: "column",
gap: { xs: "8px", sm: "10px", md: "12px" },
flexGrow: 1,
width: "100%",
height: "auto",
minHeight: { xs: "320px", sm: "340px", md: "360px" },
padding: { xs: "12px", sm: "14px", md: "16px" },
borderRadius: "16px",
border: "none",
"*:where([data-mui-color-scheme='dark']) &": {
backgroundColor: "#202020",
},
}}
>
<CardContent
sx={{ padding: 0, "&:last-child": { paddingBottom: 0 } }}
>
<Typography
component="h2"
variant="subtitle2"
color="#F2F2F2"
sx={{
fontWeight: 500,
fontSize: { xs: "16px", sm: "17px", md: "18px" },
lineHeight: "24px",
color: "#FFFFFF",
marginBottom: { xs: 1, sm: 1.5, md: 2 },
}}
>
Car Port Usage
</Typography>
<Box
sx={{
display: "flex",
flexDirection: { xs: "column", sm: "row" },
alignItems: "center",
justifyContent: "center",
}}
>
<PieChart
colors={colorPalette}
margin={dimensions.margin}
series={[
{
data: dataToDisplay.map((entry, index) => ({
title: entry.carPort,
value: entry.count,
color: colorPalette[
index % colorPalette.length
],
})),
innerRadius: dimensions.innerRadius,
outerRadius: dimensions.outerRadius,
paddingAngle: 2,
cornerRadius: 8,
highlightScope: {
faded: "global",
highlighted: "item",
},
},
]}
height={dimensions.height}
width={dimensions.width}
slotProps={{
legend: { hidden: true },
}}
/>
<Stack
spacing={1}
sx={{
mt: { xs: 2, sm: 0 },
ml: { xs: 0, sm: 2 }
}}
>
{data.map((entry, index) => (
<Stack
key={index}
direction="row"
spacing={1}
alignItems="center"
>
<Box
sx={{
width: { xs: 12, sm: 14, md: 16 },
height: { xs: 12, sm: 14, md: 16 },
backgroundColor: entry.color,
borderRadius: "50%",
}}
/>
<Typography
variant="body2"
sx={{
fontSize: { xs: "12px", sm: "13px", md: "14px" },
color: "#FFFFFF",
}}
>
{entry.title}
</Typography>
</Stack>
))}
</Stack>
</Box>
</CardContent>
</Card>
<Stack
spacing={1}
sx={{
mt: { xs: 2, sm: 0 },
ml: { xs: 0, sm: 2 },
}}
>
{dataToDisplay.map((entry, index) => (
<Stack
key={index}
direction="row"
spacing={1}
alignItems="center"
>
<Box
sx={{
width: { xs: 12, sm: 14, md: 16 },
height: { xs: 12, sm: 14, md: 16 },
backgroundColor:
colorPalette[
index % colorPalette.length
],
borderRadius: "50%",
}}
/>
<Typography
variant="body2"
sx={{
fontSize: {
xs: "12px",
sm: "13px",
md: "14px",
},
color: "#FFFFFF",
}}
>
{entry.carPort}
</Typography>
</Stack>
))}
</Stack>
</Box>
</CardContent>
</Card>
);
}

View file

@ -6,7 +6,7 @@ import useMediaQuery from "@mui/material/useMediaQuery";
export type StatCardProps = {
title: string;
value: string;
value: number;
};
export default function StatCard({ title, value }: StatCardProps) {

View file

@ -5,43 +5,47 @@ import {
CardContent,
Typography,
Box,
Select,
MenuItem,
FormControl,
SelectChangeEvent,
TextField,
Button,
} from "@mui/material";
import { BarChart } from "@mui/x-charts/BarChart";
import { axisClasses } from "@mui/x-charts/ChartsAxis";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import useMediaQuery from "@mui/material/useMediaQuery";
// Sample Data
const data = [
{ name: "Jan", v1: 40 },
{ name: "Feb", v1: 50 },
{ name: "Mar", v1: 80 },
{ name: "Apr", v1: 20 },
{ name: "May", v1: 60 },
{ name: "Jun", v1: 30 },
];
import { AppDispatch, RootState } from "../../redux/store/store";
import { useDispatch, useSelector } from "react-redux";
import { fetchDashboardData } from "../../redux/slices/dashboardSlice";
export default function RoundedBarChart() {
const theme = useTheme();
const isXsScreen = useMediaQuery(theme.breakpoints.down("sm"));
const isSmScreen = useMediaQuery(theme.breakpoints.between("sm", "md"));
const [selectedOption, setSelectedOption] = React.useState("Monthly");
const dispatch = useDispatch<AppDispatch>();
const handleChange = (event: SelectChangeEvent<string>) => {
setSelectedOption(event.target.value);
// Fetch role and topStations from Redux state
const { user } = useSelector((state: RootState) => state.profileReducer); // Assuming user role is stored in Redux
const { topStations } = useSelector(
(state: RootState) => state.dashboardReducer
);
// State for filtering
const [startDateStations, setStartDateStations] = React.useState("");
const [endDateStations, setEndDateStations] = React.useState("");
// Handle fetching data with date filters
const handleFetchData = () => {
dispatch(
fetchDashboardData({ startDateStations, endDateStations })
);
};
// Responsive chart settings
const getChartSettings = () => {
const baseSettings = {
yAxis: [
{
label: isXsScreen ? "" : "Value",
label: isXsScreen ? "" : "Count",
tickFormatter: (value: number) => `${value}`,
},
],
@ -49,6 +53,7 @@ export default function RoundedBarChart() {
{
dataKey: "name",
scaleType: "band" as const,
},
],
sx: {
@ -82,6 +87,12 @@ export default function RoundedBarChart() {
const chartSetting = getChartSettings();
// Data transformation for BarChart
const chartData = topStations.map((station) => ({
name: station?.ChargingStation?.name,
count: parseInt(station.count, 10), // Ensure count is a number
}));
return (
<Card
variant="outlined"
@ -102,6 +113,7 @@ export default function RoundedBarChart() {
"&:last-child": { paddingBottom: { xs: 2, md: 3 } },
}}
>
{/* Header */}
<Box
display="flex"
alignItems="center"
@ -121,56 +133,80 @@ export default function RoundedBarChart() {
width: isXsScreen ? "100%" : "auto",
}}
>
Charge Stats
Top Stations Stats
</Typography>
<FormControl
sx={{
mt: isXsScreen ? 0 : 2,
ml: isXsScreen ? 0 : "auto",
backgroundColor: "#202020",
color: "#F2F2F2",
width: { xs: "100%", sm: "140px", md: "149px" },
height: { xs: "40px", md: "44px" },
padding: { xs: "8px 12px", md: "12px 16px" },
gap: "8px",
}}
>
<Select
value={selectedOption}
onChange={handleChange}
sx={{
fontSize: { xs: "14px", md: "16px" },
color: "#D9D8D8",
".MuiSelect-icon": {
color: "#F2F2F2",
},
}}
IconComponent={ExpandMoreIcon}
>
<MenuItem value="Monthly">Monthly</MenuItem>
<MenuItem value="Weekly">Weekly</MenuItem>
<MenuItem value="Daily">Daily</MenuItem>
<MenuItem value="Yearly">Yearly</MenuItem>
</Select>
</FormControl>
</Box>
{/* Date Filters */}
<Box
display="flex"
flexDirection={isXsScreen ? "column" : "row"}
gap={2}
mt={2}
>
<TextField
type="date"
label="Start Date"
variant="outlined"
fullWidth
InputLabelProps={{ shrink: true }}
value={startDateStations}
onChange={(e) => setStartDateStations(e.target.value)}
sx={{
backgroundColor: "#202020",
borderRadius: "8px",
"& .MuiInputBase-input": {
color: "#F2F2F2",
},
}}
/>
<TextField
type="date"
label="End Date"
variant="outlined"
fullWidth
InputLabelProps={{ shrink: true }}
value={endDateStations}
onChange={(e) => setEndDateStations(e.target.value)}
sx={{
backgroundColor: "#202020",
borderRadius: "8px",
"& .MuiInputBase-input": {
color: "#F2F2F2",
},
}}
/>
<Button
onClick={handleFetchData}
sx={{
backgroundColor: "#52ACDF",
color: "white",
borderRadius: "8px",
width: "117px",
"&:hover": { backgroundColor: "#439BC1" },
}}
>
Apply
</Button>
</Box>
{/* Chart */}
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
width: "100%",
mt: 2,
mt: 4,
}}
>
<BarChart
borderRadius={0}
dataset={data}
dataset={chartData}
series={[
{
dataKey: "v1",
dataKey: "count",
color: "#52ACDF",
},
]}

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,24 +1,64 @@
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(); // Initialize useNavigate
const navigate = useNavigate();
const handleLoginClick = () => {
navigate("/login"); // Redirect to the login page
navigate("/login");
};
return (
<Box
sx={{
background: `radial-gradient(circle at top left, rgba(125,121,87,0.992) 10%, rgba(67,92,106,0.8) 40%, rgba(55,47,65,1) 70%),
radial-gradient(circle at center, rgba(109, 102, 102, 0.7) 0%, rgba(67,92,106,0.6) 50%, rgba(55,47,65,0.9) 70%),
radial-gradient(circle at top right, rgba(61,42,87,1) 30%, rgba(55,47,65,1) 60%, rgba(40,40,40,0.8) 70%)`,
background: `
linear-gradient(135deg, #0D0D0D 20%, #1C1E22 80%),
radial-gradient(circle at 30%, rgb(241, 201, 119) 100%, rgba(255, 204, 102, 0) 50%)
`,
color: "white",
minHeight: "100vh",
fontFamily: "Inter",
//display: "flex", // Ensures the children align correctly
//flexDirection: "column",
}}
>
{/* Navbar */}
@ -55,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",
@ -71,7 +111,6 @@ const LandingPage = () => {
</Button>
</Box>
</Box>
{/* Hero Section */}
<Container
sx={{
@ -170,56 +209,619 @@ const LandingPage = () => {
</Grid>
</Grid>
</Container>
<Container maxWidth="lg">
{" "}
<Container
maxWidth="lg"
sx={{
// width: "1320px",
// height: "239px",
width: "100%",
position: "relative",
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: 15,
}}
>
<Grid container spacing={4} textAlign="center">
{" "}
<Grid item xs={12} md={4}>
{" "}
<Grid item xs={12} md={3}>
<Typography
variant="h3"
fontWeight="bold"
color="#1e88e5"
color="#52ACDF"
fontWeight={600}
fontSize={"80px"}
lineHeight={"100px"}
fontFamily={"Poppins"}
>
{" "}
50+{" "}
</Typography>{" "}
<Typography variant="body2" color="gray">
{" "}
Successful Digital Transformations{" "}
</Typography>{" "}
</Grid>{" "}
<Grid item xs={12} md={4}>
{" "}
50+
</Typography>
<Typography
variant="body2"
color="#FFFFFF"
fontFamily={"Inter"}
fontWeight={400}
fontSize={"16px"}
lineHeight={"22px"}
>
Successful Digital Transformations
</Typography>
</Grid>
<Grid item xs={12} md={3}>
<Typography
variant="h3"
fontWeight="bold"
color="#1e88e5"
color="#52ACDF"
fontWeight={600}
fontSize={"80px"}
lineHeight={"100px"}
fontFamily={"Poppins"}
>
{" "}
100%{" "}
</Typography>{" "}
<Typography variant="body2" color="gray">
{" "}
Client Satisfaction{" "}
</Typography>{" "}
</Grid>{" "}
<Grid item xs={12} md={4}>
{" "}
100%
</Typography>
<Typography
variant="body2"
color="#FFFFFF"
fontFamily={"Inter"}
fontWeight={400}
fontSize={"16px"}
lineHeight={"22px"}
>
Client Satisfaction
</Typography>
</Grid>
<Grid item xs={12} md={3}>
<Typography
variant="h3"
fontWeight="bold"
color="#1e88e5"
color="#52ACDF"
fontWeight={600}
fontSize={"80px"}
lineHeight={"100px"}
fontFamily={"Poppins"}
>
{" "}
20+{" "}
</Typography>{" "}
<Typography variant="body2" color="gray">
{" "}
Global Partnerships{" "}
</Typography>{" "}
</Grid>{" "}
</Grid>{" "}
20+
</Typography>
<Typography
variant="body2"
color="#FFFFFF"
fontFamily={"Inter"}
fontWeight={400}
fontSize={"16px"}
lineHeight={"22px"}
>
Global Partnerships
</Typography>
</Grid>
{/* New Statistic */}
<Grid item xs={12} md={3}>
<Typography
variant="h3"
color="#52ACDF"
fontWeight={600}
fontSize={"80px"}
lineHeight={"100px"}
fontFamily={"Poppins"}
>
10+
</Typography>
<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>
<Container
maxWidth="lg"
sx={{
// minHeight: "100vh",
textAlign: "center",
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,
fontFamily: "Neue Montreal",
}}
>
<Typography variant="h4" fontWeight={700} mb={5}>
Key Features
</Typography>
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
position: "relative",
}}
>
<img
src="/iMockup - iPhone.png"
alt="iMockup - iPhone"
style={{
width: "60%",
maxWidth: "500px",
borderRadius: "10px",
}}
/>
<Box
sx={{
position: "absolute",
width: "100%",
height: "100%",
pointerEvents: "none", // Prevents interaction issues
}}
>
{/* Top Left */}
<Box
sx={{
position: "absolute",
top: "5%",
left: "5%",
transform: "translate(-5%, -5%)",
maxWidth: "200px",
}}
>
{/* Arrow pointing to mockup */}
<img
src="/vector-arrows.png"
alt="Arrow"
style={{
position: "absolute",
width: "80px",
height: "60px",
top: "100%",
left: "160%",
transform:
"translate(-50%, -50%) rotate(-20deg)",
}}
/>
<Typography
variant="h6"
fontWeight={400}
color="#FFFFFF"
fontSize={"20px"}
lineHeight={"30px"}
>
Seamless Navigation
</Typography>
<Typography variant="body2" color="#D9D8D8">
Effortlessly locate and access EV charging
stations with our intuitive map integration.
</Typography>
</Box>
{/* Top Right */}
<Box
sx={{
position: "absolute",
top: "1%",
right: "5%",
transform: "translate(5%, -5%)",
maxWidth: "200px",
textAlign: "right",
}}
>
{/* Arrow pointing to mockup */}
<img
src="/vector-arrows.png"
alt="Arrow"
style={{
position: "absolute",
width: "80px",
height: "50px",
top: "90%",
right: "110%",
transform:
"translate(50%, -50%) rotate(120deg)",
}}
/>
<Typography
variant="h6"
fontWeight={400}
color="#FFFFFF"
fontSize={"20px"}
lineHeight={"30px"}
>
Live Availability
</Typography>
<Typography variant="body2" color="#D9D8D8">
View real-time charger availability to plan your
trips efficiently.
</Typography>
</Box>
{/* Bottom Left */}
<Box
sx={{
position: "absolute",
bottom: "5%",
left: "5%",
transform: "translate(-5%, 5%)",
maxWidth: "200px",
}}
>
{/* Arrow pointing to mockup */}
<img
src="/vector-arrows.png"
alt="Arrow"
style={{
position: "absolute",
width: "80px",
height: "60px",
bottom: "80%",
left: "120%",
transform:
"translate(-50%, 50%) rotate(300deg)",
}}
/>
<Typography
variant="h6"
fontWeight={400}
color="#FFFFFF"
fontSize={"20px"}
lineHeight={"30px"}
>
Smart Recommendations
</Typography>
<Typography variant="body2" color="#D9D8D8">
Get personalized station suggestions based on
your location and usage patterns.
</Typography>
</Box>
{/* Bottom Right */}
<Box
sx={{
position: "absolute",
bottom: "5%",
right: "5%",
transform: "translate(5%, 5%)",
maxWidth: "200px",
textAlign: "right",
}}
>
{/* Arrow pointing to mockup */}
<img
src="/vector-arrows.png"
alt="Arrow"
style={{
position: "absolute",
width: "80px",
height: "60px",
bottom: "100%",
right: "200%",
transform:
"translate(50%, 50%) rotate(-200deg)",
}}
/>
<Typography
variant="h6"
fontWeight={400}
color="#FFFFFF"
fontSize={"20px"}
lineHeight={"30px"}
>
Secure Payments
</Typography>
<Typography variant="body2" color="#D9D8D8">
Make hassle-free transactions with our secure
payment gateway.
</Typography>
</Box>
</Box>
</Box>
</Container>
{/* Footer */}
<Container
maxWidth="lg"
sx={{
textAlign: "center",
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,
fontFamily: "Neue Montreal",
height: "261px",
}}
>
<Box
sx={{
// minHeight: "100vh",
textAlign: "center",
py: 4,
px: 3,
mt: 2,
fontFamily: "Neue Montreal",
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
position: "relative",
}}
>
{/* Primary Image */}
<img
src="/dev-bg.png"
alt="Developer"
style={{
width: "80px",
marginRight: "20px",
position: "relative",
zIndex: 1, // Ensure it's layered on top
}}
/>
{/* Overlapping Image */}
<img
src="/developer.png"
alt="developer"
style={{
width: "80px",
position: "absolute",
top: "10px",
left: "20px",
zIndex: 2,
}}
/>
<Typography
fontFamily={"Inter"}
fontWeight={500}
fontSize={"40px"}
lineHeight={"100%"}
color="#FFFFFF"
>
Get your application developed by our certified
experts today!
</Typography>
<Button
sx={{
backgroundColor: "#52ACDF",
color: "white",
borderRadius: "8px",
width: "160px",
height: "54px",
fontFamily: "Neue Montreal",
textTransform: "none",
fontWeight: 500,
fontSize: "18px",
lineHeight: "100%",
"&:hover": { backgroundColor: "#439BC1" },
}}
>
Schedule a Call
</Button>
</Box>
</Box>
</Container>
<Container
maxWidth="lg"
sx={{
textAlign: "center",
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,
fontFamily: "Neue Montreal",
height: "auto", // Dynamically adjust height to content
}}
>
<Box
sx={{
textAlign: "center",
py: 4,
px: 3,
mt: 2,
fontFamily: "Neue Montreal",
}}
>
<Typography
variant="h5"
fontWeight={600}
fontFamily={"Inter"}
fontSize={"40px"}
lineHeight={"100%"}
>
Your{" "}
<span style={{ color: "#52ACDF" }}>
Perfect Experience
</span>
, Just a Tap Away!
</Typography>
<Typography
variant="body2"
sx={{
my: 2,
fontFamily: "Inter",
fontWeight: 400,
fontSize: "20px",
color: "#FFFFFF",
}}
>
Discover the smartest way to charge your electric
vehicle. DigiEV's cutting-edge platform empowers you
with effortless access to EV charging stations,
real-time availability updates, and powerful tools to
plan your journey. Make every drive electric, efficient,
and extraordinary.
</Typography>
<Box
sx={{
display: "flex",
justifyContent: "center",
gap: 2,
}}
>
<img
src="/google_play.png"
alt="Google Play"
style={{ width: "120px" }}
/>
<img
src="/apple_store.png"
alt="App Store"
style={{ width: "120px" }}
/>
</Box>
</Box>
</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

@ -10,7 +10,7 @@ import managerReducer from "../redux/slices/managerSlice.ts";
import stationReducer from "../redux/slices/stationSlice.ts";
import slotReducer from "../redux/slices/slotSlice.ts";
import bookReducer from "../redux/slices/bookSlice.ts";
import dashboardReducer from "../redux/slices/dashboardSlice.ts";
const rootReducer = combineReducers({
authReducer,
@ -23,7 +23,8 @@ const rootReducer = combineReducers({
stationReducer,
slotReducer,
bookReducer,
// Add other reducers here...
dashboardReducer,
});
export type RootState = ReturnType<typeof rootReducer>;

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

@ -19,6 +19,8 @@ interface Booking {
startTime: string;
endTime: string;
carDetails: CarDetails;
carNames: string[]; // For car names
carPorts: string[]; // For car ports
}
interface BookingState {

View file

@ -0,0 +1,94 @@
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
import http from "../../lib/https";
import { toast } from "sonner";
// Define interfaces for the dashboard data structure
interface CarPortCount {
carPort: string;
count: number;
}
interface TopStation {
stationId: number;
count: string;
ChargingStation: {
id: number;
name: string;
};
}
interface DashboardState {
totalAdmins: number;
totalManagers: number;
totalUsers: number;
totalStations: number;
carPortCounts: CarPortCount[];
topStations: TopStation[];
totalBookings: number;
loading: boolean;
error: string | null;
}
const initialState: DashboardState = {
totalAdmins: 0,
totalManagers: 0,
totalUsers: 0,
totalStations: 0,
carPortCounts: [],
topStations: [],
totalBookings: 0,
loading: false,
error: null,
};
// Async thunk for fetching dashboard data
export const fetchDashboardData = createAsyncThunk<
DashboardState,
{ startDateBookings?: string; endDateBookings?: string; startDateStations?: string; endDateStations?: string}, // Accept startDate and endDate as optional parameters
{ rejectValue: string }
>("dashboard/fetchDashboardData", async (params, { rejectWithValue }) => {
try {
const response = await http.get("/dashboard", { params }); // Pass startDate and endDate as query params
return response.data;
} catch (error: any) {
toast.error("Error Fetching Dashboard Data: " + error.message);
return rejectWithValue(
error?.response?.data?.message || "An error occurred"
);
}
});
// Redux slice for the dashboard
const dashboardSlice = createSlice({
name: "dashboard",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchDashboardData.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(
fetchDashboardData.fulfilled,
(state, action: PayloadAction<DashboardState>) => {
state.loading = false;
state.totalAdmins = action.payload.totalAdmins;
state.totalManagers = action.payload.totalManagers;
state.totalUsers = action.payload.totalUsers;
state.totalStations = action.payload.totalStations;
state.carPortCounts = action.payload.carPortCounts;
state.topStations = action.payload.topStations;
state.totalBookings = action.payload.totalBookings;
}
)
.addCase(fetchDashboardData.rejected, (state, action) => {
state.loading = false;
state.error =
action.payload || "Failed to fetch dashboard data";
});
},
});
export default dashboardSlice.reducer;

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 */}