dev-jaanvi #1
BIN
public/model-s-exterior-front-view.webp
Normal file
BIN
public/model-s-exterior-front-view.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.4 KiB |
|
@ -8,15 +8,28 @@ import SearchIcon from "@mui/icons-material/Search";
|
||||||
import Divider from "@mui/material/Divider";
|
import Divider from "@mui/material/Divider";
|
||||||
import MenuButton from "../MenuButton";
|
import MenuButton from "../MenuButton";
|
||||||
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
|
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
|
||||||
import ColorModeIconDropdown from "../../shared-theme/ColorModeIconDropdown";
|
|
||||||
import NotificationsRoundedIcon from "@mui/icons-material/NotificationsRounded";
|
import NotificationsRoundedIcon from "@mui/icons-material/NotificationsRounded";
|
||||||
|
import SideMenu from "../SideMenu";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { AppDispatch, RootState } from "../../redux/store/store";
|
||||||
|
import { fetchAdminProfile } from "../../redux/slices/profileSlice";
|
||||||
|
import OptionsMenu from "../OptionsMenu";
|
||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
const [showNotifications, setShowNotifications] = React.useState(false);
|
const [showNotifications, setShowNotifications] = React.useState(false);
|
||||||
const toggleNotifications = () => {
|
const toggleNotifications = () => {
|
||||||
setShowNotifications((prev) => !prev);
|
setShowNotifications((prev) => !prev);
|
||||||
};
|
};
|
||||||
|
const [open, setOpen] = React.useState(true);
|
||||||
|
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
const { user } = useSelector(
|
||||||
|
(state: RootState) => state?.profileReducer
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
dispatch(fetchAdminProfile());
|
||||||
|
}, [dispatch]);
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -92,11 +105,11 @@ export default function Header() {
|
||||||
sx={{ width: 36, height: 36 }}
|
sx={{ width: 36, height: 36 }}
|
||||||
/>
|
/>
|
||||||
<Typography variant="body1" sx={{ color: "#202020" }}>
|
<Typography variant="body1" sx={{ color: "#202020" }}>
|
||||||
Momah
|
{user?.name || "No Admin"}
|
||||||
</Typography>
|
</Typography>
|
||||||
{/* Dropdown Icon */}
|
|
||||||
<ArrowDropDownIcon
|
<OptionsMenu
|
||||||
sx={{ color: "#202020", width: 16, height: 16 }}
|
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
{/* <ColorModeIconDropdown /> */}
|
{/* <ColorModeIconDropdown /> */}
|
||||||
|
|
|
@ -47,6 +47,11 @@ export default function MenuContent({ hidden }: PropType) {
|
||||||
icon: <AnalyticsRoundedIcon />,
|
icon: <AnalyticsRoundedIcon />,
|
||||||
url: "/panel/role-list",
|
url: "/panel/role-list",
|
||||||
},
|
},
|
||||||
|
userRole === "admin" && {
|
||||||
|
text: "Vehicles",
|
||||||
|
icon: <AnalyticsRoundedIcon />,
|
||||||
|
url: "/panel/vehicle-list",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const filteredMenuItems = baseMenuItems.filter(Boolean);
|
const filteredMenuItems = baseMenuItems.filter(Boolean);
|
||||||
|
|
|
@ -13,6 +13,7 @@ import MenuButton from "../MenuButton";
|
||||||
import { Avatar } from "@mui/material";
|
import { Avatar } from "@mui/material";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import Logout from "../LogOutFunction/LogOutFunction";
|
import Logout from "../LogOutFunction/LogOutFunction";
|
||||||
|
import { ArrowDropDownIcon } from "@mui/x-date-pickers";
|
||||||
|
|
||||||
const MenuItem = styled(MuiMenuItem)({
|
const MenuItem = styled(MuiMenuItem)({
|
||||||
margin: "2px 0",
|
margin: "2px 0",
|
||||||
|
@ -46,7 +47,7 @@ export default function OptionsMenu({ avatar }: { avatar?: boolean }) {
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
sx={{ borderColor: "transparent" }}
|
sx={{ borderColor: "transparent" }}
|
||||||
>
|
>
|
||||||
{avatar ? (
|
{/* {avatar ? (
|
||||||
<MoreVertRoundedIcon />
|
<MoreVertRoundedIcon />
|
||||||
) : (
|
) : (
|
||||||
<Avatar
|
<Avatar
|
||||||
|
@ -55,7 +56,8 @@ export default function OptionsMenu({ avatar }: { avatar?: boolean }) {
|
||||||
src="/static/images/avatar/7.jpg"
|
src="/static/images/avatar/7.jpg"
|
||||||
sx={{ width: 36, height: 36 }}
|
sx={{ width: 36, height: 36 }}
|
||||||
/>
|
/>
|
||||||
)}
|
)} */}
|
||||||
|
<ArrowDropDownIcon />
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<Menu
|
<Menu
|
||||||
anchorEl={anchorEl}
|
anchorEl={anchorEl}
|
||||||
|
@ -78,11 +80,11 @@ export default function OptionsMenu({ avatar }: { avatar?: boolean }) {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MenuItem onClick={handleProfile}>Profile</MenuItem>
|
<MenuItem onClick={handleProfile}>Profile</MenuItem>
|
||||||
<MenuItem onClick={handleClose}>My account</MenuItem>
|
{/* <MenuItem onClick={handleClose}>My account</MenuItem>
|
||||||
<Divider />
|
<Divider />
|
||||||
<MenuItem onClick={handleClose}>Add another account</MenuItem>
|
<MenuItem onClick={handleClose}>Add another account</MenuItem>
|
||||||
<MenuItem onClick={handleClose}>Settings</MenuItem>
|
<MenuItem onClick={handleClose}>Settings</MenuItem> */}
|
||||||
<Divider />
|
{/* <Divider /> */}
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -92,15 +94,12 @@ export default function OptionsMenu({ avatar }: { avatar?: boolean }) {
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* //Eknoor singh and jaanvi
|
|
||||||
//date:- 13-Feb-2025
|
|
||||||
//Implemented logout functionality which was static previously */}
|
|
||||||
|
|
||||||
<ListItemText
|
<ListItemText
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setLogoutModal(true);
|
setLogoutModal(true);
|
||||||
}}
|
}}
|
||||||
|
sx={{color:"red"}}
|
||||||
>
|
>
|
||||||
Logout
|
Logout
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
|
@ -109,9 +108,9 @@ export default function OptionsMenu({ avatar }: { avatar?: boolean }) {
|
||||||
logoutModal={logoutModal}
|
logoutModal={logoutModal}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ListItemIcon>
|
{/* <ListItemIcon>
|
||||||
<LogoutRoundedIcon fontSize="small" />
|
<LogoutRoundedIcon fontSize="small" />
|
||||||
</ListItemIcon>
|
</ListItemIcon> */}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|
|
@ -29,10 +29,6 @@ const Drawer = styled(MuiDrawer)({
|
||||||
export default function SideMenu() {
|
export default function SideMenu() {
|
||||||
const [open, setOpen] = React.useState(true);
|
const [open, setOpen] = React.useState(true);
|
||||||
|
|
||||||
//Eknoor singh
|
|
||||||
//date:- 12-Feb-2025
|
|
||||||
//Dispatch is called with user from Authstate Interface
|
|
||||||
|
|
||||||
const dispatch = useDispatch<AppDispatch>();
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
const { user } = useSelector((state: RootState) => state?.profileReducer);
|
const { user } = useSelector((state: RootState) => state?.profileReducer);
|
||||||
|
|
||||||
|
@ -74,7 +70,7 @@ export default function SideMenu() {
|
||||||
</Box>
|
</Box>
|
||||||
<MenuContent hidden={open} />
|
<MenuContent hidden={open} />
|
||||||
{/* <CardAlert /> */}
|
{/* <CardAlert /> */}
|
||||||
<Stack
|
{/* <Stack
|
||||||
direction="row"
|
direction="row"
|
||||||
sx={{
|
sx={{
|
||||||
p: 2,
|
p: 2,
|
||||||
|
@ -104,8 +100,8 @@ export default function SideMenu() {
|
||||||
{user?.email || "No Email"}
|
{user?.email || "No Email"}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<OptionsMenu avatar={open} />
|
<OptionsMenu avatar={open} />
|
||||||
</Stack>
|
</Stack> */}
|
||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,8 +33,8 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { errors },
|
formState: { errors, isValid },
|
||||||
} = useForm<ILoginForm>();
|
} = useForm<ILoginForm>({ mode: "onChange" });
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const router = useNavigate();
|
const router = useNavigate();
|
||||||
|
|
||||||
|
@ -228,6 +228,11 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
|
||||||
message:
|
message:
|
||||||
"Password must be at least 6 characters long.",
|
"Password must be at least 6 characters long.",
|
||||||
},
|
},
|
||||||
|
pattern: {
|
||||||
|
value: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{6,}$/,
|
||||||
|
message:
|
||||||
|
"Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character.",
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Box sx={{ position: "relative" }}>
|
<Box sx={{ position: "relative" }}>
|
||||||
|
@ -255,17 +260,22 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
|
||||||
? "error"
|
? "error"
|
||||||
: "primary"
|
: "primary"
|
||||||
}
|
}
|
||||||
|
sx={{
|
||||||
|
paddingRight: "40px",
|
||||||
|
height: "40px",
|
||||||
|
marginBottom: "8px",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
sx={{
|
sx={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: "50%",
|
top: "50%",
|
||||||
right: "10px",
|
right: "10px",
|
||||||
|
transform:
|
||||||
|
"translateY(-50%)",
|
||||||
background: "none",
|
background: "none",
|
||||||
borderColor:
|
borderColor:
|
||||||
"transparent",
|
"transparent",
|
||||||
transform:
|
|
||||||
"translateY(-50%)",
|
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
"transparent",
|
"transparent",
|
||||||
|
@ -295,6 +305,8 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
color: "white",
|
color: "white",
|
||||||
|
alignItems: "center",
|
||||||
|
flexWrap: "wrap",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
|
@ -326,6 +338,7 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
disabled={!isValid}
|
||||||
sx={{
|
sx={{
|
||||||
color: "white",
|
color: "white",
|
||||||
backgroundColor: "#52ACDF",
|
backgroundColor: "#52ACDF",
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
//Eknoor singh
|
|
||||||
//date:- 12-Feb-2025
|
|
||||||
//Made a special page for showing the profile details
|
|
||||||
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
Container,
|
Container,
|
||||||
|
@ -12,16 +8,15 @@ import {
|
||||||
Grid,
|
Grid,
|
||||||
Avatar,
|
Avatar,
|
||||||
Box,
|
Box,
|
||||||
|
Stack,
|
||||||
|
Divider,
|
||||||
|
Link,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { AppDispatch, RootState } from "../../redux/store/store";
|
import { AppDispatch, RootState } from "../../redux/store/store";
|
||||||
import { fetchAdminProfile } from "../../redux/slices/profileSlice";
|
import { fetchAdminProfile } from "../../redux/slices/profileSlice";
|
||||||
|
|
||||||
const ProfilePage = () => {
|
const ProfilePage = () => {
|
||||||
//Eknoor singh
|
|
||||||
//date:- 12-Feb-2025
|
|
||||||
//Dispatch is called and user, isLoading, and error from Authstate Interface
|
|
||||||
const dispatch = useDispatch<AppDispatch>();
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
const { user, isLoading } = useSelector(
|
const { user, isLoading } = useSelector(
|
||||||
(state: RootState) => state?.profileReducer
|
(state: RootState) => state?.profileReducer
|
||||||
|
@ -49,34 +44,110 @@ const ProfilePage = () => {
|
||||||
return (
|
return (
|
||||||
<Container sx={{ py: 4 }}>
|
<Container sx={{ py: 4 }}>
|
||||||
<Typography variant="h4" gutterBottom>
|
<Typography variant="h4" gutterBottom>
|
||||||
Profile
|
Account Info
|
||||||
</Typography>
|
</Typography>
|
||||||
<Card sx={{ maxWidth: 600, margin: "0 auto" }}>
|
<Card sx={{ maxWidth: "100%", margin: "0 auto" }}>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Grid container spacing={2} alignItems="center">
|
<Stack direction="column" spacing={2}>
|
||||||
<Grid item>
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
spacing={1.5}
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
|
alt="User Avatar"
|
||||||
alt={user?.name || "User Avatar"}
|
src="/avatar.png"
|
||||||
src={"/static/images/avatar/7.jpg"}
|
sx={{ width: 36, height: 36 }}
|
||||||
sx={{ width: 80, height: 80 }}
|
|
||||||
/>
|
/>
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
sx={{ color: "#202020" }}
|
||||||
|
>
|
||||||
|
{user?.name || "No Admin"}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="text.secondary"
|
||||||
|
>
|
||||||
|
{user?.userType || "N/A"}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
<Divider
|
||||||
|
flexItem
|
||||||
|
sx={{ backgroundColor: "rgba(32, 32, 32, 0.5)" }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
sx={{ color: "#202020" }}
|
||||||
|
>
|
||||||
|
Personal Information
|
||||||
|
</Typography>
|
||||||
|
<Link href="/edit-profile" underline="hover">
|
||||||
|
Edit
|
||||||
|
</Link>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Grid container >
|
||||||
|
<Grid item xs={12} sm={4}>
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
sx={{ color: "#202020" }}
|
||||||
|
>
|
||||||
|
Name:
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="text.secondary"
|
||||||
|
>
|
||||||
|
{user?.name || "N/A"}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={4}>
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
sx={{ color: "#202020" }}
|
||||||
|
>
|
||||||
|
Phone:
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="text.secondary"
|
||||||
|
>
|
||||||
|
{user?.phone || "N/A"}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={4}>
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
sx={{ color: "#202020" }}
|
||||||
|
>
|
||||||
|
Email:
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="text.secondary"
|
||||||
|
>
|
||||||
|
{user?.email || "N/A"}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs>
|
|
||||||
<Typography variant="h6">
|
|
||||||
{user?.name || "N/A"}
|
<Typography variant="body1" sx={{ color: "#202020" }}>
|
||||||
</Typography>
|
Bio:
|
||||||
<Typography variant="body2" color="text.secondary">
|
</Typography>
|
||||||
Email: {user?.email || "N/A"}
|
<Typography variant="body2" color="text.secondary">
|
||||||
</Typography>
|
{user?.bio || "No bio available."}
|
||||||
<Typography variant="body2" color="text.secondary">
|
</Typography>
|
||||||
Phone: {user?.phone || "N/A"}
|
</Stack>
|
||||||
</Typography>
|
|
||||||
<Typography variant="body2" color="text.secondary">
|
|
||||||
Role: <b>{user?.userType || "N/A"}</b>
|
|
||||||
</Typography>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
227
src/pages/VehicleList/index.tsx
Normal file
227
src/pages/VehicleList/index.tsx
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { Box, Button, TextField, Typography } from "@mui/material";
|
||||||
|
import AddEditCategoryModal from "../../components/AddEditCategoryModal";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import CustomTable, { Column } from "../../components/CustomTable";
|
||||||
|
import DeleteModal from "../../components/Modals/DeleteModal";
|
||||||
|
import { RootState } from "../../redux/reducers";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { AppDispatch } from "../../redux/store/store";
|
||||||
|
import {
|
||||||
|
addVehicle,
|
||||||
|
updateVehicle,
|
||||||
|
vehicleList,
|
||||||
|
} from "../../redux/slices/VehicleSlice";
|
||||||
|
import SearchIcon from "@mui/icons-material/Search";
|
||||||
|
const categoryRows = [
|
||||||
|
{
|
||||||
|
srno: 1,
|
||||||
|
id: 1, // Using a number for 'id'
|
||||||
|
name: "Tesla Model S",
|
||||||
|
brand: "Tesla",
|
||||||
|
imageUrl:
|
||||||
|
"https://example.com/https://imgd-ct.aeplcdn.com/1056x660/n/cw/ec/93821/model-s-exterior-front-view.jpeg?q=80.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
srno: 2,
|
||||||
|
id: 2,
|
||||||
|
name: "BMW X5",
|
||||||
|
brand: "BMW",
|
||||||
|
imageUrl: "https://example.com/bmw_x5.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
srno: 3,
|
||||||
|
id: 3,
|
||||||
|
name: "Audi A6",
|
||||||
|
brand: "Audi",
|
||||||
|
imageUrl: "https://example.com/audi_a6.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
srno: 4,
|
||||||
|
id: 4,
|
||||||
|
name: "Mercedes-Benz S-Class",
|
||||||
|
brand: "Mercedes-Benz",
|
||||||
|
imageUrl: "https://example.com/mercedes_s_class.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
srno: 5,
|
||||||
|
id: 5,
|
||||||
|
name: "Ford Mustang",
|
||||||
|
brand: "Ford",
|
||||||
|
imageUrl: "https://example.com/ford_mustang.jpg",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
export default function VehicleList() {
|
||||||
|
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
||||||
|
const [editRow, setEditRow] = useState<any>(null);
|
||||||
|
const { reset } = useForm();
|
||||||
|
|
||||||
|
const [deleteModal, setDeleteModal] = React.useState<boolean>(false);
|
||||||
|
const [viewModal, setViewModal] = React.useState<boolean>(false);
|
||||||
|
const [rowData, setRowData] = React.useState<any | null>(null);
|
||||||
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
const vehicles = useSelector(
|
||||||
|
(state: RootState) => state.vehicleReducer.vehicles
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(vehicleList());
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const handleClickOpen = () => {
|
||||||
|
setRowData(null); // Reset row data when opening for new admin
|
||||||
|
setModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseModal = () => {
|
||||||
|
setModalOpen(false);
|
||||||
|
setRowData(null);
|
||||||
|
reset();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreate = async (data: {
|
||||||
|
name: string;
|
||||||
|
brand: string;
|
||||||
|
imageUrl: string;
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
await dispatch(addVehicle(data));
|
||||||
|
await dispatch(vehicleList());
|
||||||
|
handleCloseModal();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Creation failed", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleUpdate = async (
|
||||||
|
id: number,
|
||||||
|
name: string,
|
||||||
|
brand: string,
|
||||||
|
imageUrl: string
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
await dispatch(
|
||||||
|
updateVehicle({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
brand,
|
||||||
|
imageUrl,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
await dispatch(vehicleList());
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Update failed", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const categoryColumns: Column[] = [
|
||||||
|
{ id: "srno", label: "Sr No" },
|
||||||
|
{ id: "name", label: "Vehicle Name" },
|
||||||
|
{ id: "brand", label: "Brand" },
|
||||||
|
{ id: "imageUrl", label: "Image" },
|
||||||
|
{ id: "action", label: "Action", align: "center" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const filteredVehicles = vehicles?.filter(
|
||||||
|
(vehicle) =>
|
||||||
|
vehicle.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
|
vehicle.brand.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
// const categoryRows = filteredVehicles?.length
|
||||||
|
// ? filteredVehicles?.map(
|
||||||
|
// (
|
||||||
|
// vehicle: {
|
||||||
|
// id: number;
|
||||||
|
// name: string;
|
||||||
|
// brand: string;
|
||||||
|
// imageUrl: string;
|
||||||
|
// },
|
||||||
|
// index: number
|
||||||
|
// ) => ({
|
||||||
|
// id: vehicle?.id,
|
||||||
|
// srno: index + 1,
|
||||||
|
// name: vehicle?.name,
|
||||||
|
// brand: vehicle?.brand,
|
||||||
|
// imageUrl: vehicle?.imageUrl,
|
||||||
|
// })
|
||||||
|
// )
|
||||||
|
// : [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
|
||||||
|
{/* Title and Add Category button */}
|
||||||
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h6"
|
||||||
|
sx={{ mt: 2, fontWeight: 600 }}
|
||||||
|
>
|
||||||
|
Vehicles
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: { xs: "column", sm: "row" },
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
mb: 2, // Add margin bottom for spacing
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
placeholder="Search..."
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
sx={{
|
||||||
|
width: { xs: "100%", sm: "30%" },
|
||||||
|
marginBottom: { xs: 2, sm: 0 },
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: (
|
||||||
|
<SearchIcon
|
||||||
|
sx={{ color: "#202020", marginRight: 1 }}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
size="medium"
|
||||||
|
sx={{
|
||||||
|
textAlign: "center",
|
||||||
|
width: { xs: "100%", sm: "auto" },
|
||||||
|
}}
|
||||||
|
onClick={handleClickOpen}
|
||||||
|
>
|
||||||
|
Add Vehicle
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
<CustomTable
|
||||||
|
columns={categoryColumns}
|
||||||
|
rows={categoryRows}
|
||||||
|
setDeleteModal={setDeleteModal}
|
||||||
|
deleteModal={deleteModal}
|
||||||
|
setViewModal={setViewModal}
|
||||||
|
viewModal={viewModal}
|
||||||
|
setRowData={setRowData}
|
||||||
|
setModalOpen={setModalOpen}
|
||||||
|
/>
|
||||||
|
{/* <AddEditCategoryModal
|
||||||
|
open={modalOpen}
|
||||||
|
handleClose={handleCloseModal}
|
||||||
|
editRow={rowData}
|
||||||
|
/>
|
||||||
|
<DeleteModal
|
||||||
|
open={deleteModal}
|
||||||
|
setDeleteModal={setDeleteModal}
|
||||||
|
handleDelete={handleDelete}
|
||||||
|
/> */}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,102 +0,0 @@
|
||||||
import React, { useState } from 'react';
|
|
||||||
import { Box, Button, Typography } from '@mui/material';
|
|
||||||
import AddEditCategoryModal from '../../components/AddEditCategoryModal';
|
|
||||||
import { useForm } from 'react-hook-form';
|
|
||||||
import CustomTable from '../../components/CustomTable';
|
|
||||||
import DeleteModal from '../../components/Modals/DeleteModal';
|
|
||||||
|
|
||||||
// Sample data for categories
|
|
||||||
// const categoryRows = [
|
|
||||||
// { srno: 1, name: 'Strength', date: '01/03/2025' },
|
|
||||||
// {
|
|
||||||
// srno: 2,
|
|
||||||
// name: 'HIIT (High-Intensity Interval Training)',
|
|
||||||
// date: '01/03/2025',
|
|
||||||
// },
|
|
||||||
// { srno: 3, name: 'Cardio', date: '01/03/2025' },
|
|
||||||
// { srno: 4, name: 'Combat', date: '01/03/2025' },
|
|
||||||
// { srno: 5, name: 'Yoga', date: '01/03/2025' },
|
|
||||||
// ];
|
|
||||||
|
|
||||||
export default function Vehicles() {
|
|
||||||
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
|
||||||
const [editRow, setEditRow] = useState<any>(null);
|
|
||||||
const { reset } = useForm();
|
|
||||||
|
|
||||||
const [deleteModal, setDeleteModal] = React.useState<boolean>(false);
|
|
||||||
const [rowData, setRowData] = React.useState<any | null>(null);
|
|
||||||
|
|
||||||
const handleClickOpen = () => {
|
|
||||||
setModalOpen(true);
|
|
||||||
setEditRow(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCloseModal = () => {
|
|
||||||
setModalOpen(false);
|
|
||||||
reset();
|
|
||||||
};
|
|
||||||
|
|
||||||
// const handleEdit = () => {
|
|
||||||
// setEditRow(rowData);
|
|
||||||
// };
|
|
||||||
|
|
||||||
const handleDelete = () => {
|
|
||||||
console.log('Deleted row:', rowData);
|
|
||||||
setDeleteModal(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const categoryColumns = [
|
|
||||||
{ id: 'srno', label: 'Sr No' },
|
|
||||||
{ id: 'name', label: 'Category Name' },
|
|
||||||
{ id: 'date', label: 'Date' },
|
|
||||||
{ id: 'action', label: 'Action', align: 'center' },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
width: '100%',
|
|
||||||
maxWidth: {
|
|
||||||
sm: '100%',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Title and Add Category button */}
|
|
||||||
{/* <Typography component="h2" variant="h6" sx={{ mt: 2, fontWeight: 600 }}>
|
|
||||||
Vehicles
|
|
||||||
</Typography> */}
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
size="medium"
|
|
||||||
sx={{ textAlign: 'right' }}
|
|
||||||
onClick={handleClickOpen}
|
|
||||||
>
|
|
||||||
Add Categorywewfw
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<CustomTable
|
|
||||||
columns={categoryColumns}
|
|
||||||
rows={categoryRows}
|
|
||||||
editRow={editRow}
|
|
||||||
setDeleteModal={setDeleteModal}
|
|
||||||
setRowData={setRowData}
|
|
||||||
setModalOpen={setModalOpen}
|
|
||||||
/>
|
|
||||||
<AddEditCategoryModal
|
|
||||||
open={modalOpen}
|
|
||||||
handleClose={handleCloseModal}
|
|
||||||
editRow={rowData}
|
|
||||||
/>
|
|
||||||
<DeleteModal
|
|
||||||
open={deleteModal}
|
|
||||||
setDeleteModal={setDeleteModal}
|
|
||||||
handleDelete={handleDelete}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -5,6 +5,8 @@ import adminReducer from "./slices/adminSlice";
|
||||||
import profileReducer from "./slices/profileSlice";
|
import profileReducer from "./slices/profileSlice";
|
||||||
import userReducer from "./slices/userSlice.ts";
|
import userReducer from "./slices/userSlice.ts";
|
||||||
import roleReducer from "./slices/roleSlice.ts";
|
import roleReducer from "./slices/roleSlice.ts";
|
||||||
|
import vehicleReducer from "./slices/VehicleSlice.ts";
|
||||||
|
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
authReducer,
|
authReducer,
|
||||||
|
@ -12,6 +14,7 @@ const rootReducer = combineReducers({
|
||||||
profileReducer,
|
profileReducer,
|
||||||
userReducer,
|
userReducer,
|
||||||
roleReducer,
|
roleReducer,
|
||||||
|
vehicleReducer
|
||||||
});
|
});
|
||||||
|
|
||||||
export type RootState = ReturnType<typeof rootReducer>;
|
export type RootState = ReturnType<typeof rootReducer>;
|
||||||
|
|
135
src/redux/slices/VehicleSlice.ts
Normal file
135
src/redux/slices/VehicleSlice.ts
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
|
||||||
|
import http from "../../lib/https";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
interface Vehicle {
|
||||||
|
id:number;
|
||||||
|
name:string;
|
||||||
|
brand:string;
|
||||||
|
imageUrl:string;
|
||||||
|
|
||||||
|
}
|
||||||
|
interface VehicleState {
|
||||||
|
vehicles:Vehicle[];
|
||||||
|
loading: boolean;
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
const initialState: VehicleState = {
|
||||||
|
vehicles: [],
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const vehicleList = createAsyncThunk<Vehicle, void, { rejectValue: string }>(
|
||||||
|
"fetchVehicles",
|
||||||
|
async (_, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
const token = localStorage?.getItem("authToken");
|
||||||
|
if (!token) throw new Error("No token found");
|
||||||
|
|
||||||
|
const response = await http.get("/");
|
||||||
|
|
||||||
|
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"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
//Add Vehicle
|
||||||
|
export const addVehicle = createAsyncThunk<
|
||||||
|
Vehicle,
|
||||||
|
{
|
||||||
|
name: string;
|
||||||
|
brand: string;
|
||||||
|
imageUrl: string;
|
||||||
|
},
|
||||||
|
{ rejectValue: string }
|
||||||
|
>("/AddVehicle", async (data, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
const response = await http.post("/", data);
|
||||||
|
return response.data;
|
||||||
|
} catch (error: any) {
|
||||||
|
return rejectWithValue(
|
||||||
|
error.response?.data?.message || "An error occurred"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Update Vehicle details
|
||||||
|
export const updateVehicle = createAsyncThunk(
|
||||||
|
"updateVehicle",
|
||||||
|
async ({ id, ...vehicleData }: Vehicle, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
const response = await http.put(`/${id}`, vehicleData);
|
||||||
|
toast.success("Vehicle Deatils updated successfully");
|
||||||
|
return response?.data;
|
||||||
|
} catch (error: any) {
|
||||||
|
toast.error("Error updating the user: " + error);
|
||||||
|
return rejectWithValue(
|
||||||
|
error.response?.data?.message || "An error occurred"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const vehicleSlice = createSlice({
|
||||||
|
name: "vehicle",
|
||||||
|
initialState,
|
||||||
|
reducers: {},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder
|
||||||
|
.addCase(vehicleList.pending, (state) => {
|
||||||
|
state.loading = true;
|
||||||
|
state.error = null;
|
||||||
|
})
|
||||||
|
.addCase(
|
||||||
|
vehicleList.fulfilled,
|
||||||
|
(state, action: PayloadAction<Vehicle[]>) => {
|
||||||
|
state.loading = false;
|
||||||
|
state.vehicles = action.payload;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.addCase(vehicleList.rejected, (state, action) => {
|
||||||
|
state.loading = false;
|
||||||
|
state.error = action.error.message || "Failed to fetch users";
|
||||||
|
})
|
||||||
|
.addCase(addVehicle.pending, (state) => {
|
||||||
|
state.loading = true;
|
||||||
|
// state.error = null;
|
||||||
|
})
|
||||||
|
.addCase(
|
||||||
|
addVehicle.fulfilled,
|
||||||
|
(state, action: PayloadAction<Vehicle>) => {
|
||||||
|
state.loading = false;
|
||||||
|
state.vehicles.push(action.payload);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.addCase(
|
||||||
|
addVehicle.rejected,
|
||||||
|
(state, action: PayloadAction<string | undefined>) => {
|
||||||
|
state.loading = false;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.addCase(updateVehicle.pending, (state) => {
|
||||||
|
state.loading = true;
|
||||||
|
})
|
||||||
|
.addCase(updateVehicle.fulfilled, (state, action) => {
|
||||||
|
const updateVehicle = action.payload;
|
||||||
|
state.vehicles = state?.vehicles?.map((vehicle) =>
|
||||||
|
vehicle?.id === updateVehicle?.id ? updateVehicle : vehicle
|
||||||
|
);
|
||||||
|
state.loading = false;
|
||||||
|
})
|
||||||
|
.addCase(updateVehicle.rejected, (state) => {
|
||||||
|
state.loading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default vehicleSlice.reducer;
|
|
@ -28,15 +28,6 @@ const initialState: UserState = {
|
||||||
error: null,
|
error: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Async thunk to fetch user list
|
|
||||||
// export const userList = createAsyncThunk<User[]>("users/fetchUsers", async () => {
|
|
||||||
// try {
|
|
||||||
// const response = await axios.get<User[]>("/api/users"); // Adjust the API endpoint as needed
|
|
||||||
// return response.data;
|
|
||||||
// } catch (error: any) {
|
|
||||||
// throw new Error(error.response?.data?.message || "Failed to fetch users");
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
export const userList = createAsyncThunk<User, void, { rejectValue: string }>(
|
export const userList = createAsyncThunk<User, void, { rejectValue: string }>(
|
||||||
"fetchUsers",
|
"fetchUsers",
|
||||||
async (_, { rejectWithValue }) => {
|
async (_, { rejectWithValue }) => {
|
||||||
|
|
|
@ -4,12 +4,13 @@ import LoadingComponent from "./components/Loading";
|
||||||
import DashboardLayout from "./layouts/DashboardLayout";
|
import DashboardLayout from "./layouts/DashboardLayout";
|
||||||
import RoleList from "./pages/RoleList";
|
import RoleList from "./pages/RoleList";
|
||||||
import AddEditRolePage from "./pages/AddEditRolePage";
|
import AddEditRolePage from "./pages/AddEditRolePage";
|
||||||
|
import VehicleList from "./pages/VehicleList";
|
||||||
|
|
||||||
// Page imports
|
// Page imports
|
||||||
const Login = lazy(() => import("./pages/Auth/Login"));
|
const Login = lazy(() => import("./pages/Auth/Login"));
|
||||||
const SignUp = lazy(() => import("./pages/Auth/SignUp"));
|
const SignUp = lazy(() => import("./pages/Auth/SignUp"));
|
||||||
const Dashboard = lazy(() => import("./pages/Dashboard"));
|
const Dashboard = lazy(() => import("./pages/Dashboard"));
|
||||||
const Vehicles = lazy(() => import("./pages/Vehicles"));
|
const Vehicles = lazy(() => import("./pages/VehicleList"));
|
||||||
const AdminList = lazy(() => import("./pages/AdminList"));
|
const AdminList = lazy(() => import("./pages/AdminList"));
|
||||||
const ProfilePage = lazy(() => import("./pages/ProfilePage"));
|
const ProfilePage = lazy(() => import("./pages/ProfilePage"));
|
||||||
const NotFoundPage = lazy(() => import("./pages/NotFound"));
|
const NotFoundPage = lazy(() => import("./pages/NotFound"));
|
||||||
|
@ -95,6 +96,15 @@ export default function AppRouter() {
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="vehicle-list"
|
||||||
|
element={
|
||||||
|
<ProtectedRoute
|
||||||
|
caps={[]}
|
||||||
|
component={<VehicleList />}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="permissions"
|
path="permissions"
|
||||||
element={
|
element={
|
||||||
|
|
Loading…
Reference in a new issue