charging Prices API integration
This commit is contained in:
parent
99e3f8c9f9
commit
156925abfa
|
@ -24,7 +24,7 @@ html, body {
|
|||
|
||||
|
||||
@font-face {
|
||||
font-family: 'Publica Sans Round Medium';
|
||||
font-family: '"Publica Sans Round Medium", sans-serif';
|
||||
|
||||
src: url('../public/fonts/PublicaSansRound-Md.otf') format('otf');
|
||||
|
||||
|
|
|
@ -61,9 +61,9 @@ export default function ManagerGrid() {
|
|||
|
||||
{/* Charts */}
|
||||
<Grid container spacing={3} mt={1}>
|
||||
<Grid item xs={12} sm={12} md={6} lg={6}>
|
||||
{/* <Grid item xs={12} sm={12} md={6} lg={6}>
|
||||
<SessionsChart />
|
||||
</Grid>
|
||||
</Grid> */}
|
||||
<Grid item xs={12} sm={12} md={6} lg={6}>
|
||||
<ResourcesPieChart />
|
||||
</Grid>
|
||||
|
|
|
@ -86,11 +86,12 @@ export default function UserDashboard() {
|
|||
sx={{
|
||||
width: "100%",
|
||||
color: "#DE0E1E9",
|
||||
py: 3,
|
||||
py: 0,
|
||||
mt:0
|
||||
}}
|
||||
>
|
||||
{/* Greeting */}
|
||||
<Box display="flex" alignItems="center" px={3} mb={4}>
|
||||
<Box display="flex" alignItems="center" px={3} mb={3} >
|
||||
{/* <Avatar
|
||||
sx={{
|
||||
width: 64,
|
||||
|
@ -106,7 +107,7 @@ export default function UserDashboard() {
|
|||
{user?.name?.charAt(0).toUpperCase() || "U"}
|
||||
</Avatar> */}
|
||||
<Box>
|
||||
<Typography variant="h3">
|
||||
<Typography variant="h4">
|
||||
Welcome back, {user?.name || "User"}!
|
||||
</Typography>
|
||||
<Typography color="#DE0E1E9">
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as React from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import Card from "@mui/material/Card";
|
||||
import CardContent from "@mui/material/CardContent";
|
||||
import Typography from "@mui/material/Typography";
|
||||
|
@ -8,24 +9,54 @@ import MenuItem from "@mui/material/MenuItem";
|
|||
import FormControl from "@mui/material/FormControl";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { fetchDashboardData } from "../../redux/slices/dashboardSlice";
|
||||
import { RootState } from "../../redux/store/store"; // Adjust the path accordingly
|
||||
|
||||
export default function SessionsChart() {
|
||||
const theme = useTheme();
|
||||
const [selectedStation, setSelectedStation] = React.useState(
|
||||
"Delhi NCR EV Station"
|
||||
);
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
const [selectedStation, setSelectedStation] = React.useState<string>("");
|
||||
|
||||
const handleChange = (event: { target: { value: React.SetStateAction<string> } }) => {
|
||||
setSelectedStation(event.target.value);
|
||||
};
|
||||
// Get the dashboard data from Redux state
|
||||
const { basicPrice, loading } = useSelector(
|
||||
(state: RootState) => state.dashboardReducer
|
||||
);
|
||||
|
||||
return (
|
||||
// Get unique station names from basicPrice for the dropdown
|
||||
const stationNames = Array.from(
|
||||
new Set(
|
||||
basicPrice?.map(
|
||||
(price: { stationName: string }) => price.stationName
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// Set default station when basicPrice loads
|
||||
React.useEffect(() => {
|
||||
if (stationNames.length > 0 && !selectedStation) {
|
||||
setSelectedStation(stationNames[0]);
|
||||
}
|
||||
}, [basicPrice, stationNames, selectedStation]);
|
||||
|
||||
// Handle station selection from dropdown
|
||||
const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => {
|
||||
setSelectedStation(event.target.value as string);
|
||||
};
|
||||
|
||||
// Filter basic prices for the selected station
|
||||
const selectedBasePrices =
|
||||
basicPrice?.filter(
|
||||
(price: { stationName: string }) =>
|
||||
price.stationName === selectedStation
|
||||
) || [];
|
||||
|
||||
return (
|
||||
<Card
|
||||
variant="outlined"
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "auto",
|
||||
minHeight: { xs: "260px", sm: "270px", md: "290px" },
|
||||
minHeight: { xs: "360px", sm: "400px", md: "421px" },
|
||||
gap: "16px",
|
||||
borderRadius: "30px",
|
||||
padding: { xs: "12px", sm: "16px", md: "20px" },
|
||||
|
@ -34,18 +65,23 @@ export default function SessionsChart() {
|
|||
}}
|
||||
>
|
||||
<CardContent
|
||||
sx={{ padding: 0, "&:last-child": { paddingBottom: 0 } }}
|
||||
sx={{
|
||||
flex: 1,
|
||||
padding: 0,
|
||||
"&:last-child": { paddingBottom: 0 },
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
align="left"
|
||||
// color="#F2F2F2"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: "12px", sm: "14px", md: "16px" },
|
||||
lineHeight: "24px",
|
||||
letterSpacing: "0%",
|
||||
// color: "#FAFAFA",
|
||||
marginBottom: { xs: 1, sm: 0.7, md: 1 },
|
||||
}}
|
||||
>
|
||||
|
@ -65,7 +101,8 @@ export default function SessionsChart() {
|
|||
<Select
|
||||
value={selectedStation}
|
||||
onChange={handleChange}
|
||||
label="Select Station"
|
||||
displayEmpty
|
||||
disabled={loading || stationNames.length === 0}
|
||||
sx={{
|
||||
color: "#000000",
|
||||
"& .MuiSvgIcon-root": { color: "#000000" },
|
||||
|
@ -73,106 +110,144 @@ export default function SessionsChart() {
|
|||
}}
|
||||
IconComponent={ExpandMoreIcon}
|
||||
>
|
||||
<MenuItem value="Delhi NCR EV Station">
|
||||
Delhi NCR EV Station
|
||||
</MenuItem>
|
||||
<MenuItem value="Mumbai EV Station">
|
||||
Mumbai EV Station
|
||||
</MenuItem>
|
||||
<MenuItem value="Bangalore EV Station">
|
||||
Bangalore EV Station
|
||||
</MenuItem>
|
||||
<MenuItem value="Pune EV Station">
|
||||
Pune EV Station
|
||||
</MenuItem>
|
||||
{stationNames.length === 0 && (
|
||||
<MenuItem value="" disabled>
|
||||
Loading stations...
|
||||
</MenuItem>
|
||||
)}
|
||||
{stationNames.map((name) => (
|
||||
<MenuItem key={name} value={name}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{/* Grid container for the four boxes */}
|
||||
{/* Display the base prices for the selected station */}
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: {
|
||||
xs: "repeat(1, 1fr)", // 1 column on mobile
|
||||
sm: "repeat(2, 1fr)", // 2 columns on tablets
|
||||
md: "repeat(2, 1fr)", // 2x2 grid on desktop
|
||||
xs: "repeat(1, 1fr)",
|
||||
sm: "repeat(2, 1fr)",
|
||||
md: "repeat(2, 1fr)",
|
||||
},
|
||||
gap: { xs: 1, sm: 1.5, md: 2 },
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{[1, 2, 3, 4].map((item) => (
|
||||
<Box
|
||||
key={item}
|
||||
{selectedBasePrices.length > 0 ? (
|
||||
selectedBasePrices.map(
|
||||
(price: {
|
||||
stationId: any;
|
||||
port: string;
|
||||
price: number;
|
||||
}) => (
|
||||
<Box
|
||||
key={`${price.stationId}-${price.port}`}
|
||||
sx={{
|
||||
height: {
|
||||
xs: "105px",
|
||||
sm: "115px",
|
||||
md: "128px",
|
||||
},
|
||||
borderRadius: "8px",
|
||||
p: {
|
||||
xs: "10px",
|
||||
sm: "12px",
|
||||
md: "14px",
|
||||
},
|
||||
backgroundColor: "#D9E7ED",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
fontSize: {
|
||||
xs: "12px",
|
||||
sm: "14px",
|
||||
md: "16px",
|
||||
},
|
||||
lineHeight: {
|
||||
xs: "20px",
|
||||
md: "24px",
|
||||
},
|
||||
marginBottom: "4px",
|
||||
color: "#111111",
|
||||
}}
|
||||
gutterBottom
|
||||
>
|
||||
{price.port}
|
||||
</Typography>
|
||||
<Box
|
||||
display="flex"
|
||||
gap={1}
|
||||
alignItems="center"
|
||||
>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
fontSize: {
|
||||
xs: "12px",
|
||||
sm: "14px",
|
||||
md: "16px",
|
||||
},
|
||||
lineHeight: {
|
||||
xs: "20px",
|
||||
md: "24px",
|
||||
},
|
||||
}}
|
||||
color="#454545"
|
||||
gutterBottom
|
||||
>
|
||||
{price.price.toFixed(2)}{" "}
|
||||
{/* Ensure consistent decimal places */}
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
fontSize: {
|
||||
xs: "12px",
|
||||
sm: "14px",
|
||||
md: "16px",
|
||||
},
|
||||
lineHeight: {
|
||||
xs: "20px",
|
||||
md: "24px",
|
||||
},
|
||||
}}
|
||||
color="#454545"
|
||||
>
|
||||
cents/kWh
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
)
|
||||
) : (
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
height: {
|
||||
xs: "105px",
|
||||
sm: "115px",
|
||||
md: "128px",
|
||||
fontSize: {
|
||||
xs: "12px",
|
||||
sm: "14px",
|
||||
md: "16px",
|
||||
},
|
||||
borderRadius: "8px",
|
||||
p: { xs: "10px", sm: "12px", md: "14px" },
|
||||
// backgroundColor: "#272727",
|
||||
// color: "#D9D8D8",
|
||||
backgroundColor: "#D9E7ED",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
color: "#454545",
|
||||
textAlign: "center",
|
||||
gridColumn: "span 2",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
fontSize: {
|
||||
xs: "12px",
|
||||
sm: "14px",
|
||||
md: "16px",
|
||||
},
|
||||
lineHeight: { xs: "20px", md: "24px" },
|
||||
marginBottom: "4px",
|
||||
color: "#111111",
|
||||
}}
|
||||
gutterBottom
|
||||
>
|
||||
Basic Charging
|
||||
</Typography>
|
||||
<Box display="flex" gap={1} alignItems="center">
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
fontSize: {
|
||||
xs: "12px",
|
||||
sm: "14px",
|
||||
md: "16px",
|
||||
},
|
||||
lineHeight: { xs: "20px", md: "24px" },
|
||||
}}
|
||||
color="#454545"
|
||||
gutterBottom
|
||||
>
|
||||
16.83
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
fontSize: {
|
||||
xs: "12px",
|
||||
sm: "14px",
|
||||
md: "16px",
|
||||
},
|
||||
lineHeight: { xs: "20px", md: "24px" },
|
||||
}}
|
||||
color="#454545"
|
||||
>
|
||||
cents/kWh
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
No pricing data available for this station.
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,32 +1,31 @@
|
|||
body {
|
||||
margin: 0;
|
||||
font-family: "Gilroy";
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
margin: 0;
|
||||
font-family: '"Publica Sans Round Medium", sans-serif';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
||||
monospace;
|
||||
}
|
||||
|
||||
.mui-typography {
|
||||
font-family: "Gilroy";
|
||||
background-color: rgb(7, 127, 233);
|
||||
font-family: "Gilroy";
|
||||
background-color: rgb(7, 127, 233);
|
||||
}
|
||||
|
||||
.css-1w8ddxu-MuiBarElement-root {
|
||||
width: 19px !important;
|
||||
border-radius: 50px !important;
|
||||
rx: 8;
|
||||
ry: 8
|
||||
width: 19px !important;
|
||||
border-radius: 50px !important;
|
||||
rx: 8;
|
||||
ry: 8;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Publica Sans Round Medium';
|
||||
src: url('../public/fonts/PublicaSansRound-Md.otf') format('otf');
|
||||
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
font-family: '"Publica Sans Round Medium", sans-serif';
|
||||
src: url("../public/fonts/PublicaSansRound-Md.otf") format("otf");
|
||||
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
|
|
@ -48,24 +48,25 @@ const Dashboard: React.FC<DashboardProps> = ({
|
|||
return <UserDashboard />;
|
||||
|
||||
default:
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
backgroundColor: "#202020",
|
||||
height: "100vh",
|
||||
textAlign: "center",
|
||||
padding: 2,
|
||||
}}
|
||||
>
|
||||
<Typography color="white">
|
||||
Access Denied: You do not have permission to view
|
||||
this dashboard.
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
return("")
|
||||
// return (
|
||||
// <Box
|
||||
// sx={{
|
||||
// display: "flex",
|
||||
// justifyContent: "center",
|
||||
// alignItems: "center",
|
||||
// backgroundColor: "#D0E1E9",
|
||||
// height: "100vh",
|
||||
// textAlign: "center",
|
||||
// padding: 2,
|
||||
// }}
|
||||
// >
|
||||
// <Typography color="black">
|
||||
// Access Denied: You do not have permission to view
|
||||
// this dashboard.
|
||||
// </Typography>
|
||||
// </Box>
|
||||
// );
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ 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;
|
||||
|
@ -17,6 +16,13 @@ interface TopStation {
|
|||
};
|
||||
}
|
||||
|
||||
interface BasicPrice {
|
||||
port: string;
|
||||
price: number;
|
||||
stationId: number;
|
||||
stationName: string;
|
||||
}
|
||||
|
||||
interface DashboardState {
|
||||
totalAdmins?: number;
|
||||
totalManagers?: number;
|
||||
|
@ -24,6 +30,7 @@ interface DashboardState {
|
|||
totalStations?: number;
|
||||
carPortCounts?: CarPortCount[];
|
||||
topStations?: TopStation[];
|
||||
basicPrice?: BasicPrice[];
|
||||
totalBookings?: number;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
|
@ -36,6 +43,7 @@ const initialState: DashboardState = {
|
|||
totalStations: 0,
|
||||
carPortCounts: [],
|
||||
topStations: [],
|
||||
basicPrice: [],
|
||||
totalBookings: 0,
|
||||
loading: false,
|
||||
error: null,
|
||||
|
@ -44,12 +52,17 @@ const initialState: DashboardState = {
|
|||
// 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
|
||||
{
|
||||
startDateBookings?: string;
|
||||
endDateBookings?: string;
|
||||
startDateStations?: string;
|
||||
endDateStations?: string;
|
||||
},
|
||||
{ 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;
|
||||
const response = await http.get("/dashboard", { params });
|
||||
return response.data as DashboardState; // Ensure response matches DashboardState
|
||||
} catch (error: any) {
|
||||
toast.error("Error Fetching Dashboard Data: " + error.message);
|
||||
return rejectWithValue(
|
||||
|
@ -73,15 +86,16 @@ const dashboardSlice = createSlice({
|
|||
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;
|
||||
// Map response fields to state
|
||||
state.totalAdmins = action.payload.totalAdmins ?? 0;
|
||||
state.totalManagers = action.payload.totalManagers ?? 0;
|
||||
state.totalUsers = action.payload.totalUsers ?? 0;
|
||||
state.totalStations = action.payload.totalStations ?? 0;
|
||||
state.carPortCounts = action.payload.carPortCounts ?? [];
|
||||
state.topStations = action.payload.topStations ?? [];
|
||||
state.basicPrice = action.payload.basicPrice ?? [];
|
||||
state.totalBookings = action.payload.totalBookings ?? 0;
|
||||
}
|
||||
|
||||
)
|
||||
.addCase(fetchDashboardData.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
|
|
|
@ -35,7 +35,7 @@ export const stationDetailList = createAsyncThunk<
|
|||
const token = localStorage?.getItem("authToken");
|
||||
if (!token) throw new Error("No token found");
|
||||
|
||||
const response = await http.get("/get-station-details");
|
||||
const response = await http.get("/get-station-detail");
|
||||
if (!response.data?.data) throw new Error("Invalid API response");
|
||||
return response.data.data;
|
||||
} catch (error: any) {
|
||||
|
@ -88,8 +88,7 @@ export const updateStationDetails = createAsyncThunk<
|
|||
try {
|
||||
const response = await http.patch(
|
||||
`/update-station-details/${id}`,
|
||||
managerStationData,
|
||||
|
||||
managerStationData
|
||||
);
|
||||
toast.success("Station Details updated successfully");
|
||||
return response.data; // Return the updated data
|
||||
|
@ -207,7 +206,7 @@ const managerStationSlice = createSlice({
|
|||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
.addCase(deleteStationDetails.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error =
|
||||
|
|
Loading…
Reference in a new issue