dev-jaanvi #1

Open
jaanvi wants to merge 155 commits from dev-jaanvi into main
8 changed files with 245 additions and 156 deletions
Showing only changes of commit 156925abfa - Show all commits

View file

@ -24,7 +24,7 @@ html, body {
@font-face { @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'); src: url('../public/fonts/PublicaSansRound-Md.otf') format('otf');

View file

@ -61,9 +61,9 @@ export default function ManagerGrid() {
{/* Charts */} {/* Charts */}
<Grid container spacing={3} mt={1}> <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 /> <SessionsChart />
</Grid> </Grid> */}
<Grid item xs={12} sm={12} md={6} lg={6}> <Grid item xs={12} sm={12} md={6} lg={6}>
<ResourcesPieChart /> <ResourcesPieChart />
</Grid> </Grid>

View file

@ -86,11 +86,12 @@ export default function UserDashboard() {
sx={{ sx={{
width: "100%", width: "100%",
color: "#DE0E1E9", color: "#DE0E1E9",
py: 3, py: 0,
mt:0
}} }}
> >
{/* Greeting */} {/* Greeting */}
<Box display="flex" alignItems="center" px={3} mb={4}> <Box display="flex" alignItems="center" px={3} mb={3} >
{/* <Avatar {/* <Avatar
sx={{ sx={{
width: 64, width: 64,
@ -106,7 +107,7 @@ export default function UserDashboard() {
{user?.name?.charAt(0).toUpperCase() || "U"} {user?.name?.charAt(0).toUpperCase() || "U"}
</Avatar> */} </Avatar> */}
<Box> <Box>
<Typography variant="h3"> <Typography variant="h4">
Welcome back, {user?.name || "User"}! Welcome back, {user?.name || "User"}!
</Typography> </Typography>
<Typography color="#DE0E1E9"> <Typography color="#DE0E1E9">

View file

@ -1,4 +1,5 @@
import * as React from "react"; import * as React from "react";
import { useDispatch, useSelector } from "react-redux";
import Card from "@mui/material/Card"; import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent"; import CardContent from "@mui/material/CardContent";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
@ -8,24 +9,54 @@ import MenuItem from "@mui/material/MenuItem";
import FormControl from "@mui/material/FormControl"; import FormControl from "@mui/material/FormControl";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import { useTheme } from "@mui/material/styles"; 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() { export default function SessionsChart() {
const theme = useTheme(); const theme = useTheme();
const [selectedStation, setSelectedStation] = React.useState( const dispatch = useDispatch();
"Delhi NCR EV Station" const [selectedStation, setSelectedStation] = React.useState<string>("");
);
const handleChange = (event: { target: { value: React.SetStateAction<string> } }) => { // Get the dashboard data from Redux state
setSelectedStation(event.target.value); 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 <Card
variant="outlined" variant="outlined"
sx={{ sx={{
width: "100%", width: "100%",
height: "auto", height: "auto",
minHeight: { xs: "260px", sm: "270px", md: "290px" }, minHeight: { xs: "360px", sm: "400px", md: "421px" },
gap: "16px", gap: "16px",
borderRadius: "30px", borderRadius: "30px",
padding: { xs: "12px", sm: "16px", md: "20px" }, padding: { xs: "12px", sm: "16px", md: "20px" },
@ -34,18 +65,23 @@ export default function SessionsChart() {
}} }}
> >
<CardContent <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 <Typography
variant="h6" variant="h6"
align="left" align="left"
// color="#F2F2F2"
sx={{ sx={{
fontWeight: 600, fontWeight: 600,
fontSize: { xs: "12px", sm: "14px", md: "16px" }, fontSize: { xs: "12px", sm: "14px", md: "16px" },
lineHeight: "24px", lineHeight: "24px",
letterSpacing: "0%", letterSpacing: "0%",
// color: "#FAFAFA",
marginBottom: { xs: 1, sm: 0.7, md: 1 }, marginBottom: { xs: 1, sm: 0.7, md: 1 },
}} }}
> >
@ -65,7 +101,8 @@ export default function SessionsChart() {
<Select <Select
value={selectedStation} value={selectedStation}
onChange={handleChange} onChange={handleChange}
label="Select Station" displayEmpty
disabled={loading || stationNames.length === 0}
sx={{ sx={{
color: "#000000", color: "#000000",
"& .MuiSvgIcon-root": { color: "#000000" }, "& .MuiSvgIcon-root": { color: "#000000" },
@ -73,106 +110,144 @@ export default function SessionsChart() {
}} }}
IconComponent={ExpandMoreIcon} IconComponent={ExpandMoreIcon}
> >
<MenuItem value="Delhi NCR EV Station"> {stationNames.length === 0 && (
Delhi NCR EV Station <MenuItem value="" disabled>
</MenuItem> Loading stations...
<MenuItem value="Mumbai EV Station"> </MenuItem>
Mumbai EV Station )}
</MenuItem> {stationNames.map((name) => (
<MenuItem value="Bangalore EV Station"> <MenuItem key={name} value={name}>
Bangalore EV Station {name}
</MenuItem> </MenuItem>
<MenuItem value="Pune EV Station"> ))}
Pune EV Station
</MenuItem>
</Select> </Select>
</FormControl> </FormControl>
{/* Grid container for the four boxes */} {/* Display the base prices for the selected station */}
<Box <Box
sx={{ sx={{
display: "grid", display: "grid",
gridTemplateColumns: { gridTemplateColumns: {
xs: "repeat(1, 1fr)", // 1 column on mobile xs: "repeat(1, 1fr)",
sm: "repeat(2, 1fr)", // 2 columns on tablets sm: "repeat(2, 1fr)",
md: "repeat(2, 1fr)", // 2x2 grid on desktop md: "repeat(2, 1fr)",
}, },
gap: { xs: 1, sm: 1.5, md: 2 }, gap: { xs: 1, sm: 1.5, md: 2 },
width: "100%", width: "100%",
}} }}
> >
{[1, 2, 3, 4].map((item) => ( {selectedBasePrices.length > 0 ? (
<Box selectedBasePrices.map(
key={item} (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={{ sx={{
height: { fontSize: {
xs: "105px", xs: "12px",
sm: "115px", sm: "14px",
md: "128px", md: "16px",
}, },
borderRadius: "8px", color: "#454545",
p: { xs: "10px", sm: "12px", md: "14px" }, textAlign: "center",
// backgroundColor: "#272727", gridColumn: "span 2",
// color: "#D9D8D8",
backgroundColor: "#D9E7ED",
display: "flex",
flexDirection: "column",
justifyContent: "center",
}} }}
> >
<Typography No pricing data available for this station.
variant="body2" </Typography>
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>
))}
</Box> </Box>
</CardContent> </CardContent>
</Card> </Card>
); );
} }

View file

@ -1,32 +1,31 @@
body { body {
margin: 0; margin: 0;
font-family: "Gilroy"; font-family: '"Publica Sans Round Medium", sans-serif';
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
code { code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace; monospace;
} }
.mui-typography { .mui-typography {
font-family: "Gilroy"; font-family: "Gilroy";
background-color: rgb(7, 127, 233); background-color: rgb(7, 127, 233);
} }
.css-1w8ddxu-MuiBarElement-root { .css-1w8ddxu-MuiBarElement-root {
width: 19px !important; width: 19px !important;
border-radius: 50px !important; border-radius: 50px !important;
rx: 8; rx: 8;
ry: 8 ry: 8;
} }
@font-face { @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'); src: url("../public/fonts/PublicaSansRound-Md.otf") format("otf");
font-weight: 500; font-weight: 500;
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
} }

View file

@ -48,24 +48,25 @@ const Dashboard: React.FC<DashboardProps> = ({
return <UserDashboard />; return <UserDashboard />;
default: default:
return ( return("")
<Box // return (
sx={{ // <Box
display: "flex", // sx={{
justifyContent: "center", // display: "flex",
alignItems: "center", // justifyContent: "center",
backgroundColor: "#202020", // alignItems: "center",
height: "100vh", // backgroundColor: "#D0E1E9",
textAlign: "center", // height: "100vh",
padding: 2, // textAlign: "center",
}} // padding: 2,
> // }}
<Typography color="white"> // >
Access Denied: You do not have permission to view // <Typography color="black">
this dashboard. // Access Denied: You do not have permission to view
</Typography> // this dashboard.
</Box> // </Typography>
); // </Box>
// );
} }
}; };

View file

@ -2,7 +2,6 @@ import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
import http from "../../lib/https"; import http from "../../lib/https";
import { toast } from "sonner"; import { toast } from "sonner";
// Define interfaces for the dashboard data structure
interface CarPortCount { interface CarPortCount {
carPort: string; carPort: string;
count: number; count: number;
@ -17,6 +16,13 @@ interface TopStation {
}; };
} }
interface BasicPrice {
port: string;
price: number;
stationId: number;
stationName: string;
}
interface DashboardState { interface DashboardState {
totalAdmins?: number; totalAdmins?: number;
totalManagers?: number; totalManagers?: number;
@ -24,6 +30,7 @@ interface DashboardState {
totalStations?: number; totalStations?: number;
carPortCounts?: CarPortCount[]; carPortCounts?: CarPortCount[];
topStations?: TopStation[]; topStations?: TopStation[];
basicPrice?: BasicPrice[];
totalBookings?: number; totalBookings?: number;
loading: boolean; loading: boolean;
error: string | null; error: string | null;
@ -36,6 +43,7 @@ const initialState: DashboardState = {
totalStations: 0, totalStations: 0,
carPortCounts: [], carPortCounts: [],
topStations: [], topStations: [],
basicPrice: [],
totalBookings: 0, totalBookings: 0,
loading: false, loading: false,
error: null, error: null,
@ -44,12 +52,17 @@ const initialState: DashboardState = {
// Async thunk for fetching dashboard data // Async thunk for fetching dashboard data
export const fetchDashboardData = createAsyncThunk< export const fetchDashboardData = createAsyncThunk<
DashboardState, 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 } { rejectValue: string }
>("dashboard/fetchDashboardData", async (params, { rejectWithValue }) => { >("dashboard/fetchDashboardData", async (params, { rejectWithValue }) => {
try { try {
const response = await http.get("/dashboard", { params }); // Pass startDate and endDate as query params const response = await http.get("/dashboard", { params });
return response.data; return response.data as DashboardState; // Ensure response matches DashboardState
} catch (error: any) { } catch (error: any) {
toast.error("Error Fetching Dashboard Data: " + error.message); toast.error("Error Fetching Dashboard Data: " + error.message);
return rejectWithValue( return rejectWithValue(
@ -73,15 +86,16 @@ const dashboardSlice = createSlice({
fetchDashboardData.fulfilled, fetchDashboardData.fulfilled,
(state, action: PayloadAction<DashboardState>) => { (state, action: PayloadAction<DashboardState>) => {
state.loading = false; state.loading = false;
state.totalAdmins = action.payload.totalAdmins; // Map response fields to state
state.totalManagers = action.payload.totalManagers; state.totalAdmins = action.payload.totalAdmins ?? 0;
state.totalUsers = action.payload.totalUsers; state.totalManagers = action.payload.totalManagers ?? 0;
state.totalStations = action.payload.totalStations; state.totalUsers = action.payload.totalUsers ?? 0;
state.carPortCounts = action.payload.carPortCounts; state.totalStations = action.payload.totalStations ?? 0;
state.topStations = action.payload.topStations; state.carPortCounts = action.payload.carPortCounts ?? [];
state.totalBookings = action.payload.totalBookings; state.topStations = action.payload.topStations ?? [];
state.basicPrice = action.payload.basicPrice ?? [];
state.totalBookings = action.payload.totalBookings ?? 0;
} }
) )
.addCase(fetchDashboardData.rejected, (state, action) => { .addCase(fetchDashboardData.rejected, (state, action) => {
state.loading = false; state.loading = false;

View file

@ -35,7 +35,7 @@ export const stationDetailList = createAsyncThunk<
const token = localStorage?.getItem("authToken"); const token = localStorage?.getItem("authToken");
if (!token) throw new Error("No token found"); 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"); if (!response.data?.data) throw new Error("Invalid API response");
return response.data.data; return response.data.data;
} catch (error: any) { } catch (error: any) {
@ -88,8 +88,7 @@ export const updateStationDetails = createAsyncThunk<
try { try {
const response = await http.patch( const response = await http.patch(
`/update-station-details/${id}`, `/update-station-details/${id}`,
managerStationData, managerStationData
); );
toast.success("Station Details updated successfully"); toast.success("Station Details updated successfully");
return response.data; // Return the updated data return response.data; // Return the updated data