diff --git a/public/apple_store.png b/public/apple_store.png new file mode 100644 index 0000000..c37372c Binary files /dev/null and b/public/apple_store.png differ diff --git a/public/dev-bg.png b/public/dev-bg.png new file mode 100644 index 0000000..794996d Binary files /dev/null and b/public/dev-bg.png differ diff --git a/public/developer.png b/public/developer.png new file mode 100644 index 0000000..f67cb23 Binary files /dev/null and b/public/developer.png differ diff --git a/public/google_play.png b/public/google_play.png new file mode 100644 index 0000000..a31274d Binary files /dev/null and b/public/google_play.png differ diff --git a/public/iMockup - iPhone.png b/public/iMockup - iPhone.png new file mode 100644 index 0000000..6745822 Binary files /dev/null and b/public/iMockup - iPhone.png differ diff --git a/public/tablet-img.png b/public/tablet-img.png new file mode 100644 index 0000000..18e83d0 Binary files /dev/null and b/public/tablet-img.png differ diff --git a/public/vector-arrows.png b/public/vector-arrows.png new file mode 100644 index 0000000..516aeb3 Binary files /dev/null and b/public/vector-arrows.png differ diff --git a/src/components/AddBookingModal/addBookingModal.tsx b/src/components/AddBookingModal/addBookingModal.tsx index 3209344..605b1c8 100644 --- a/src/components/AddBookingModal/addBookingModal.tsx +++ b/src/components/AddBookingModal/addBookingModal.tsx @@ -219,7 +219,7 @@ export default function AddBookingModal({ {carNames.map((car, index) => ( {car.name} diff --git a/src/components/CustomTable/customTable.tsx b/src/components/CustomTable/customTable.tsx index 724a039..c62eda9 100644 --- a/src/components/CustomTable/customTable.tsx +++ b/src/components/CustomTable/customTable.tsx @@ -203,17 +203,15 @@ const CustomTable: React.FC = ({ 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") + ) && ( + + + {/* Line Chart */} + 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')", + }, + }} + > + + + + + ); +} diff --git a/src/components/MainGrid/mainGrid.tsx b/src/components/MainGrid/mainGrid.tsx index b0036e4..43ae733 100644 --- a/src/components/MainGrid/mainGrid.tsx +++ b/src/components/MainGrid/mainGrid.tsx @@ -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(); + 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 ( - - {/* Dashboard Header */} - - Dashboard - + + {/* Dashboard Header */} + + Dashboard + - {/* Grid Layout */} - - {/* Statistic Cards */} - {data.map((card, index) => ( - - - - ))} + {/* Grid Layout */} + + {/* Statistic Cards */} + {statCards.map((card, index) => ( + + + + ))} - {/* Charts */} - - - - - - - - - - - - - - + {/* Charts */} + + + + + + + + + + + + + + ); } diff --git a/src/components/MenuContent/index.tsx b/src/components/MenuContent/index.tsx index b7ae4a8..5938723 100644 --- a/src/components/MenuContent/index.tsx +++ b/src/components/MenuContent/index.tsx @@ -36,9 +36,9 @@ export default function MenuContent({ hidden }: PropType) { url: "/panel/admin-list", }, userRole === "superadmin" && { - text: "Managers", + text: "Manager", icon: , - 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: , - url: "/panel/slot-list", // Placeholder for now + url: "/panel/all-available-slots", // Placeholder for now }, userRole === "user" && { text: "Near By Stations", diff --git a/src/components/ResourcePieChart/resourcePieChart.tsx b/src/components/ResourcePieChart/resourcePieChart.tsx index 49ae19a..8e95f91 100644 --- a/src/components/ResourcePieChart/resourcePieChart.tsx +++ b/src/components/ResourcePieChart/resourcePieChart.tsx @@ -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(); + // // 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 ( - - - - Resources - - - + + + + Car Port Usage + + + ({ + 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 }, + }} + /> - - {data.map((entry, index) => ( - - - - {entry.title} - - - ))} - - - - + + {dataToDisplay.map((entry, index) => ( + + + + {entry.carPort} + + + ))} + + + + ); } \ No newline at end of file diff --git a/src/components/StatCard/statCard.tsx b/src/components/StatCard/statCard.tsx index a52b553..dba1a2c 100644 --- a/src/components/StatCard/statCard.tsx +++ b/src/components/StatCard/statCard.tsx @@ -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) { diff --git a/src/components/barChartCard/barChartCard.tsx b/src/components/barChartCard/barChartCard.tsx index fbd2046..01f7493 100644 --- a/src/components/barChartCard/barChartCard.tsx +++ b/src/components/barChartCard/barChartCard.tsx @@ -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(); - const handleChange = (event: SelectChangeEvent) => { - 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 ( + {/* Header */} - Charge Stats + Top Stations Stats - - - - + {/* Date Filters */} + + setStartDateStations(e.target.value)} + sx={{ + backgroundColor: "#202020", + borderRadius: "8px", + "& .MuiInputBase-input": { + color: "#F2F2F2", + }, + }} + /> + setEndDateStations(e.target.value)} + sx={{ + backgroundColor: "#202020", + borderRadius: "8px", + "& .MuiInputBase-input": { + color: "#F2F2F2", + }, + }} + /> + + + + {/* Chart */} admin?.userType === "admin").map( ( admin: { id: string; diff --git a/src/pages/AllMangersList/index.tsx b/src/pages/AllMangersList/index.tsx new file mode 100644 index 0000000..f5bdbdb --- /dev/null +++ b/src/pages/AllMangersList/index.tsx @@ -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(); + 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 */} + + + ); +} diff --git a/src/pages/AvailableSlotsList/index.tsx b/src/pages/AvailableSlotsList/index.tsx new file mode 100644 index 0000000..7301c67 --- /dev/null +++ b/src/pages/AvailableSlotsList/index.tsx @@ -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(); + 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 ( + <> + + + + ); +} diff --git a/src/pages/EvSlotList/index.tsx b/src/pages/EvSlotList/index.tsx index f2da394..0e4269e 100644 --- a/src/pages/EvSlotList/index.tsx +++ b/src/pages/EvSlotList/index.tsx @@ -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(); 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", diff --git a/src/pages/LandingPage/index.tsx b/src/pages/LandingPage/index.tsx index 152299a..888e6e5 100644 --- a/src/pages/LandingPage/index.tsx +++ b/src/pages/LandingPage/index.tsx @@ -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: , + title: "Seamless Charging Experience", + description: + "Find and book EV charging stations effortlessly with DigiEv's smart platform.", + }, + { + icon: , + title: "Real-Time Availability", + description: + "Check live station availability and optimize your route for an efficient charge.", + }, + { + icon: , + title: "Widespread Network", + description: + "Access a vast network of EV stations across multiple locations with ease.", + }, + { + icon: , // 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 ( {/* Navbar */} @@ -55,8 +95,8 @@ const LandingPage = () => { }} /> - {/* Hero Section */} { - - {" "} + + - {" "} - - {" "} + - {" "} - 50+{" "} - {" "} - - {" "} - Successful Digital Transformations{" "} - {" "} - {" "} - - {" "} + 50+ + + + Successful Digital Transformations + + + - {" "} - 100%{" "} - {" "} - - {" "} - Client Satisfaction{" "} - {" "} - {" "} - - {" "} + 100% + + + Client Satisfaction + + + - {" "} - 20+{" "} - {" "} - - {" "} - Global Partnerships{" "} - {" "} - {" "} - {" "} + 20+ + + + Global Partnerships + + + {/* New Statistic */} + + + 10+ + + + Years of Innovation + + + + + + + + Welcome to DigiEv{" "} + - Your EV Charging Partner + + + + Simplifying Electric Vehicle Charging + + + 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. + + + DigiEv Dashboard + + + {features.map((feature, index) => ( + + + + + {feature.icon} + + + {feature.title} + + + {feature.description} + + + + + ))} + + + + + Key Features + + + iMockup - iPhone + + {/* Top Left */} + + {/* Arrow pointing to mockup */} + Arrow + + Seamless Navigation + + + Effortlessly locate and access EV charging + stations with our intuitive map integration. + + + {/* Top Right */} + + {/* Arrow pointing to mockup */} + Arrow + + Live Availability + + + View real-time charger availability to plan your + trips efficiently. + + + {/* Bottom Left */} + + {/* Arrow pointing to mockup */} + Arrow + + Smart Recommendations + + + Get personalized station suggestions based on + your location and usage patterns. + + + {/* Bottom Right */} + + {/* Arrow pointing to mockup */} + Arrow + + Secure Payments + + + Make hassle-free transactions with our secure + payment gateway. + + + + + + {/* Footer */} + + + + {/* Primary Image */} + Developer + {/* Overlapping Image */} + developer + + Get your application developed by our certified + experts today! + + + + + + + + + + + Your{" "} + + Perfect Experience + + , Just a Tap Away! + + + + 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. + + + Google Play + App Store + + ); diff --git a/src/pages/UserList/index.tsx b/src/pages/UserList/index.tsx index cf8eb07..4fc21fa 100644 --- a/src/pages/UserList/index.tsx +++ b/src/pages/UserList/index.tsx @@ -20,7 +20,7 @@ export default function UserList() { const dispatch = useDispatch(); 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) => ({ diff --git a/src/redux/reducers.ts b/src/redux/reducers.ts index 9e2059d..898a677 100644 --- a/src/redux/reducers.ts +++ b/src/redux/reducers.ts @@ -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; diff --git a/src/redux/slices/adminSlice.ts b/src/redux/slices/adminSlice.ts index 25584f8..c73ab79 100644 --- a/src/redux/slices/adminSlice.ts +++ b/src/redux/slices/adminSlice.ts @@ -13,9 +13,10 @@ interface User { } interface Admin { + Admins: any; id: string; name: string; - role: string; + userType: string; email: string; phone: string; registeredAddress: string; diff --git a/src/redux/slices/bookSlice.ts b/src/redux/slices/bookSlice.ts index 52e735b..a49c9f1 100644 --- a/src/redux/slices/bookSlice.ts +++ b/src/redux/slices/bookSlice.ts @@ -19,6 +19,8 @@ interface Booking { startTime: string; endTime: string; carDetails: CarDetails; + carNames: string[]; // For car names + carPorts: string[]; // For car ports } interface BookingState { diff --git a/src/redux/slices/dashboardSlice.ts b/src/redux/slices/dashboardSlice.ts new file mode 100644 index 0000000..f4bc836 --- /dev/null +++ b/src/redux/slices/dashboardSlice.ts @@ -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) => { + 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; diff --git a/src/redux/slices/managerSlice.ts b/src/redux/slices/managerSlice.ts index 3ed22c7..0e7ca7d 100644 --- a/src/redux/slices/managerSlice.ts +++ b/src/redux/slices/managerSlice.ts @@ -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) => { + 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) => { diff --git a/src/redux/slices/slotSlice.ts b/src/redux/slices/slotSlice.ts index 04a9b58..d6d6a29 100644 --- a/src/redux/slices/slotSlice.ts +++ b/src/redux/slices/slotSlice.ts @@ -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) => { + 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; }) diff --git a/src/router.tsx b/src/router.tsx index dab1948..be0466e 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -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() { }> {/* Default Route */} - } /> + } /> {/* Auth Routes */} @@ -117,6 +118,18 @@ export default function AppRouter() { /> } /> + } /> + } + /> + } /> + } + /> {/* Catch-all Route */}