dev-jaanvi #1

Open
jaanvi wants to merge 155 commits from dev-jaanvi into main
22 changed files with 25171 additions and 14419 deletions
Showing only changes of commit 7de0799b02 - Show all commits

View file

@ -1,6 +1,8 @@
{ {
"trailingComma": "es5",
"tabWidth": 4, "tabWidth": 4,
"semi": false, "useTabs": true,
"singleQuote": false "semi": true,
"singleQuote": false,
"bracketSpacing": true
} }

15491
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
{ {
"name": "digi-health-admin-dashboard", "name": "digi-ev-admin-dashboard",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
@ -13,33 +13,34 @@
"@mui/x-tree-view": "^7.23.2", "@mui/x-tree-view": "^7.23.2",
"@react-spring/web": "^9.7.5", "@react-spring/web": "^9.7.5",
"@reduxjs/toolkit": "^2.5.0", "@reduxjs/toolkit": "^2.5.0",
"AdapterDayjs": "link:@mui/x-date-pickers/AdapterDayjs", "AdapterDayjs": "file:@mui/x-date-pickers/AdapterDayjs",
"AppBar": "link:@mui/material/AppBar",
"Box": "link:@mui/material/Box",
"PieChart": "link:@mui/x-charts/PieChart",
"RichTreeView": "link:@mui/x-tree-view/RichTreeView",
"Stack": "link:@mui/material/Stack",
"Tabs": "link:@mui/material/Tabs",
"Toolbar": "link:@mui/material/Toolbar",
"Typography": "link:@mui/material/Typography",
"add": "^2.0.6", "add": "^2.0.6",
"AppBar": "file:@mui/material/AppBar",
"axios": "^1.7.9", "axios": "^1.7.9",
"Box": "file:@mui/material/Box",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cra-template-typescript": "1.2.0", "cra-template-typescript": "1.2.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"hooks": "link:@mui/x-charts/hooks", "hooks": "file:@mui/x-charts/hooks",
"mui-phone-number": "^3.0.3", "mui-phone-number": "^3.0.3",
"mui-tel-input": "^7.0.0", "mui-tel-input": "^7.0.0",
"PieChart": "file:@mui/x-charts/PieChart",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "^19.0.0", "react": "^18.0.0",
"react-dom": "^19.0.0", "react-cookie": "^7.2.2",
"react-dom": "^18.0.0",
"react-dropzone": "^14.3.5", "react-dropzone": "^14.3.5",
"react-hook-form": "^7.54.2", "react-hook-form": "^7.54.2",
"react-redux": "^9.2.0", "react-redux": "^9.2.0",
"react-router-dom": "^7.1.1", "react-router-dom": "^7.1.1",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"react-toastify": "^11.0.2", "react-toastify": "^11.0.2",
"styles": "link:@mui/material/styles", "RichTreeView": "file:@mui/x-tree-view/RichTreeView",
"Stack": "file:@mui/material/Stack",
"styles": "file:@mui/material/styles",
"Tabs": "file:@mui/material/Tabs",
"Toolbar": "file:@mui/material/Toolbar",
"Typography": "file:@mui/material/Typography",
"web-vitals": "^4.2.4" "web-vitals": "^4.2.4"
}, },
"scripts": { "scripts": {
@ -68,8 +69,8 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.10.5", "@types/node": "^22.10.5",
"@types/react": "^19.0.3", "@types/react": "^19.0.4",
"@types/react-dom": "^19.0.2", "@types/react-dom": "^19.0.2",
"typescript": "^5.7.2" "typescript": "^5.7.3"
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -10,34 +10,12 @@
content="Web site created using create-react-app" content="Web site created using create-react-app"
/> />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
work correctly both with client-side routing and a non-root public URL.
jaanvi marked this conversation as resolved Outdated

add app specific meta description.

add app specific meta description.
Learn how to configure a non-root public URL by running `npm run build`. <title>DigiEV - Eco-friendly Charge</title>
-->
<title>React App</title>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div> <div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body> </body>
</html> </html>

View file

@ -1,5 +1,5 @@
import { BrowserRouter as Router} from 'react-router-dom'; import { BrowserRouter as Router } from "react-router-dom";
import AppRouter from './router'; import AppRouter from "./router";
function App() { function App() {
return ( return (

View file

@ -1,33 +1,221 @@
// import React, { useEffect } from "react";
// import {
// Button,
// Dialog,
// DialogActions,
// DialogContent,
// DialogTitle,
// TextField,
// } from "@mui/material";
// import { useForm, Controller } from "react-hook-form";
// import { useSelector } from "react-redux";
// import { RootState } from "../../redux/store/store";
// //By Jaanvi : Edit Model :: 11-feb-25
// interface AddEditCategoryModalProps {
// open: boolean;
// handleClose: () => void;
// editRow: any;
// handleUpdate: (id: string, name: string, role: string) => void;
// }
// interface FormData {
// role: string;
// name: string;
// category: string;
// }
// const AddEditCategoryModal: React.FC<AddEditCategoryModalProps> = ({
// open,
// handleClose,
// editRow,
// handleUpdate,
// }) => {
// const {
// control,
// handleSubmit,
// formState: { errors },
// setValue,
// reset,
// } = useForm<FormData>({
// defaultValues: {
// category: "",
// name: "",
// role: "",
// },
// });
// const { isLoading } = useSelector((state: RootState) => state.auth.admin);
// const onSubmit = (data: FormData) => {
// if (editRow) {
// handleUpdate(editRow.id, data.name, data.role);
// handleClose();
// reset();
// } else {
// console.log(data.category);
// }
// };
// useEffect(() => {
// if (editRow) {
// setValue("category", editRow.name);
// setValue("name", editRow.name);
// setValue("role", editRow.role);
// } else {
// reset();
// }
// }, [editRow, setValue, reset]);
// return (
// <>
// <Dialog
// open={open}
// onClose={handleClose}
// maxWidth="md"
// fullWidth
// PaperProps={{
// component: "form",
// onSubmit: handleSubmit(onSubmit),
// }}
// >
// <DialogTitle>{editRow ? "Edit" : "Add"} Category</DialogTitle>
// <DialogContent>
// <Controller
// name="category"
// control={control}
// rules={{
// required: "Category Name is required",
// }}
// render={({ field }) => (
// <TextField
// {...field}
// autoFocus
// required
// margin="dense"
// label="Add Category Name"
// type="text"
// fullWidth
// variant="standard"
// error={!!errors.category}
// helperText={errors.category?.message}
// />
// )}
// />
// {/* AdminField */}
// <Controller
// name="name"
// control={control}
// rules={{
// required: "Admin Name is required",
// }}
// render={({ field }) => (
// <TextField
// {...field}
// autoFocus
// required
// margin="dense"
// label="Admin Name"
// type="text"
// fullWidth
// variant="standard"
// error={!!errors.name}
// helperText={errors.name?.message}
// />
// )}
// />
// <Controller
// name="role"
// control={control}
// rules={{
// required: "Role is required",
// }}
// render={({ field }) => (
// <TextField
// {...field}
// margin="dense"
// label="Role"
// type="text"
// fullWidth
// variant="standard"
// error={!!errors.role}
// helperText={errors.role?.message}
// />
// )}
// />
// </DialogContent>
// <DialogActions>
// <Button onClick={handleClose}>Cancel</Button>
// <Button type="submit">Save</Button>
// </DialogActions>
// </Dialog>
// </>
// );
// };
// export default AddEditCategoryModal;
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, TextField } from "@mui/material"; import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
TextField,
} from "@mui/material";
import { useForm, Controller } from "react-hook-form"; import { useForm, Controller } from "react-hook-form";
//By Jaanvi : Edit Model :: 11-feb-25
interface AddEditCategoryModalProps { interface AddEditCategoryModalProps {
open: boolean; open: boolean;
handleClose: () => void; handleClose: () => void;
editRow: any; editRow: any;
handleUpdate: (id: string, name: string, role: string) => void;
} }
interface FormData { interface FormData {
category: string; role: string;
name: string;
//category: string;
} }
const AddEditCategoryModal: React.FC<AddEditCategoryModalProps> = ({ open, handleClose,editRow }) => { const AddEditCategoryModal: React.FC<AddEditCategoryModalProps> = ({
const { control, handleSubmit, formState: { errors },setValue,reset } = useForm<FormData>({ open,
handleClose,
editRow,
handleUpdate,
}) => {
const {
control,
handleSubmit,
formState: { errors },
setValue,
reset,
} = useForm<FormData>({
defaultValues: { defaultValues: {
category: "", //category: "",
name: "",
role: "",
}, },
}); });
const onSubmit = (data: FormData) => { const onSubmit = (data: FormData) => {
console.log(data.category); if (editRow) {
handleUpdate(editRow.id, data.name, data.role);
}
// else {
// console.log(data.category);
// }
handleClose(); handleClose();
reset(); reset();
}; };
useEffect(() => { useEffect(() => {
if (editRow) { if (editRow) {
setValue('category', editRow.name); // setValue("category", editRow.name);
setValue("name", editRow.name);
setValue("role", editRow.role);
} else { } else {
reset(); reset();
} }
@ -41,18 +229,17 @@ const AddEditCategoryModal: React.FC<AddEditCategoryModalProps> = ({ open, handl
maxWidth="md" maxWidth="md"
fullWidth fullWidth
PaperProps={{ PaperProps={{
component: 'form', component: "form",
onSubmit: handleSubmit(onSubmit), onSubmit: handleSubmit(onSubmit),
}} }}
> >
<DialogTitle>{editRow ? "Editwefwefwe" : 'Add'} Category</DialogTitle> <DialogTitle>{editRow ? "Edit" : "Add"} Category</DialogTitle>
<DialogContent> <DialogContent>
<Controller {/* <Controller
name="category" name="category"
control={control} control={control}
rules={{ rules={{
required: "Category Name is required", required: "Category Name is required",
}} }}
render={({ field }) => ( render={({ field }) => (
<TextField <TextField
@ -68,6 +255,48 @@ const AddEditCategoryModal: React.FC<AddEditCategoryModalProps> = ({ open, handl
helperText={errors.category?.message} helperText={errors.category?.message}
/> />
)} )}
/> */}
{/* AdminField */}
<Controller
name="name"
control={control}
rules={{
required: "Admin Name is required",
}}
render={({ field }) => (
<TextField
{...field}
autoFocus
required
margin="dense"
label="Admin Name"
type="text"
fullWidth
variant="standard"
error={!!errors.name}
helperText={errors.name?.message}
/>
)}
/>
<Controller
name="role"
control={control}
rules={{
required: "Role is required",
}}
render={({ field }) => (
<TextField
{...field}
margin="dense"
label="Role"
type="text"
fullWidth
variant="standard"
error={!!errors.role}
helperText={errors.role?.message}
/>
)}
/> />
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>

View file

@ -1,12 +1,12 @@
import * as React from 'react'; import * as React from "react";
import { styled } from '@mui/material/styles'; import { styled } from "@mui/material/styles";
import Table from '@mui/material/Table'; import Table from "@mui/material/Table";
import TableBody from '@mui/material/TableBody'; import TableBody from "@mui/material/TableBody";
import TableCell, { tableCellClasses } from '@mui/material/TableCell'; import TableCell, { tableCellClasses } from "@mui/material/TableCell";
import TableContainer from '@mui/material/TableContainer'; import TableContainer from "@mui/material/TableContainer";
import TableHead from '@mui/material/TableHead'; import TableHead from "@mui/material/TableHead";
import TableRow from '@mui/material/TableRow'; import TableRow from "@mui/material/TableRow";
import Paper, { paperClasses } from '@mui/material/Paper'; import Paper, { paperClasses } from "@mui/material/Paper";
import { import {
Box, Box,
Button, Button,
@ -14,13 +14,13 @@ import {
IconButton, IconButton,
listClasses, listClasses,
Menu, Menu,
} from '@mui/material'; } from "@mui/material";
import MoreVertRoundedIcon from '@mui/icons-material/MoreVertRounded'; import MoreVertRoundedIcon from "@mui/icons-material/MoreVertRounded";
// Styled components for customization // Styled components for customization
const StyledTableCell = styled(TableCell)(({ theme }) => ({ const StyledTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: { [`&.${tableCellClasses.head}`]: {
backgroundColor: ' #1565c0', backgroundColor: " #1565c0",
color: theme.palette.common.white, color: theme.palette.common.white,
}, },
[`&.${tableCellClasses.body}`]: { [`&.${tableCellClasses.body}`]: {
@ -29,10 +29,10 @@ const StyledTableCell = styled(TableCell)(({ theme }) => ({
})); }));
const StyledTableRow = styled(TableRow)(({ theme }) => ({ const StyledTableRow = styled(TableRow)(({ theme }) => ({
'&:nth-of-type(odd)': { "&:nth-of-type(odd)": {
backgroundColor: theme.palette.action.hover, backgroundColor: theme.palette.action.hover,
}, },
'&:last-child td, &:last-child th': { "&:last-child td, &:last-child th": {
border: 0, border: 0,
}, },
})); }));
@ -40,7 +40,7 @@ const StyledTableRow = styled(TableRow)(({ theme }) => ({
interface Column { interface Column {
id: string; id: string;
label: string; label: string;
align?: 'left' | 'center' | 'right'; align?: "left" | "center" | "right";
} }
interface Row { interface Row {
@ -62,7 +62,7 @@ const CustomTable: React.FC<CustomTableProps> = ({
setRowData, setRowData,
setModalOpen, setModalOpen,
}) => { }) => {
console.log('columnsss', columns, rows); console.log("columnsss", columns, rows);
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null); const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl); const open = Boolean(anchorEl);
@ -74,8 +74,8 @@ const CustomTable: React.FC<CustomTableProps> = ({
}; };
const isImage = (value: any) => { const isImage = (value: any) => {
if (typeof value === 'string') { if (typeof value === "string") {
return value.startsWith('http') || value.startsWith('data:image'); // Check for URL or base64 image return value.startsWith("http") || value.startsWith("data:image"); // Check for URL or base64 image
} }
return false; return false;
}; };
@ -86,7 +86,10 @@ const CustomTable: React.FC<CustomTableProps> = ({
<TableHead> <TableHead>
<TableRow> <TableRow>
{columns.map((column) => ( {columns.map((column) => (
<StyledTableCell key={column.id} align={column.align || 'left'}> <StyledTableCell
key={column.id}
align={column.align || "left"}
>
{column.label} {column.label}
</StyledTableCell> </StyledTableCell>
))} ))}
@ -96,18 +99,21 @@ const CustomTable: React.FC<CustomTableProps> = ({
{rows.map((row, rowIndex) => ( {rows.map((row, rowIndex) => (
<StyledTableRow key={rowIndex}> <StyledTableRow key={rowIndex}>
{columns.map((column) => ( {columns.map((column) => (
<StyledTableCell key={column.id} align={column.align || 'left'}> <StyledTableCell
key={column.id}
align={column.align || "left"}
>
{isImage(row[column.id]) ? ( {isImage(row[column.id]) ? (
<img <img
src={row[column.id]} src={row[column.id]}
alt="Row " alt="Row "
style={{ style={{
width: '50px', width: "50px",
height: '50px', height: "50px",
objectFit: 'cover', objectFit: "cover",
}} }}
/> />
) : column.id !== 'action' ? ( ) : column.id !== "action" ? (
row[column.id] row[column.id]
) : ( ) : (
<IconButton <IconButton
@ -132,25 +138,25 @@ const CustomTable: React.FC<CustomTableProps> = ({
open={open} open={open}
onClose={handleClose} onClose={handleClose}
onClick={handleClose} onClick={handleClose}
transformOrigin={{ horizontal: 'right', vertical: 'top' }} transformOrigin={{ horizontal: "right", vertical: "top" }}
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }} anchorOrigin={{ horizontal: "right", vertical: "bottom" }}
sx={{ sx={{
[`& .${listClasses.root}`]: { [`& .${listClasses.root}`]: {
padding: '4px', padding: "4px",
}, },
[`& .${paperClasses.root}`]: { [`& .${paperClasses.root}`]: {
padding: 0, padding: 0,
}, },
[`& .${dividerClasses.root}`]: { [`& .${dividerClasses.root}`]: {
margin: '4px -4px', margin: "4px -4px",
}, },
}} }}
> >
<Box <Box
sx={{ sx={{
display: 'flex', display: "flex",
flexDirection: 'column', flexDirection: "column",
justifyContent: 'flex-start', justifyContent: "flex-start",
}} }}
> >
<Button <Button
@ -158,9 +164,9 @@ const CustomTable: React.FC<CustomTableProps> = ({
onClick={() => setModalOpen(true)} onClick={() => setModalOpen(true)}
color="primary" color="primary"
sx={{ sx={{
justifyContent: 'flex-start', justifyContent: "flex-start",
py: 0, py: 0,
textTransform: 'capitalize', textTransform: "capitalize",
}} }}
> >
Edit Edit
@ -170,9 +176,9 @@ const CustomTable: React.FC<CustomTableProps> = ({
onClick={() => setDeleteModal(true)} onClick={() => setDeleteModal(true)}
color="error" color="error"
sx={{ sx={{
justifyContent: 'flex-start', justifyContent: "flex-start",
py: 0, py: 0,
textTransform: 'capitalize', textTransform: "capitalize",
}} }}
> >
Delete Delete

View file

@ -1,19 +1,19 @@
import * as React from 'react'; import * as React from "react";
import { styled } from '@mui/material/styles'; import { styled } from "@mui/material/styles";
import Divider, { dividerClasses } from '@mui/material/Divider'; import Divider, { dividerClasses } from "@mui/material/Divider";
import Menu from '@mui/material/Menu'; import Menu from "@mui/material/Menu";
import MuiMenuItem from '@mui/material/MenuItem'; import MuiMenuItem from "@mui/material/MenuItem";
import { paperClasses } from '@mui/material/Paper'; import { paperClasses } from "@mui/material/Paper";
import { listClasses } from '@mui/material/List'; import { listClasses } from "@mui/material/List";
import ListItemText from '@mui/material/ListItemText'; import ListItemText from "@mui/material/ListItemText";
import ListItemIcon, { listItemIconClasses } from '@mui/material/ListItemIcon'; import ListItemIcon, { listItemIconClasses } from "@mui/material/ListItemIcon";
import LogoutRoundedIcon from '@mui/icons-material/LogoutRounded'; import LogoutRoundedIcon from "@mui/icons-material/LogoutRounded";
import MoreVertRoundedIcon from '@mui/icons-material/MoreVertRounded'; import MoreVertRoundedIcon from "@mui/icons-material/MoreVertRounded";
import MenuButton from '../MenuButton'; import MenuButton from "../MenuButton";
import { Avatar } from '@mui/material'; import { Avatar } from "@mui/material";
const MenuItem = styled(MuiMenuItem)({ const MenuItem = styled(MuiMenuItem)({
margin: '2px 0', margin: "2px 0",
}); });
export default function OptionsMenu({ avatar }: { avatar?: boolean }) { export default function OptionsMenu({ avatar }: { avatar?: boolean }) {
@ -30,7 +30,7 @@ export default function OptionsMenu({ avatar }: { avatar?: boolean }) {
<MenuButton <MenuButton
aria-label="Open menu" aria-label="Open menu"
onClick={handleClick} onClick={handleClick}
sx={{ borderColor: 'transparent' }} sx={{ borderColor: "transparent" }}
> >
{avatar ? ( {avatar ? (
<MoreVertRoundedIcon /> <MoreVertRoundedIcon />
@ -49,17 +49,17 @@ export default function OptionsMenu({ avatar }: { avatar?: boolean }) {
open={open} open={open}
onClose={handleClose} onClose={handleClose}
onClick={handleClose} onClick={handleClose}
transformOrigin={{ horizontal: 'right', vertical: 'top' }} transformOrigin={{ horizontal: "right", vertical: "top" }}
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }} anchorOrigin={{ horizontal: "right", vertical: "bottom" }}
sx={{ sx={{
[`& .${listClasses.root}`]: { [`& .${listClasses.root}`]: {
padding: '4px', padding: "4px",
}, },
[`& .${paperClasses.root}`]: { [`& .${paperClasses.root}`]: {
padding: 0, padding: 0,
}, },
[`& .${dividerClasses.root}`]: { [`& .${dividerClasses.root}`]: {
margin: '4px -4px', margin: "4px -4px",
}, },
}} }}
> >
@ -73,7 +73,7 @@ export default function OptionsMenu({ avatar }: { avatar?: boolean }) {
onClick={handleClose} onClick={handleClose}
sx={{ sx={{
[`& .${listItemIconClasses.root}`]: { [`& .${listItemIconClasses.root}`]: {
ml: 'auto', ml: "auto",
minWidth: 0, minWidth: 0,
}, },
}} }}

View file

@ -1,39 +1,22 @@
import { intersection, uniq } from 'lodash';
import React from 'react'; import React from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { checkUserAuth } from '../redux/slices/authSlice'; import { checkUserAuth } from '../redux/slices/authSlice';
const useAuth = (caps, { matchAllCaps = false }) => { const useAuth = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [isAuthorized, setIsAuthorized] = React.useState(false); const [isAuthorized, setIsAuthorized] = React.useState(false);
const { isAuthenticated } = useSelector((state) => ({
const { isAuthenticated, userCapabilities } = useSelector((state) => ({
isAuthenticated: state.authReducer.isAuthenticated, isAuthenticated: state.authReducer.isAuthenticated,
userCapabilities: state.authReducer.userCapabilities,
})); }));
const requiredCaps = React.useMemo(
() => uniq(Array.isArray(caps) ? caps : [caps]),
[caps]
);
React.useEffect(() => { React.useEffect(() => {
const userMatchedCaps = intersection(userCapabilities, requiredCaps); if (isAuthenticated) {
let isUserAuthorized = matchAllCaps
? userMatchedCaps?.length === requiredCaps?.length
: userMatchedCaps?.length > 0;
if (requiredCaps.length === 0) {
isUserAuthorized = true;
}
// if (isAuthenticated === null) {
if (isAuthenticated === null || false || undefined) {
dispatch(checkUserAuth()); dispatch(checkUserAuth());
jaanvi marked this conversation as resolved Outdated

If its authenticated then why again checking checkUserAuth.

If its authenticated then why again checking checkUserAuth.
} else { } else {
setIsAuthorized(isAuthenticated && isUserAuthorized); setIsAuthorized(false);
} }
}, [dispatch, isAuthenticated, requiredCaps, userCapabilities]); }, [dispatch, isAuthenticated]);
return { isAuthenticated, isAuthorized }; return { isAuthenticated, isAuthorized };
}; };

View file

@ -1,13 +1,13 @@
import React from 'react'; import React from "react";
import ReactDOM from 'react-dom/client'; import ReactDOM from "react-dom/client";
import './index.css'; import "./index.css";
import reportWebVitals from './reportWebVitals'; import reportWebVitals from "./reportWebVitals";
import App from './App'; import App from "./App";
import { Provider } from 'react-redux'; import { Provider } from "react-redux";
import store from './redux/store/store.ts'; import store from "./redux/store/store.ts";
import { Slide, ToastContainer } from 'react-toastify'; import { Slide, ToastContainer } from "react-toastify";
const root = ReactDOM.createRoot(document.getElementById('root')); const root = ReactDOM.createRoot(document.getElementById("root"));
root.render( root.render(
<React.StrictMode> <React.StrictMode>
<Provider store={store}> <Provider store={store}>
@ -17,7 +17,7 @@ root.render(
hideProgressBar hideProgressBar
theme="dark" theme="dark"
transition={Slide} transition={Slide}
toastStyle={{ border: '1px solid dimgray' }} toastStyle={{ border: "1px solid dimgray" }}
/> />
</Provider> </Provider>
</React.StrictMode> </React.StrictMode>

View file

@ -1,64 +1,78 @@
import React, { useEffect, useState } from "react" import React, { useEffect, useState } from "react";
import { Box, Button, Typography } from "@mui/material" import { Box, Button, Typography } from "@mui/material";
import AddEditCategoryModal from "../../components/AddEditCategoryModal" import AddEditCategoryModal from "../../components/AddEditCategoryModal";
import { useForm } from "react-hook-form" // import { useForm } from "react-hook-form";
import CustomTable from "../../components/CustomTable" import { useForm } from "react-hook-form";
import DeleteModal from "../../components/Modals/DeleteModal/DeleteModal" import CustomTable from "../../components/CustomTable";
import { useDispatch, useSelector } from "react-redux" import DeleteModal from "../../components/Modals/DeleteModal/DeleteModal";
import { adminList } from "../../redux/slices/authSlice" import { useDispatch, useSelector } from "react-redux";
import { AppDispatch, RootState } from "../../redux/store/store" // Import RootState for selector import { adminList, updateAdmin } from "../../redux/slices/authSlice";
import { AppDispatch, RootState } from "../../redux/store/store"; // Import RootState for selector
// Sample data for categories // Sample data for categories
export default function AdminList() { export default function AdminList() {
const [modalOpen, setModalOpen] = useState(false) const [modalOpen, setModalOpen] = useState(false);
const [editRow, setEditRow] = useState<any>(null) const [editRow, setEditRow] = useState<any>(null);
const { reset } = useForm() const { reset } = useForm();
const [deleteModal, setDeleteModal] = React.useState<boolean>(false) const [deleteModal, setDeleteModal] = React.useState<boolean>(false);
const [rowData, setRowData] = React.useState<any | null>(null) const [rowData, setRowData] = React.useState<any | null>(null);
const dispatch = useDispatch<AppDispatch>() const dispatch = useDispatch<AppDispatch>();
// Fetching admin data from the Redux store // Fetching admin data from the Redux store
const admins = useSelector((state: RootState) => state.auth.admins) const admins = useSelector((state: RootState) => state.auth.admins);
// Dispatching the API call when the component mounts // Dispatching the API call when the component mounts
useEffect(() => { useEffect(() => {
dispatch(adminList()) dispatch(adminList());
}, [dispatch]) }, [dispatch]);
const handleClickOpen = () => { const handleClickOpen = () => {
setModalOpen(true) setModalOpen(true);
setEditRow(null) setEditRow(null);
} };
const handleCloseModal = () => { const handleCloseModal = () => {
setModalOpen(false) setModalOpen(false);
reset() reset();
} };
const handleDelete = () => { const handleDelete = () => {
setDeleteModal(false) setDeleteModal(false);
};
//By Jaanvi : Edit feature :: 11-feb-25
const handleUpdate = async (id: string, name: string, role: string) => {
try {
await dispatch(updateAdmin({ id, name, role }));
dispatch(adminList()); // Fetch updated admins list after update
} catch (error) {
console.error("Update failed", error);
} }
};
const categoryColumns = [ const categoryColumns = [
{ id: "srno", label: "Sr No" }, { id: "srno", label: "Sr No" },
{ id: "name", label: "Name" }, { id: "name", label: "Name" },
{ id: "role", label: "Role" }, { id: "role", label: "Role" },
{ id: "action", label: "Action", align: "center" }, { id: "action", label: "Action", align: "center" },
] ];
// If no admins are available, display the sample data // If no admins are available, display the sample data
const categoryRows = admins?.length const categoryRows = admins?.length
? admins?.map( ? admins?.map(
(admin: { name: string; role: string }, index: number) => ({ (
admin: { id: string; name: string; role: string },
index: number
) => ({
srno: index + 1, srno: index + 1,
id: admin?.id,
name: admin?.name, name: admin?.name,
role: admin?.role || "N/A", role: admin?.role || "N/A",
}) })
) )
: [] : [];
return ( return (
<> <>
@ -103,6 +117,7 @@ export default function AdminList() {
open={modalOpen} open={modalOpen}
handleClose={handleCloseModal} handleClose={handleCloseModal}
editRow={rowData} editRow={rowData}
handleUpdate={handleUpdate}
/> />
<DeleteModal <DeleteModal
open={deleteModal} open={deleteModal}
@ -110,5 +125,5 @@ export default function AdminList() {
handleDelete={handleDelete} handleDelete={handleDelete}
/> />
</> </>
) );
} }

View file

@ -213,7 +213,7 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
<Typography sx={{ textAlign: 'center' }}> <Typography sx={{ textAlign: 'center' }}>
Don&apos;t have an account?{' '} Don&apos;t have an account?{' '}
<Link <Link
href="/signup/" href="/auth/signup"
variant="body2" variant="body2"
sx={{ alignSelf: 'center' }} sx={{ alignSelf: 'center' }}
> >

View file

@ -117,11 +117,11 @@ const dispatch = useDispatch();
return ( return (
<AppTheme {...props}> <AppTheme {...props}>
<CssBaseline enableColorScheme /> {/* <CssBaseline enableColorScheme /> */}
<ColorModeSelect sx={{ position: 'fixed', top: '1rem', right: '1rem' }} /> <ColorModeSelect sx={{ position: 'fixed', top: '1rem', right: '1rem' }} />/
<SignUpContainer direction="column" justifyContent="space-between"> <SignUpContainer direction="column" justifyContent="space-between">
<Card variant="outlined"> <Card variant="outlined">
<SitemarkIcon /> Digi-EV
<Typography <Typography
component="h1" component="h1"
variant="h4" variant="h4"
@ -204,12 +204,12 @@ const dispatch = useDispatch();
/> />
</FormControl> </FormControl>
<MuiPhoneNumber <MuiPhoneNumber
defaultCountry='it' defaultCountry='in'
onChange={handleOnChange} onChange={handleOnChange}
value={phoneNumber} value={phoneNumber}
/> />
<FormControlLabel {/* <FormControlLabel
control={ control={
<Controller <Controller
name="allowExtraEmails" name="allowExtraEmails"
@ -220,16 +220,16 @@ const dispatch = useDispatch();
/> />
} }
label="I want to receive updates via email." label="I want to receive updates via email."
/> /> */}
<Button type="submit" fullWidth variant="contained"> <Button type="submit" fullWidth variant="contained">
Sign up Sign up
</Button> </Button>
</Box> </Box>
<Divider> {/* <Divider>
<Typography sx={{ color: 'text.secondary' }}>or</Typography> <Typography sx={{ color: 'text.secondary' }}>or</Typography>
</Divider> </Divider> */}
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> {/* <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
<Button <Button
fullWidth fullWidth
variant="outlined" variant="outlined"
@ -256,6 +256,19 @@ const dispatch = useDispatch();
Sign in Sign in
</Link> </Link>
</Typography> </Typography>
</Box> */}
<Divider>or</Divider>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
<Typography sx={{ textAlign: 'center' }}>
Already have an account? &nbsp;
<Link
href="/auth/login"
variant="body2"
sx={{ alignSelf: 'center' }}
>
Sign in
</Link>
</Typography>
</Box> </Box>
</Card> </Card>
</SignUpContainer> </SignUpContainer>

View file

@ -0,0 +1,9 @@
import React from 'react'
function NotFoundPage() {
return (
<div>NotFoundPage</div>
)
}
export default NotFoundPage;

View file

@ -19,7 +19,7 @@ const categoryRows = [
]; ];
export default function Vehicles() { export default function Vehicles() {
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState<boolean>(false);
const [editRow, setEditRow] = useState<any>(null); const [editRow, setEditRow] = useState<any>(null);
const { reset } = useForm(); const { reset } = useForm();

11
src/redux/reducers.ts Normal file
View file

@ -0,0 +1,11 @@
import { combineReducers } from "@reduxjs/toolkit";
import authReducer from "./slices/authSlice";
const rootReducer = combineReducers({
auth: authReducer,
});
export type RootState = ReturnType<typeof rootReducer>;
export default rootReducer;

View file

@ -1,32 +0,0 @@
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';
// Example: You might have an API call for deleting admin like this
export const deleteAdmin = createAsyncThunk(
'auth/deleteAdmin',
async (adminId: number, thunkAPI) => {
try {
const response = await axios.delete(`/api/admins/${adminId}`);
return adminId; // Return the ID of the deleted admin
} catch (error) {
return thunkAPI.rejectWithValue(error.response.data);
}
}
);
const authSlice = createSlice({
name: 'auth',
initialState: {
admins: [], // Array of admins
},
reducers: {},
extraReducers: (builder) => {
builder.addCase(deleteAdmin.fulfilled, (state, action) => {
// Remove the deleted admin from the state
state.admins = state.admins.filter((admin) => admin.id !== action.payload);
});
// Handle rejected state if needed
},
});
export default authSlice.reducer;

View file

@ -1,31 +1,26 @@
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit" import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
import axios from "axios" import axios from "axios";
import { backendHttp, apiHttp } from "../../lib/https" import { backendHttp, apiHttp } from "../../lib/https";
import { toast } from "react-toastify" import { toast } from "react-toastify";
// Define types for state // Define types for state
interface User { interface User {
map( id: string;
arg0: ( email: string;
admin: { name: string; role: string },
index: number
) => { srno: number; name: string; role: string }
): unknown
id: string
email: string
} }
interface Admin { interface Admin {
id: string id: string;
name: string name: string;
role: string role: string;
} }
interface AuthState { interface AuthState {
user: User | null user: User | null;
admins: Admin[] admins: Admin[];
isAuthenticated: boolean isAuthenticated: boolean;
isLoading: boolean isLoading: boolean;
error: string | null error: string | null;
} }
// Async thunk for login // Async thunk for login
@ -38,16 +33,16 @@ export const loginUser = createAsyncThunk<
const response = await backendHttp.post("admin/login", { const response = await backendHttp.post("admin/login", {
email, email,
password, password,
}) });
localStorage.setItem("authToken", response.data?.data?.token) // Save token localStorage.setItem("authToken", response.data?.data?.token); // Save token
toast.success(response.data?.message) toast.success(response.data?.message);
return response.data return response.data;
} catch (error: any) { } catch (error: any) {
return rejectWithValue( return rejectWithValue(
error.response?.data?.message || "An error occurred" error.response?.data?.message || "An error occurred"
) );
} }
}) });
// Async thunk for register // Async thunk for register
export const registerUser = createAsyncThunk< export const registerUser = createAsyncThunk<
@ -59,14 +54,14 @@ export const registerUser = createAsyncThunk<
const response = await axios.post( const response = await axios.post(
"https://health-digi.dmlabs.in/auth/register", "https://health-digi.dmlabs.in/auth/register",
data data
) );
return response.data return response.data;
} catch (error: any) { } catch (error: any) {
return rejectWithValue( return rejectWithValue(
error.response?.data?.message || "An error occurred" error.response?.data?.message || "An error occurred"
) );
} }
}) });
export const adminList = createAsyncThunk< export const adminList = createAsyncThunk<
Admin[], Admin[],
@ -74,21 +69,41 @@ export const adminList = createAsyncThunk<
{ rejectValue: string } { rejectValue: string }
>("/auth", async (_, { rejectWithValue }) => { >("/auth", async (_, { rejectWithValue }) => {
try { try {
const response = await apiHttp.get("/auth") const response = await apiHttp.get("/auth");
console.log(response)
return response?.data?.data?.map( return response?.data?.data?.map(
(admin: { name: string; role: string }) => ({ (admin: { id: string; name: string; role: string }) => ({
name: admin?.name, id: admin.id,
role: admin?.role, name: admin.name,
role: admin.role || "N/A",
}) })
) );
// console.log(response.data.data)
} catch (error: any) { } catch (error: any) {
return rejectWithValue( return rejectWithValue(
error.response?.data?.message || "An error occurred" error.response?.data?.message || "An error occurred"
) );
} }
}) });
//By Jaanvi : Edit feature :: 11-feb-25
// updateAdmin Action
export const updateAdmin = createAsyncThunk(
"/auth/id",
async (
{ id, name, role }: { id: any; name: string; role: string },
{ rejectWithValue }
) => {
try {
const response = await apiHttp.put(`/auth/${id}`, { name, role });
toast.success("Admin updated successfully");
console.log(response.data);
return response.data;
} catch (error: any) {
return rejectWithValue(
error.response?.data?.message || "An error occurred"
);
}
}
);
const initialState: AuthState = { const initialState: AuthState = {
user: null, user: null,
@ -96,82 +111,91 @@ const initialState: AuthState = {
isAuthenticated: false, isAuthenticated: false,
isLoading: false, isLoading: false,
error: null, error: null,
} };
const authSlice = createSlice({ const authSlice = createSlice({
name: "auth", name: "auth",
initialState, initialState,
reducers: { reducers: {
logout: (state) => { logout: (state) => {
state.user = null state.user = null;
state.isAuthenticated = false state.isAuthenticated = false;
}, },
}, },
extraReducers: (builder) => { extraReducers: (builder) => {
builder builder
// Login // Login
.addCase(loginUser.pending, (state) => { .addCase(loginUser.pending, (state) => {
state.isLoading = true state.isLoading = true;
state.error = null state.error = null;
}) })
.addCase( .addCase(
loginUser.fulfilled, loginUser.fulfilled,
(state, action: PayloadAction<User>) => { (state, action: PayloadAction<User>) => {
state.isLoading = false state.isLoading = false;
state.isAuthenticated = true state.isAuthenticated = true;
state.user = action.payload state.user = action.payload;
} }
) )
.addCase( .addCase(
loginUser.rejected, loginUser.rejected,
(state, action: PayloadAction<string | undefined>) => { (state, action: PayloadAction<string | undefined>) => {
state.isLoading = false state.isLoading = false;
state.error = action.payload || "An error occurred" state.error = action.payload || "An error occurred";
} }
) )
// Register // Register
.addCase(registerUser.pending, (state) => { .addCase(registerUser.pending, (state) => {
state.isLoading = true state.isLoading = true;
state.error = null state.error = null;
}) })
.addCase( .addCase(
registerUser.fulfilled, registerUser.fulfilled,
(state, action: PayloadAction<User>) => { (state, action: PayloadAction<User>) => {
state.isLoading = false state.isLoading = false;
state.isAuthenticated = true state.isAuthenticated = true;
state.user = action.payload state.user = action.payload;
} }
) )
.addCase( .addCase(
registerUser.rejected, registerUser.rejected,
(state, action: PayloadAction<string | undefined>) => { (state, action: PayloadAction<string | undefined>) => {
state.isLoading = false state.isLoading = false;
state.error = action.payload || "An error occurred" state.error = action.payload || "An error occurred";
} }
) )
// Fetch admin list
// created by Jaanvi and Eknoor
.addCase(adminList.pending, (state) => { .addCase(adminList.pending, (state) => {
state.isLoading = true state.isLoading = true;
state.error = null state.error = null;
}) })
.addCase( .addCase(
adminList.fulfilled, adminList.fulfilled,
(state, action: PayloadAction<Admin[]>) => { (state, action: PayloadAction<Admin[]>) => {
state.isLoading = false state.isLoading = false;
state.admins = action.payload // ✅ Store admins correctly state.admins = action.payload;
} }
) )
.addCase( .addCase(
adminList.rejected, adminList.rejected,
(state, action: PayloadAction<string | undefined>) => { (state, action: PayloadAction<string | undefined>) => {
state.isLoading = false state.isLoading = false;
state.error = action.payload || "An error occurred" state.error = action.payload || "An error occurred";
} }
) )
}, // update admin cases
.addCase(updateAdmin.fulfilled, (state, action) => {
const updatedAdmin = action.payload;
state.admins = state.admins.map((admin) =>
admin.id === updatedAdmin.id ? updatedAdmin : admin
);
}) })
.addCase(updateAdmin.rejected, (state) => {
state.isLoading = false;
state.error = null;
});
},
});
export const { logout } = authSlice.actions export const { logout } = authSlice.actions;
export default authSlice.reducer export default authSlice.reducer;

View file

@ -1,13 +1,10 @@
import { configureStore } from '@reduxjs/toolkit'; import { configureStore } from "@reduxjs/toolkit";
import authReducer from '../slices/authSlice.ts'
const store = configureStore({
reducer: {
auth: authReducer,
},
});
import rootReducer from "../reducers";
export const store = configureStore({
reducer: rootReducer,
});
export type RootState = ReturnType<typeof store.getState>; export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch; export type AppDispatch = typeof store.dispatch;
export default store; export default store;

View file

@ -1,25 +1,25 @@
import { Routes as BaseRoutes, Navigate, Route } from "react-router-dom" import { Routes as BaseRoutes, Navigate, Route } from "react-router-dom";
// import useAuth from "./hooks/useAuth"; // import useAuth from "./hooks/useAuth";
import React, { Suspense } from "react" import React, { Suspense } from "react";
import LoadingComponent from "./components/Loading" import LoadingComponent from "./components/Loading";
import DashboardLayout from "./layouts/DashboardLayout" import DashboardLayout from "./layouts/DashboardLayout";
import Login from "./pages/Auth/Login" import Login from "./pages/Auth/Login";
import SignUp from "./pages/Auth/SignUp" import SignUp from "./pages/Auth/SignUp";
import Dashboard from "./pages/Dashboard" import Dashboard from "./pages/Dashboard";
import Vehicles from "./pages/Vechiles" import Vehicles from "./pages/Vehicles";
import AdminList from "./pages/AdminList" import AdminList from "./pages/AdminList";
function ProtectedRoute({ function ProtectedRoute({
caps, caps,
component, component,
}: { }: {
caps: string[] caps: string[];
component: React.ReactNode component: React.ReactNode;
}) { }) {
if (!localStorage.getItem("authToken")) if (!localStorage.getItem("authToken"))
return <Navigate to={`/auth/login`} replace /> return <Navigate to={`/auth/login`} replace />;
return component return component;
} }
export default function AppRouter() { export default function AppRouter() {
@ -70,5 +70,5 @@ export default function AppRouter() {
<Route path="*" element={<>404</>} /> <Route path="*" element={<>404</>} />
</BaseRoutes> </BaseRoutes>
</Suspense> </Suspense>
) );
} }

View file

@ -10,10 +10,11 @@
], ],
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"jsx": "react-jsx",
"types": ["react", "react-dom"],
"lib": ["dom", "dom.iterable", "esnext"],
"paths": { "paths": {
"@/*": [ "@/*": ["./src/*"]
"./src/*"
]
} }
} }
} }