Compare commits

...

10 Commits

Author SHA1 Message Date
ROSHAN GARG
f40d90e8f9 updated dashboard and the changes are done in dates formate
Some checks failed
NPM Installation / build (16.x, ubuntu-latest) (push) Has been cancelled
NPM Installation / build (16.x, windows-latest) (push) Has been cancelled
NPM Installation / build (17.x, ubuntu-latest) (push) Has been cancelled
NPM Installation / build (17.x, windows-latest) (push) Has been cancelled
NPM Installation / build (18.x, ubuntu-latest) (push) Has been cancelled
NPM Installation / build (18.x, windows-latest) (push) Has been cancelled
2024-09-06 11:06:10 +05:30
ROSHAN GARG
3bb5473ce4 order processing done 2024-08-30 16:34:24 +05:30
ROSHAN GARG
6580f5d854 order process completed 2024-08-23 10:49:28 +05:30
ROSHAN GARG
b1e6917e2a new 2024-08-16 14:12:07 +05:30
ROSHAN GARG
e2663b9e80 new 2024-08-16 13:57:41 +05:30
ROSHAN GARG
7039c86365 redux implemented 2024-08-16 13:42:45 +05:30
ROSHAN GARG
3b173b4987 kyc updated 2024-08-09 16:21:01 +05:30
ROSHAN GARG
61da636a2d update api done 2024-08-02 18:02:17 +05:30
ROSHAN GARG
490314ef24 auth done 2024-07-26 19:14:25 +05:30
ROSHAN GARG
27eb6f4c7c first commit 2024-07-26 18:56:18 +05:30
29 changed files with 2755 additions and 196 deletions

View File

@ -18,6 +18,9 @@ module.exports = {
],
plugins: ['react', 'react-hooks'],
rules: {
rules: {
'react/react-in-jsx-scope': 'off',
},
// Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
// e.g. "@typescript-eslint/explicit-function-return-type": "off",
},

View File

@ -182,4 +182,5 @@ CoreUI is an MIT-licensed open source project and is completely free to use. How
copyright 2024 creativeLabs Łukasz Holeczek.
Code released under [the MIT license](https://github.com/coreui/coreui-free-react-admin-template/blob/main/LICENSE).
Code released under [the MIT license](https://github.com/coreui/coreui-free-react-admin-template/blob/main/LICENSE).# cheminova-principal-distributer

View File

@ -40,12 +40,14 @@
"classnames": "^2.5.1",
"core-js": "^3.37.1",
"date-fns": "^3.6.0",
"jwt-decode": "^4.0.0",
"prop-types": "^15.8.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-redux": "^9.1.2",
"react-router-dom": "^6.23.1",
"redux": "5.0.1",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"simplebar-react": "^3.2.5",
"sweetalert2": "^11.12.3"
},

View File

@ -8,7 +8,9 @@ import {
cilCursor,
cilDescription,
cilDrop,
cilFile,
cilNotes,
cilPaperPlane,
cilPencil,
cilPuzzle,
cilShare,
@ -30,6 +32,25 @@ const _nav = [
to: '/shop',
icon: <CIcon icon={cilShare} customClassName="nav-icon" />,
},
{
component: CNavItem,
name: 'Cart',
to: '/cart',
icon: <CIcon icon={cilShare} customClassName="nav-icon" />,
},
{
component: CNavItem,
name: 'Orders Placed',
to: '/orders-placed',
icon: <CIcon icon={cilPaperPlane} customClassName="nav-icon" />,
},
{
component: CNavItem,
name: 'Product manual',
to: '/product-manual',
icon: <CIcon icon={cilFile} customClassName="nav-icon" />,
},
// {
// component: CNavItem,
// name: 'Orders',

View File

@ -24,9 +24,11 @@ import {
cilMoon,
cilSun,
} from '@coreui/icons'
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'
import { AppBreadcrumb } from './index'
import { AppHeaderDropdown } from './header/index'
import { IconButton } from '@mui/material'
const AppHeader = () => {
const headerRef = useRef()
@ -51,6 +53,7 @@ const AppHeader = () => {
>
<CIcon icon={cilMenu} size="lg" />
</CHeaderToggler>
{/* <CHeaderNav className="d-none d-md-flex">
<CNavItem>
<CNavLink to="/dashboard" as={NavLink}>
@ -130,6 +133,12 @@ const AppHeader = () => {
<div className="vr h-100 mx-2 text-body text-opacity-75"></div>
</li> */}
<AppHeaderDropdown />
{/* <CNavLink to="/cart" as={NavLink}>
<IconButton>
<ShoppingCartIcon style={{ color: 'black' }} />
</IconButton>
</CNavLink> */}
</CHeaderNav>
</CContainer>
<CContainer className="px-4" fluid>

View File

@ -56,7 +56,7 @@ const AppHeaderDropdown = () => {
return (
<CDropdown variant="nav-item">
<CDropdownToggle placement="bottom-end" className="py-0 pe-0" caret={false}>
<div className="media d-flex align-items-center">
<div className="media d-flex align-items-center " style={{ marginTop: '0.5rem' }}>
<FontAwesomeIcon
style={{ fontSize: '2rem' }}
className="user-avatar md-avatar rounded-circle "

16
src/index.css Normal file
View File

@ -0,0 +1,16 @@
/* Poppins */
@import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap');
/* Space Grotesk */
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk&display=swap');
/* Inter */
@import url('https://fonts.googleapis.com/css2?family=Inter&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
margin: 0;
padding: 0;
min-height: 100vh;
}

View File

@ -2,6 +2,7 @@ import React from 'react'
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'
import 'core-js'
import './index.css'
import App from './App'
import store from './store'

View File

@ -2,15 +2,38 @@
/* eslint-disable react/prop-types */
import { useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { jwtDecode } from 'jwt-decode'
const isTokenExpired = (token) => {
try {
const decodedToken = jwtDecode(token)
console.log('Decoded Token:', decodedToken) // Debugging
const currentTime = Date.now() / 1000
console.log('Current Time:', currentTime) // Debugging
console.log('Token Expiration Time:', decodedToken.exp) // Debugging
return decodedToken.exp < currentTime
} catch (error) {
console.error('Error decoding token:', error) // Debugging
return true // If there's an error decoding the token, consider it expired
}
}
const ProtectedRoute = ({ element: Element }) => {
const navigate = useNavigate()
console.log('req came here ')
useEffect(() => {
if (!localStorage.getItem('authToken')) {
navigate('/login')
const checkToken = () => {
const token = localStorage.getItem('authToken')
console.log('Token:', token) // Debugging
if (!token || isTokenExpired(token)) {
console.log('Token is expired or not present, redirecting to login')
navigate('/login')
} else {
console.log('Token is valid')
}
}
checkToken()
}, [navigate])
return <Element />

View File

@ -0,0 +1,126 @@
const initialState = {
items: [],
}
const cartactionTypes = {
ADD_TO_CART: 'ADD_TO_CART',
REMOVE_FROM_CART: 'REMOVE_FROM_CART',
INCREASE_COUNT: 'INCREASE_COUNT',
DECREASE_COUNT: 'DECREASE_COUNT',
LOAD_CART: 'LOAD_CART',
CLEAR_CART: 'CLEAR_CART',
}
export const addToCart = (product) => (dispatch, getState) => {
dispatch({
type: cartactionTypes.ADD_TO_CART,
payload: product,
})
localStorage.setItem('cart', JSON.stringify(getState().cart.items))
}
export const removeFromCart = (productId) => (dispatch, getState) => {
dispatch({
type: cartactionTypes.REMOVE_FROM_CART,
payload: productId,
})
localStorage.setItem('cart', JSON.stringify(getState().cart.items))
}
export const increaseCount = (productId) => (dispatch, getState) => {
dispatch({
type: cartactionTypes.INCREASE_COUNT,
payload: productId,
})
localStorage.setItem('cart', JSON.stringify(getState().cart.items))
}
export const decreaseCount = (productId) => (dispatch, getState) => {
dispatch({
type: cartactionTypes.DECREASE_COUNT,
payload: productId,
})
localStorage.setItem('cart', JSON.stringify(getState().cart.items))
}
export const loadCart = () => (dispatch) => {
const cartItems = JSON.parse(localStorage.getItem('cart')) || []
dispatch({
type: cartactionTypes.LOAD_CART,
payload: cartItems,
})
}
export const clearCart = () => (dispatch) => {
dispatch({
type: cartactionTypes.CLEAR_CART,
})
localStorage.removeItem('cart')
}
export const cartReducer = (state = initialState, action) => {
switch (action.type) {
case cartactionTypes.LOAD_CART:
return {
...state,
items: action.payload,
}
case cartactionTypes.ADD_TO_CART:
const existingItem = state.items.find((item) => item._id === action.payload._id)
if (existingItem) {
return {
...state,
items: state.items.map((item) =>
item._id === action.payload._id ? { ...item, count: item.count + 1 } : item,
),
}
} else {
return {
...state,
items: [...state.items, { ...action.payload, count: 1 }],
}
}
case cartactionTypes.REMOVE_FROM_CART:
return {
...state,
items: state.items.filter((item) => item._id !== action.payload),
}
case cartactionTypes.INCREASE_COUNT:
return {
...state,
items: state.items.map((item) =>
item._id === action.payload ? { ...item, count: item.count + 1 } : item,
),
}
case cartactionTypes.DECREASE_COUNT:
return {
...state,
items: state.items.map((item) =>
item._id === action.payload && item.count > 1 ? { ...item, count: item.count - 1 } : item,
),
}
case cartactionTypes.CLEAR_CART:
return {
...state,
items: [],
}
default:
return state
}
}
// src/store/cart/selectors.js
// Selector to get all cart items
export const selectCartItems = (state) => state.cart.items
// Selector to get the total count of items in the cart
export const selectCartItemCount = (state) =>
state.cart.items.reduce((total, item) => total + item.count, 0)
// Selector to get the subtotal (sum of price * count) of items in the cart
export const selectCartSubtotal = (state) =>
state.cart.items.reduce((total, item) => total + item.price * item.count, 0)
// Selector to check if a specific product is already in the cart
export const selectIsProductInCart = (productId) => (state) =>
state.cart.items.some((item) => item._id === productId)

View File

@ -1,5 +1,11 @@
import { element } from 'prop-types'
import React from 'react'
import Cart from './views/pages/cart/cart'
import OrderDetails from './views/orders/OrderDetails'
import ProductManual from './views/pages/productManual/productManual'
import ViewProductManual from './views/pages/productManual/viewProductManual'
const Dashboard = React.lazy(() => import('./views/dashboard/Dashboard'))
const Shop = React.lazy(() => import('./views/shops/Shop'))
const Order = React.lazy(() => import('./views/orders/Order'))
@ -12,13 +18,20 @@ const routes = [
{ path: '/', exact: true, name: 'Home' },
{ path: '/dashboard', name: 'Dashboard', element: Dashboard },
{ path: '/shop', name: 'Shop', element: Shop },
{ path: '/order', name: 'Order', element: Order },
{ path: '/orders-placed', name: 'Order', element: Order },
{ path: '/orders-placed/:id', name: 'Order', element: OrderDetails },
// KYC
{ path: '/kyc', name: 'Kyc', element: Kyc },
{ path: '/kyc', name: 'KYC', element: Kyc },
{ path: '/kyc/details/:id', name: 'Kyc details', element: KycDetails },
// Product manual
{ path: '/product-manual', name: 'Product Manual ', element: ProductManual },
{ path: '/product-manual/:id', name: 'Product Manual ', element: ViewProductManual },
{ path: '/my-profile', name: 'Profile', element: MyProfile },
{ path: '/change-password', name: 'Change password', element: ChangePassword },
{ path: '/cart', name: 'Cart', element: Cart },
]
export default routes

View File

@ -1,4 +1,7 @@
import { legacy_createStore as createStore } from 'redux'
import { applyMiddleware, combineReducers, legacy_createStore as createStore } from 'redux'
import { cartReducer } from './redux-store/CartStore/ducs'
import { thunk } from 'redux-thunk'
// import { composeWithDevTools } from 'redux-devtools-extension'
const initialState = {
sidebarShow: true,
@ -13,6 +16,9 @@ const changeState = (state = initialState, { type, ...rest }) => {
return state
}
}
const store = createStore(changeState)
const rootReducer = combineReducers({
cart: cartReducer,
changeState: changeState,
})
const store = createStore(rootReducer, applyMiddleware(thunk))
export default store

View File

@ -1,10 +1,80 @@
import React from 'react'
import { Box, Typography, Card, CardContent, Grid } from '@mui/material'
import React, { useState, useEffect } from 'react'
import Axios from '../../axios'
import AddShoppingCartIcon from '@mui/icons-material/AddShoppingCart'
import LocalShippingIcon from '@mui/icons-material/LocalShipping'
import CancelIcon from '@mui/icons-material/Cancel'
import HourglassEmptyIcon from '@mui/icons-material/HourglassEmpty'
import CheckCircleIcon from '@mui/icons-material/CheckCircle'
const Dashboard = () => {
const [counts, setCounts] = useState({
new: 0,
dispatched: 0,
cancelled: 0,
processing: 0,
delivered: 0,
})
// Fetch counts on component mount
useEffect(() => {
const getCounts = async () => {
try {
const res = await Axios.get('/api/get-counts-pdOrders') // Updated API endpoint
if (res.status === 200) {
console.log('res', res)
setCounts(res.data.counts) // Assuming the response is in the format { new, dispatched, cancelled, processing, delivered }
}
} catch (error) {
console.error('Failed to fetch status counts:', error)
}
}
getCounts()
}, [])
// Define colors for different statuses
const statusColors = {
new: '#4caf50', // Green
dispatched: '#2196f3', // Blue
cancelled: '#f44336', // Red
processing: '#ff9800', // Orange
delivered: '#9c27b0', // Purple
}
const statusIcons = {
new: <AddShoppingCartIcon sx={{ fontSize: 50, color: '#fff' }} />,
dispatched: <LocalShippingIcon sx={{ fontSize: 50, color: '#fff' }} />,
cancelled: <CancelIcon sx={{ fontSize: 50, color: '#fff' }} />,
processing: <HourglassEmptyIcon sx={{ fontSize: 50, color: '#fff' }} />,
delivered: <CheckCircleIcon sx={{ fontSize: 50, color: '#fff' }} />,
}
return (
<>
<div>Dashboard</div>
</>
<Box sx={{ flexGrow: 1, padding: 2 }}>
<Typography textAlign="center" variant="h3" gutterBottom>
Orders Status Summary
</Typography>
<Grid container spacing={3}>
{Object.keys(counts).map((status) => (
<Grid item xs={12} sm={6} md={3} key={status}>
<Card sx={{ backgroundColor: statusColors[status], color: '#fff' }}>
<CardContent
sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}
>
<Box>
<Typography variant="h6" component="div">
{status.charAt(0).toUpperCase() + status.slice(1)}
</Typography>
<Typography variant="h4" component="div">
{counts[status]}
</Typography>
</Box>
{statusIcons[status]} {/* Display the corresponding icon */}
</CardContent>
</Card>
</Grid>
))}
</Grid>
</Box>
)
}

View File

@ -1,7 +1,158 @@
import React from 'react'
import React, { useEffect, useState } from 'react'
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
Typography,
Skeleton,
Box,
Button,
TablePagination,
} from '@mui/material'
import Axios from '../../axios'
import { isAutheticated } from '../../auth'
import { useNavigate } from 'react-router-dom'
const Order = () => {
return <div>Order</div>
const [orders, setOrders] = useState([])
const [loading, setLoading] = useState(true)
const [page, setPage] = useState(0)
const [rowsPerPage, setRowsPerPage] = useState(5)
const [totalOrders, setTotalOrders] = useState(0)
const token = isAutheticated()
const navigate = useNavigate()
const formatAMPM = (date) => {
var hours = new Date(date).getHours()
var minutes = new Date(date).getMinutes()
var ampm = hours >= 12 ? 'PM' : 'AM'
hours = hours % 12
hours = hours ? hours : 12 // the hour '0' should be '12'
minutes = minutes < 10 ? '0' + minutes : minutes
var strTime = hours + ':' + minutes + ' ' + ampm
return strTime
}
// Helper function to capitalize the first letter of a string
const capitalizeFirstLetter = (string) => {
return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase()
}
useEffect(() => {
// Fetch orders from the API with pagination
const fetchOrders = async () => {
try {
const response = await Axios.get('/api/get-placed-order-pd', {
headers: {
'Access-Control-Allow-Origin': '*',
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
params: {
page: page + 1, // page number for API (usually starts from 1)
limit: rowsPerPage, // number of rows per page
},
})
setOrders(response.data.plcaedOrders)
setTotalOrders(response.data.totalOrders) // Ensure the API returns the total count of orders
} catch (error) {
console.error('Error fetching orders:', error)
} finally {
setLoading(false)
}
}
fetchOrders()
}, [page, rowsPerPage])
const handleChangePage = (event, newPage) => {
setPage(newPage)
}
const handleChangeRowsPerPage = (event) => {
setRowsPerPage(parseInt(event.target.value, 10))
setPage(0) // Reset page to 0 when rows per page changes
}
return (
<Box>
<Typography variant="h4" mb={2} textAlign={'center'}>
Order placed list
</Typography>
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow sx={{ fontWeight: 'bold' }}>
<TableCell sx={{ fontWeight: 'bold' }}>Order ID</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Order Date</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Items</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Order Value</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Status</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
{loading ? (
Array.from(new Array(rowsPerPage)).map((_, index) => (
<TableRow key={index}>
<TableCell colSpan={6}>
<Skeleton height={40} />
</TableCell>
</TableRow>
))
) : orders.length > 0 ? (
orders.map((order) => (
<TableRow key={order._id}>
<TableCell>{order.uniqueId}</TableCell>
<TableCell>
{new Date(order?.createdAt)
.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
})
.replace(',', '')}{' '}
, {`${formatAMPM(order?.createdAt)}`}
</TableCell>
<TableCell>{order.orderItem.length}</TableCell>
<TableCell>{order.grandTotal.toFixed(2)}</TableCell>
<TableCell>{capitalizeFirstLetter(order.status)}</TableCell>
<TableCell>
<Button
variant="contained"
color="primary"
onClick={() => navigate(`/orders-placed/${order._id}`)}
>
View
</Button>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={6} align="center">
<Typography variant="body1">Data not found</Typography>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
component="div"
count={totalOrders} // Total number of orders
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</TableContainer>
</Box>
)
}
export default Order

View File

@ -0,0 +1,233 @@
import React, { useEffect, useState } from 'react'
import {
Box,
Button,
Grid,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Typography,
} from '@mui/material'
import axios from 'axios' // Make sure you have axios installed
import { useNavigate, useParams } from 'react-router-dom'
import Axios from '../../axios'
import { isAutheticated } from '../../auth'
const OrderDetails = () => {
const [order, setOrder] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const token = isAutheticated()
const navigate = useNavigate()
const { id } = useParams() // Assuming order ID is passed as a route parameter
const [ownerDetails, setOwnerDetails] = useState()
const getData = async () => {
let res = await Axios.get(`/api/v1/user/details`, {
headers: {
Authorization: `Bearer ${token}`,
},
})
if (res.data.success) {
setOwnerDetails({ ...res.data.user })
}
}
console.log('order', order)
useEffect(() => {
const fetchOrderDetails = async () => {
try {
const response = await Axios.get(`/api/get-single-placed-order-pd/${id}`, {
headers: {
Authorization: `Bearer ${token}`,
},
}) // Adjust API endpoint as needed
setOrder(response.data.singleOrder)
console.log(response)
setLoading(false)
} catch (err) {
setError(err.return_msg)
setLoading(false)
}
}
getData()
fetchOrderDetails()
}, [id])
if (loading) {
return <Typography>Loading...</Typography>
}
if (error) {
return <Typography>Error: {error}</Typography>
}
if (!order) {
return <Typography>No data found</Typography>
}
const {
uniqueId,
billTo,
shipTo,
paymentMode,
orderItem,
subtotal,
gstTotal,
grandTotal,
statusHistory, // Assume this is an array of status objects
} = order
console.log(statusHistory)
return (
<Box>
{/* Order Details Section */}
<Box sx={{ my: '2rem', background: '#fff', padding: '1rem', borderRadius: '0.8rem' }}>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={2}>
<Typography variant="h4" sx={{ flexGrow: 1, textAlign: 'center' }}>
Order ID: {uniqueId}
</Typography>
<Button color="primary" onClick={() => navigate('/orders-placed')} variant="contained">
Back
</Button>
</Box>
<Grid container spacing={2}>
<Grid item sm={6} md={6} lg={6}>
<Typography variant="h5" sx={{ mb: '0.5rem' }}>
Bill Address
</Typography>
<Typography sx={{ mb: '0.5rem' }}>{billTo}</Typography>
</Grid>
<Grid item sm={6} md={6} lg={6}>
<Typography variant="h5" sx={{ mb: '0.5rem' }}>
Ship Address
</Typography>
<Typography sx={{ mb: '0.5rem' }}>{shipTo}</Typography>
</Grid>
<Grid item sm={6} md={6} lg={6}>
<Typography variant="h5" sx={{ mb: '0.5rem' }}>
Payment Mode: <strong>{paymentMode}</strong>
</Typography>
</Grid>
<Grid item sm={12} md={12} lg={12}>
<Typography variant="h5" sx={{ mb: '0.5rem' }}>
SBU: <strong>{ownerDetails?.SBU}</strong>
</Typography>
</Grid>
</Grid>
</Box>
{/* Order Items Table */}
<Box my={8}>
<Grid container spacing={5}>
<Grid item xs={12}>
<Box>
<TableContainer
component={Paper}
elevation={0}
sx={{ borderBottom: '1.5px solid #CFCFD5', borderRadius: 0 }}
>
<Table sx={{ minWidth: 650 }} size="large">
<TableHead>
<TableRow>
<TableCell sx={{ fontWeight: 'bold' }}>Product</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Category</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Brand</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>SKU</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Quantity</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Price</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>GST</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Subtotal</TableCell>
</TableRow>
</TableHead>
<TableBody>
{order.orderItem.map((row, index) => {
const gstAmount = (row.price * row.GST) / 100
return (
<TableRow
key={index}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell>
<Box sx={{ display: 'flex' }}>
<Box>
<Typography fontWeight={600}>{row.name}</Typography>
</Box>
</Box>
</TableCell>
<TableCell>{row.categoryName}</TableCell>
<TableCell>{row.brandName}</TableCell>
<TableCell>{row.SKU}</TableCell>
<TableCell>{row.quantity}</TableCell>
<TableCell>{row.price}</TableCell>
<TableCell>{gstAmount}</TableCell>
<TableCell>{row.price * row.quantity}</TableCell>
</TableRow>
)
})}
<TableRow>
<TableCell colSpan={7} />
<TableCell>
Subtotal:<strong> {subtotal}</strong>
</TableCell>
</TableRow>
<TableRow>
<TableCell colSpan={7} />
<TableCell>
Total GST:<strong> {gstTotal} </strong>
</TableCell>
</TableRow>
<TableRow>
<TableCell colSpan={7} />
<TableCell>
Grand Total: <strong> {grandTotal}</strong>
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Box>
</Grid>
</Grid>
</Box>
{/* Status History Table */}
{statusHistory.length > 0 && (
<Box mt={8}>
<Typography variant="h5" sx={{ mb: '1rem' }}>
Status History
</Typography>
<TableContainer
component={Paper}
elevation={0}
sx={{ borderBottom: '1.5px solid #CFCFD5', borderRadius: '0.8rem' }}
>
<Table sx={{ minWidth: 650 }} size="large">
<TableHead>
<TableRow>
<TableCell>Status</TableCell>
<TableCell>Timestamp</TableCell>
</TableRow>
</TableHead>
<TableBody>
{statusHistory.map((status, index) => (
<TableRow key={index}>
<TableCell>{status.status}</TableCell>
<TableCell>{new Date(status.timestamp).toLocaleString()}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Box>
)}
</Box>
)
}
export default OrderDetails

View File

@ -0,0 +1,46 @@
/* eslint-disable react/prop-types */
import React from 'react'
const MessageList = ({ messages }) => {
const formatAMPM = (date) => {
var hours = new Date(date).getHours()
var minutes = new Date(date).getMinutes()
var ampm = hours >= 12 ? 'PM' : 'AM'
hours = hours % 12
hours = hours ? hours : 12 // the hour '0' should be '12'
minutes = minutes < 10 ? '0' + minutes : minutes
var strTime = hours + ':' + minutes + ' ' + ampm
return strTime
}
const formatDateWithoutComma = (date) => {
const dateObj = new Date(date)
const dateStr = dateObj.toDateString().split(' ')
return `${dateStr[1]} ${dateStr[2]} ${dateStr[3]}`
}
return (
<>
{messages.map((msg, index) => (
<div
key={index}
style={{
marginBottom: '10px',
padding: '10px',
borderRadius: '5px',
backgroundColor: msg.user === 'Principal Distributer' ? '#212631' : 'blue',
boxShadow: '0px 3px 6px rgba(0,0,0,0.1)',
}}
>
<div style={{ fontWeight: 'bold', marginBottom: '5px', color: 'white' }}>{msg.user}</div>
<div style={{ fontWeight: 'bold', marginBottom: '5px', color: 'white' }}>
{/* {new Date(`${msg.replyDate}`).toDateString()} */}
{`${formatDateWithoutComma(msg?.replyDate)}`}
<span> , {`${formatAMPM(msg?.replyDate)}`}</span>
</div>
<div style={{ color: 'white' }}>{msg.message}</div>
</div>
))}
</>
)
}
export default MessageList

View File

@ -0,0 +1,55 @@
/* eslint-disable react/prop-types */
import React from 'react'
import { Box, Modal, Typography, IconButton, Button } from '@mui/material'
import CloseIcon from '@mui/icons-material/Close'
const ImgModal = ({ open, handleClose, imageUrl }) => {
const handleDownload = () => {
const link = document.createElement('a')
link.href = imageUrl
link.download = imageUrl.split('/').pop()
link.click()
}
return (
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={{ ...modalStyle, width: '90%', height: '90%' }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
<Typography id="modal-modal-title" variant="h6" component="h2">
Document Image
</Typography>
<IconButton onClick={handleClose}>
<CloseIcon />
</IconButton>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'center', mb: 2 }}>
<img
src={imageUrl}
alt="Document"
style={{ maxWidth: '100%', maxHeight: '600px', objectFit: 'contain' }}
/>
</Box>
<Button variant="contained" onClick={handleDownload} sx={{ textTransform: 'unset' }}>
Download
</Button>
</Box>
</Modal>
)
}
const modalStyle = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
bgcolor: 'background.paper',
boxShadow: 24,
p: 4,
}
export default ImgModal

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react'
import React, { useState, useEffect } from 'react'
import {
Box,
Table,
@ -10,34 +10,51 @@ import {
Paper,
TablePagination,
Button,
IconButton,
Tooltip,
Tabs,
Tab,
Modal,
TextField,
DialogActions,
Typography,
} from '@mui/material'
import { Visibility, ThumbUp, ThumbDown } from '@mui/icons-material'
import { format } from 'date-fns'
import { useNavigate } from 'react-router-dom'
const generateRandomData = (numRows) => {
const statuses = ['New', 'Pending', 'Rejected', 'Approved']
const data = []
for (let i = 0; i < numRows; i++) {
data.push({
id: i + 1,
tradeName: `Trade ${i + 1}`,
createdOn: new Date(),
status: statuses[Math.floor(Math.random() * statuses.length)],
})
}
return data
}
import Axios from '../../../axios'
import { isAutheticated } from '../../../auth'
import Swal from 'sweetalert2'
const Kyc = () => {
const [rows, setRows] = useState(generateRandomData(50))
const [rows, setRows] = useState([])
const [page, setPage] = useState(0)
const [rowsPerPage, setRowsPerPage] = useState(5)
const [rowsPerPage, setRowsPerPage] = useState(10)
const [tabIndex, setTabIndex] = useState(0)
const [loading, setLoading] = useState(false)
const [openModal, setOpenModal] = useState(false)
const [openModal2, setOpenModal2] = useState(false)
const [rejectionReason, setRejectionReason] = useState('')
const [selectedRowId, setSelectedRowId] = useState(null)
const [selectedRowId2, setSelectedRowId2] = useState(null)
const navigate = useNavigate()
const token = isAutheticated()
useEffect(() => {
const fetchData = async () => {
try {
const response = await Axios.get('/api/kyc/getAll/', {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
})
console.log(response)
setRows(response.data)
} catch (error) {
console.error('Error fetching data: ', error)
}
}
fetchData()
}, [])
const handleChangePage = (event, newPage) => {
setPage(newPage)
@ -52,75 +69,283 @@ const Kyc = () => {
navigate(`/kyc/details/${id}`)
}
const handleTabChange = (event, newValue) => {
setTabIndex(newValue)
}
const filterRowsByStatus = (status) => {
return rows.filter((row) => row.status === status)
}
const filteredRows = filterRowsByStatus(
tabIndex === 0 ? 'new' : tabIndex === 1 ? 'approved' : 'reject',
)
const handleRejectClick = async (id) => {
setSelectedRowId(id)
setOpenModal(true)
}
const handleApproveClick = async (id) => {
setSelectedRowId2(id)
setOpenModal2(true)
}
const handleApproveConfirm = async () => {
try {
setLoading(true)
console.log(selectedRowId2)
await Axios.patch(
`/api/kyc/update/${selectedRowId2}`,
{
status: 'approved',
},
{
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
},
)
setRows((prevRows) =>
prevRows.map((row) => (row._id === selectedRowId2 ? { ...row, status: 'approved' } : row)),
)
setLoading(true)
Swal.fire('Success', 'Approved', 'success')
handleModalClose2()
} catch (error) {
setLoading(false)
console.error('Error approving KYC: ', error)
Swal.fire('Error!', error.message, 'error')
handleModalClose2()
}
}
const handleModalClose = () => {
setOpenModal(false)
setRejectionReason('')
}
const handleModalClose2 = () => {
setOpenModal2(false)
}
const handleRejectionReasonChange = (event) => {
setRejectionReason(event.target.value)
}
const handleRejectConfirm = async () => {
try {
setLoading(true)
await Axios.patch(
`/api/kyc/update/${selectedRowId}`,
{
status: 'reject',
rejectionReason,
user: 'Principal Distributer',
},
{
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
},
)
setRows((prevRows) =>
prevRows.map((row) => (row._id === selectedRowId ? { ...row, status: 'reject' } : row)),
)
setLoading(true)
Swal.fire('Success', 'Rejected', 'success')
handleModalClose()
} catch (error) {
setLoading(false)
console.error('Error rejecting KYC: ', error)
Swal.fire('Error!', error.message, 'error')
handleModalClose()
}
}
const formatAMPM = (date) => {
var hours = new Date(date).getHours()
var minutes = new Date(date).getMinutes()
var ampm = hours >= 12 ? 'PM' : 'AM'
hours = hours % 12
hours = hours ? hours : 12 // the hour '0' should be '12'
minutes = minutes < 10 ? '0' + minutes : minutes
var strTime = hours + ':' + minutes + ' ' + ampm
return strTime
}
const capitalizeStatus = (status) => {
return status.charAt(0).toUpperCase() + status.slice(1)
}
// Helper function to format the date
const formatDateWithoutComma = (date) => {
const dateObj = new Date(date)
const dateStr = dateObj.toDateString().split(' ')
return `${dateStr[1]} ${dateStr[2]} ${dateStr[3]}`
}
return (
<Box sx={{ width: '100%' }}>
<Paper sx={{ width: '100%', mb: 2 }}>
<Tabs value={tabIndex} onChange={handleTabChange}>
<Tab label="New" sx={{ textTransform: 'unset' }} />
<Tab label="Approved" sx={{ textTransform: 'unset' }} />
<Tab label="Rejected" sx={{ textTransform: 'unset' }} />
</Tabs>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>ID</TableCell>
<TableCell>Trade Name</TableCell>
<TableCell>Created On</TableCell>
<TableCell>Status</TableCell>
<TableCell>Action</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>ID</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Trade Name</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Created On</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Status</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((row) => (
<TableRow key={row.id}>
<TableCell>{row.id}</TableCell>
<TableCell>{row.tradeName}</TableCell>
<TableCell>{format(row.createdOn, 'yyyy-MM-dd HH:mm:ss')}</TableCell>
<TableCell>{row.status}</TableCell>
<TableCell>
<Tooltip title="View">
{/* <IconButton color="primary">
<Visibility />
</IconButton> */}
<Button
sx={{ mr: '1rem' }}
color="primary"
variant="contained"
onClick={() => handleViewClick(row.id)}
>
View
</Button>
</Tooltip>
<Tooltip title="Approve">
{/* <IconButton color="success">
<ThumbUp />
</IconButton> */}
<Button sx={{ mr: '1rem' }} color="success" variant="contained">
Approve
</Button>
</Tooltip>
<Tooltip title="Reject">
{/* <IconButton color="error">
<ThumbDown />
</IconButton> */}
<Button sx={{ mr: '1rem' }} color="error" variant="contained">
Reject
</Button>
</Tooltip>
</TableCell>
</TableRow>
))}
{filteredRows.length !== 0 &&
filteredRows
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((row) => (
<TableRow key={row._id}>
<TableCell>{row._id}</TableCell>
<TableCell>{row.trade_name}</TableCell>
<TableCell>
{`${formatDateWithoutComma(row?.createdAt)}`}
<span>, {`${formatAMPM(row?.createdAt)}`}</span>
</TableCell>
<TableCell>{capitalizeStatus(row.status)}</TableCell>
<TableCell>
<Tooltip title="View">
<Button
sx={{ mr: '1rem', textTransform: 'unset' }}
color="primary"
variant="contained"
onClick={() => handleViewClick(row._id)}
>
View
</Button>
</Tooltip>
{tabIndex === 0 && (
<>
<Tooltip title="Approve">
<Button
sx={{ mr: '1rem', textTransform: 'unset' }}
color="success"
variant="contained"
onClick={() => handleApproveClick(row._id)}
>
Approve
</Button>
</Tooltip>
<Tooltip title="Reject">
<Button
sx={{ mr: '1rem', textTransform: 'unset' }}
color="error"
variant="contained"
onClick={() => handleRejectClick(row._id)}
>
Reject
</Button>
</Tooltip>
</>
)}
</TableCell>
</TableRow>
))}
{filteredRows.length === 0 && (
<h5 style={{ textAlign: 'center' }}>No kyc available</h5>
)}
</TableBody>
</Table>
</TableContainer>
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
rowsPerPageOptions={[10, 15, 25]}
component="div"
count={rows.length}
count={filteredRows.length}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</Paper>
<Modal
open={openModal}
onClose={handleModalClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={{ ...modalStyle, width: 400 }}>
<h2 id="modal-modal-title">Rejection Reason</h2>
<TextField
fullWidth
multiline
rows={4}
value={rejectionReason}
onChange={handleRejectionReasonChange}
variant="outlined"
label="Enter rejection reason"
/>
<DialogActions>
<Button onClick={handleModalClose} sx={{ textTransform: 'unset' }} color="primary">
Cancel
</Button>
<Button
onClick={handleRejectConfirm}
sx={{ textTransform: 'unset' }}
color="error"
variant="contained"
>
Confirm
</Button>
</DialogActions>
</Box>
</Modal>
<Modal
open={openModal2}
onClose={handleModalClose2}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={{ ...modalStyle, width: 400 }}>
<h2 id="modal-modal-title">Approval Confirmation</h2>
<Typography id="modal-modal-description" sx={{ mt: 2 }}>
Are you sure you want to approve this KYC?
</Typography>
<DialogActions>
<Button
onClick={handleModalClose2}
sx={{ textTransform: 'unset' }}
color="primary"
// variant="contained"
>
Cancel
</Button>
<Button
onClick={handleApproveConfirm}
sx={{ textTransform: 'unset' }}
color="success"
variant="contained"
>
Confirm
</Button>
</DialogActions>
</Box>
</Modal>
</Box>
)
}
const modalStyle = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
bgcolor: 'background.paper',
boxShadow: 24,
p: 4,
}
export default Kyc

View File

@ -1,51 +1,194 @@
import React from 'react'
import { Box, Typography, Grid, Paper, Avatar } from '@mui/material'
import { useParams } from 'react-router-dom'
import React, { useState, useEffect } from 'react'
import axios from 'axios'
import {
Box,
Typography,
Grid,
Paper,
Avatar,
Button,
TextField,
Modal,
DialogActions,
} from '@mui/material'
import { useNavigate, useParams } from 'react-router-dom'
import { format } from 'date-fns'
const generateRandomRetailerDetails = (id) => ({
id,
tradeName: `Trade ${id}`,
name: `Retailer ${id}`,
address: '123 Main St',
townCity: 'Townsville',
district: 'District A',
state: 'State B',
pincode: '123456',
mobileNumber: '123-456-7890',
mappedDistributor: `Distributor ${id}`,
documents: {
panNumber: 'ABCDE1234F',
panCard:
'https://www.shutterstock.com/shutterstock/photos/2329691987/display_1500/stock-vector-blank-pan-card-vector-image-income-tax-card-personal-account-number-image-translation-income-2329691987.jpg',
aadharNumber: '1234-5678-9101',
aadharCard: 'https://via.placeholder.com/100',
gstNumber: '22AAAAA0000A1Z5',
gstRegistration: 'https://via.placeholder.com/100',
pesticideLicense: 'https://via.placeholder.com/100',
fertilizerLicense: 'https://via.placeholder.com/100',
entranceBoardSelfie: 'https://via.placeholder.com/100',
},
salesCoordinator: {
designation: 'Sales Coordinator',
name: `Coordinator ${id}`,
employeeId: `EMP${id}`,
uploadedOn: new Date(),
resubmittedOn: new Date(),
},
notes: ['Note 1', 'Note 2', 'Note 3'],
})
import Axios from '../../../axios'
import { isAutheticated } from '../../../auth'
import MessageList from './MessageList'
import Swal from 'sweetalert2'
import ImgModal from './imgModal'
const KycDetails = () => {
const { id } = useParams()
console.log(id)
const retailerDetails = generateRandomRetailerDetails(id)
const [openRejectModal, setOpenRejectModal] = useState(false)
const [openApproveModal, setOpenApproveModal] = useState(false)
const [rejectionReason, setRejectionReason] = useState('')
const [selectedRowId, setSelectedRowId] = useState(null)
const [retailerDetails, setRetailerDetails] = useState(null)
const [openImageModal, setOpenImageModal] = useState(false)
const [selectedImageUrl, setSelectedImageUrl] = useState('')
const token = isAutheticated()
const navigate = useNavigate()
useEffect(() => {
const fetchData = async () => {
try {
const response = await Axios.get(`/api/kyc/get-single-kyc/${id}`, {
headers: {
'Access-Control-Allow-Origin': '*',
Authorization: `Bearer ${token}`,
'Content-Type': 'multipart/formdata',
},
})
console.log(response.data)
setRetailerDetails(response.data)
} catch (error) {
console.error('Error fetching data: ', error)
}
}
fetchData()
}, [id])
const [msg, setMsg] = useState('')
const handelSend = async () => {
try {
const resp = await Axios.patch(
`/api/kyc/update/${id}`,
{
rejectionReason: msg,
user: 'Principal Distributer', // Replace with actual user type
},
{
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
},
)
setRetailerDetails(resp.data.kyc)
setMsg('')
Swal.fire('Success', 'Message sent', 'success')
} catch (error) {
console.error('Error sending message: ', error)
Swal.fire('Error!', error.message, 'error')
}
}
const handleModalClose = () => {
setOpenRejectModal(false)
setOpenApproveModal(false)
setRejectionReason('')
}
const handleRejectionReasonChange = (event) => {
setRejectionReason(event.target.value)
}
const formatDateWithoutComma = (date) => {
const dateObj = new Date(date)
const dateStr = dateObj.toDateString().split(' ')
return `${dateStr[1]} ${dateStr[2]} ${dateStr[3]}`
}
const handleRejectConfirm = async () => {
try {
const res = await Axios.patch(
`/api/kyc/update/${id}`,
{
status: 'reject',
rejectionReason,
user: 'Principal Distributer', // Replace with actual user type
},
{
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
},
)
setRetailerDetails(res.data.kyc)
Swal.fire('Success', 'Rejected', 'success')
handleModalClose()
} catch (error) {
console.error('Error rejecting KYC: ', error)
Swal.fire('Error!', error.message, 'error')
handleModalClose()
}
}
const handleApproveConfirm = async () => {
try {
const res = await Axios.patch(
`/api/kyc/update/${id}`,
{
status: 'approved',
// user: 'Principal Distributer', // Replace with actual user type
},
{
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
},
)
setRetailerDetails(res.data.kyc)
Swal.fire('Success', 'Approved', 'success')
handleModalClose()
} catch (error) {
console.error('Error approving KYC: ', error)
Swal.fire('Error!', error.message, 'error')
handleModalClose()
}
}
const formatAMPM = (date) => {
var hours = new Date(date).getHours()
var minutes = new Date(date).getMinutes()
var ampm = hours >= 12 ? 'PM' : 'AM'
hours = hours % 12
hours = hours ? hours : 12 // the hour '0' should be '12'
minutes = minutes < 10 ? '0' + minutes : minutes
var strTime = hours + ':' + minutes + ' ' + ampm
return strTime
}
const handleRejectClick = () => {
setOpenRejectModal(true)
}
const handleApproveClick = () => {
setOpenApproveModal(true)
}
const handleImageClick = (url) => {
setSelectedImageUrl(url)
console.log(selectedImageUrl)
setOpenImageModal(true)
}
if (!retailerDetails) {
return <Typography>Loading...</Typography>
}
return (
<Box sx={{ p: 3 }}>
<Typography variant="h4" gutterBottom>
Retailer Details
</Typography>
<Box sx={{ display: 'flex', mb: '1rem' }}>
<Typography variant="h4" flex={1} gutterBottom>
Retailer Details
</Typography>
<Button
onClick={() => navigate('/kyc')}
color="primary"
sx={{ padding: '0.5rem 1rem' }}
variant="contained"
>
Back
</Button>
</Box>
<Paper sx={{ p: 2, mb: 3 }}>
<Typography variant="h5" gutterBottom>
@ -54,7 +197,7 @@ const KycDetails = () => {
<Grid container spacing={2}>
<Grid item xs={6}>
<Typography>
<strong>Trade Name:</strong> {retailerDetails.tradeName}
<strong>Trade Name:</strong> {retailerDetails.trade_name}
</Typography>
<Typography>
<strong>Name:</strong> {retailerDetails.name}
@ -63,7 +206,7 @@ const KycDetails = () => {
<strong>Address:</strong> {retailerDetails.address}
</Typography>
<Typography>
<strong>Town/City:</strong> {retailerDetails.townCity}
<strong>Town/City:</strong> {retailerDetails.city}
</Typography>
</Grid>
<Grid item xs={6}>
@ -77,10 +220,11 @@ const KycDetails = () => {
<strong>Pincode:</strong> {retailerDetails.pincode}
</Typography>
<Typography>
<strong>Mobile Number:</strong> {retailerDetails.mobileNumber}
<strong>Mobile Number:</strong> {retailerDetails.mobile_number}
</Typography>
<Typography>
<strong>Mapped Principal Distributor:</strong> {retailerDetails.mappedDistributor}
<strong>Mapped Principal Distributor:</strong>{' '}
{retailerDetails.principal_distributer.name}
</Typography>
</Grid>
</Grid>
@ -93,28 +237,31 @@ const KycDetails = () => {
<Grid container spacing={2}>
<Grid item xs={6}>
<Typography>
<strong>PAN Number:</strong> {retailerDetails.documents.panNumber}
<strong>PAN Number:</strong> {retailerDetails.pan_number}
</Typography>
<Avatar
variant="square"
src={retailerDetails.documents.panCard}
src={retailerDetails.pan_img.url}
sx={{ width: 100, height: 100, mb: 2 }}
onClick={() => handleImageClick(retailerDetails.pan_img.url)}
/>
<Typography>
<strong>Aadhar Number:</strong> {retailerDetails.documents.aadharNumber}
<strong>Aadhar Number:</strong> {retailerDetails.aadhar_number}
</Typography>
<Avatar
variant="square"
src={retailerDetails.documents.aadharCard}
src={retailerDetails.aadhar_img.url}
sx={{ width: 100, height: 100, mb: 2 }}
onClick={() => handleImageClick(retailerDetails.aadhar_img.url)}
/>
<Typography>
<strong>GST Number:</strong> {retailerDetails.documents.gstNumber}
<strong>GST Number:</strong> {retailerDetails.gst_number}
</Typography>
<Avatar
variant="square"
src={retailerDetails.documents.gstRegistration}
src={retailerDetails.gst_img.url}
sx={{ width: 100, height: 100, mb: 2 }}
onClick={() => handleImageClick(retailerDetails.gst_img.url)}
/>
</Grid>
<Grid item xs={6}>
@ -123,24 +270,31 @@ const KycDetails = () => {
</Typography>
<Avatar
variant="square"
src={retailerDetails.documents.pesticideLicense}
src={retailerDetails.pesticide_license_img.url}
sx={{ width: 100, height: 100, mb: 2 }}
onClick={() => handleImageClick(retailerDetails.pesticide_license_img.url)}
/>
<Typography>
<strong>Fertilizer License (optional):</strong>
</Typography>
<Avatar
variant="square"
src={retailerDetails.documents.fertilizerLicense}
sx={{ width: 100, height: 100, mb: 2 }}
/>
{retailerDetails.fertilizer_license_img ? (
<Avatar
variant="square"
src={retailerDetails.fertilizer_license_img.url}
sx={{ width: 100, height: 100, mb: 2 }}
onClick={() => handleImageClick(retailerDetails.fertilizer_license_img.url)}
/>
) : (
<Typography>Img not available</Typography>
)}
<Typography>
<strong>Selfie of Entrance Board:</strong>
</Typography>
<Avatar
variant="square"
src={retailerDetails.documents.entranceBoardSelfie}
src={retailerDetails.selfie_entrance_img.url}
sx={{ width: 100, height: 100, mb: 2 }}
onClick={() => handleImageClick(retailerDetails.selfie_entrance_img.url)}
/>
</Grid>
</Grid>
@ -148,43 +302,165 @@ const KycDetails = () => {
<Paper sx={{ p: 2, mb: 3 }}>
<Typography variant="h6" gutterBottom>
Block 3: Sales Coordinators/Territory Manager Details
Sales Coordinators/Territory Manager Details
</Typography>
<Grid container spacing={2}>
<Grid item xs={6}>
<Typography>
<strong>Designation:</strong> {retailerDetails.salesCoordinator.designation}
<strong>Designation:</strong>{' '}
{retailerDetails.userType === 'SalesCoOrdinator'
? 'Sales Coordinator'
: 'Territory Manager'}
</Typography>
<Typography>
<strong>Name:</strong> {retailerDetails.salesCoordinator.name}
<strong>Name:</strong> {retailerDetails.addedBy.name}
</Typography>
<Typography>
<strong>Employee ID:</strong> {retailerDetails.salesCoordinator.employeeId}
<strong>Employee ID:</strong> {retailerDetails.addedBy.uniqueId}
</Typography>
</Grid>
<Grid item xs={6}>
<Typography>
<strong>Uploaded on:</strong>{' '}
{format(retailerDetails.salesCoordinator.uploadedOn, 'yyyy-MM-dd HH:mm:ss')}
{/* {new Date(`${retailerDetails?.createdAt}`).toDateString()} */}
{`${formatDateWithoutComma(retailerDetails?.createdAt)}`}
<span> , {`${formatAMPM(retailerDetails?.createdAt)}`}</span>
</Typography>
<Typography>
<strong>Resubmitted on:</strong>{' '}
{format(retailerDetails.salesCoordinator.resubmittedOn, 'yyyy-MM-dd HH:mm:ss')}
{/* {new Date(`${retailerDetails?.updatedAt}`).toDateString()} */}
{`${formatDateWithoutComma(retailerDetails?.updatedAt)}`}
<span> , {`${formatAMPM(retailerDetails?.createdAt)}`}</span>
</Typography>
</Grid>
</Grid>
</Paper>
<Paper sx={{ p: 2, mb: 3 }}>
<Typography variant="h6" gutterBottom>
Block 4: Notes
</Typography>
{retailerDetails.notes.map((note, index) => (
<Typography key={index}>{note}</Typography>
))}
</Paper>
{retailerDetails.notes.length > 0 && retailerDetails.status === 'reject' && (
<Paper sx={{ p: 2, mb: 3 }}>
<Typography variant="h6" gutterBottom>
Notes:
</Typography>
<MessageList messages={retailerDetails.notes} />
<TextField
fullWidth
multiline
rows={4}
value={msg}
onChange={(e) => setMsg(e.target.value)}
variant="outlined"
label="send message"
/>
<Button
variant="contained"
sx={{ textTransform: 'unset', mt: '2rem' }}
onClick={() => handelSend()}
>
Send
</Button>
</Paper>
)}
{retailerDetails.status === 'new' && (
<>
<Modal
open={openRejectModal}
onClose={handleModalClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={{ ...modalStyle, width: 400 }}>
<h2 id="modal-modal-title">Rejection Reason</h2>
<TextField
fullWidth
multiline
rows={4}
value={rejectionReason}
onChange={handleRejectionReasonChange}
variant="outlined"
label="Enter rejection reason"
/>
<DialogActions>
<Button onClick={handleModalClose} sx={{ textTransform: 'unset' }} color="primary">
Cancel
</Button>
<Button
onClick={handleRejectConfirm}
sx={{ textTransform: 'unset' }}
color="error"
variant="contained"
>
Confirm
</Button>
</DialogActions>
</Box>
</Modal>
<Modal
open={openApproveModal}
onClose={handleModalClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={{ ...modalStyle, width: 400 }}>
<h2 id="modal-modal-title">Approval Confirmation</h2>
<Typography id="modal-modal-description" sx={{ mt: 2 }}>
Are you sure you want to approve this KYC?
</Typography>
<DialogActions>
<Button onClick={handleModalClose} sx={{ textTransform: 'unset' }} color="primary">
Cancel
</Button>
<Button
onClick={handleApproveConfirm}
sx={{ textTransform: 'unset' }}
color="success"
variant="contained"
>
Confirm
</Button>
</DialogActions>
</Box>
</Modal>
<Paper sx={{ p: 2, mb: 3 }}>
<Button
sx={{ mr: '1rem', textTransform: 'unset' }}
color="success"
variant="contained"
onClick={handleApproveClick}
>
Approve
</Button>
<Button
sx={{ mr: '1rem', textTransform: 'unset' }}
color="error"
variant="contained"
onClick={handleRejectClick}
>
Reject
</Button>
</Paper>
</>
)}
<ImgModal
open={openImageModal}
handleClose={() => setOpenImageModal(false)}
imageUrl={selectedImageUrl}
/>
</Box>
)
}
const modalStyle = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
bgcolor: 'background.paper',
boxShadow: 24,
p: 4,
}
export default KycDetails

View File

@ -0,0 +1,167 @@
import {
Box,
Button,
FormControl,
FormControlLabel,
Grid,
InputLabel,
MenuItem,
Radio,
RadioGroup,
Select,
Typography,
FormHelperText,
} from '@mui/material'
import React, { useState } from 'react'
const AddressAndPayment = ({
billTo,
setBillTo,
shipTo,
setShipTo,
paymentMode,
setPaymentMode,
handleTabChange,
}) => {
const [billToError, setBillToError] = useState(false)
const [shipToError, setShipToError] = useState(false)
const [paymentModeError, setPaymentModeError] = useState(false)
const handleReviewOrderClick = (e) => {
let isValid = true
// Check if Bill Address is filled
if (billTo === '') {
setBillToError(true)
isValid = false
} else {
setBillToError(false)
}
// Check if Ship Address is filled
if (shipTo === '') {
setShipToError(true)
isValid = false
} else {
setShipToError(false)
}
// Check if Payment Mode is selected
if (paymentMode === '') {
setPaymentModeError(true)
isValid = false
} else {
setPaymentModeError(false)
}
// If all fields are valid, move to the next tab
if (isValid) {
handleTabChange(e, 2)
}
}
return (
<div>
<Box sx={{ my: '2rem', background: '#fff', padding: '1rem', borderRadius: '0.8rem' }}>
<Typography variant="h4" textAlign={'center'} marginBottom={2}>
Select Address and Payment Mode
</Typography>
<Grid container spacing={3}>
<Grid item sm={6} md={6} lg={6}>
<Typography sx={{ mb: '0.5rem' }}>Bill Address</Typography>
<FormControl required fullWidth error={billToError}>
<InputLabel id="bill-address-label">Bill Address</InputLabel>
<Select
labelId="bill-address-label"
id="bill-address-select"
required
value={billTo}
label="Bill Address"
onChange={(e) => setBillTo(e.target.value)}
>
<MenuItem value={'123, MG Road, Bengaluru, Karnataka - 560001'}>
123, MG Road, Bengaluru, Karnataka - 560001
</MenuItem>
<MenuItem value={'456, Park Street, Kolkata, West Bengal - 700016'}>
456, Park Street, Kolkata, West Bengal - 700016
</MenuItem>
<MenuItem value={'789, Connaught Place, New Delhi - 110001'}>
789, Connaught Place, New Delhi - 110001
</MenuItem>
</Select>
{billToError && <FormHelperText>Bill Address is required</FormHelperText>}
</FormControl>
</Grid>
<Grid item sm={6} md={6} lg={6}>
<Typography sx={{ mb: '0.5rem' }}>Ship Address</Typography>
<FormControl required fullWidth error={shipToError}>
<InputLabel id="ship-address-label">Ship Address</InputLabel>
<Select
labelId="ship-address-label"
id="ship-address-select"
value={shipTo}
label="Ship Address"
onChange={(e) => setShipTo(e.target.value)}
>
<MenuItem value={'123, MG Road, Bengaluru, Karnataka - 560001'}>
123, MG Road, Bengaluru, Karnataka - 560001
</MenuItem>
<MenuItem value={'456, Park Street, Kolkata, West Bengal - 700016'}>
456, Park Street, Kolkata, West Bengal - 700016
</MenuItem>
<MenuItem value={'789, Connaught Place, New Delhi - 110001'}>
789, Connaught Place, New Delhi - 110001
</MenuItem>
</Select>
{shipToError && <FormHelperText>Ship Address is required</FormHelperText>}
</FormControl>
</Grid>
{billTo !== '' && (
<Grid item sm={6} md={6} lg={6}>
<Typography variant="h5" sx={{ mb: '0.5rem' }}>
Selected Bill Address
</Typography>
<Typography sx={{ mb: '0.5rem' }}>{billTo}</Typography>
</Grid>
)}
{shipTo !== '' && (
<Grid item sm={6} md={6} lg={6}>
<Typography variant="h5" sx={{ mb: '0.5rem' }}>
Selected Ship to Address
</Typography>
<Typography sx={{ mb: '0.5rem' }}>{shipTo}</Typography>
</Grid>
)}
<Grid item sm={12} md={12} lg={12}>
<Typography>Payment Mode</Typography>
<FormControl component="fieldset" error={paymentModeError}>
<RadioGroup value={paymentMode} onChange={(e) => setPaymentMode(e.target.value)}>
<FormControlLabel
value="online-transfer"
control={<Radio />}
label="Online Transfer"
/>
<FormControlLabel value="cheque" control={<Radio />} label="Cheque" />
<FormControlLabel value="credit" control={<Radio />} label="Credit" />
</RadioGroup>
{paymentModeError && <FormHelperText>Payment Mode is required</FormHelperText>}
</FormControl>
</Grid>
</Grid>
<Box sx={{ display: 'flex', justifyContent: 'center', my: 4 }}>
<Button
variant="contained"
size="large"
color="primary"
onClick={handleReviewOrderClick}
sx={{ width: '200px' }}
>
View Order
</Button>
</Box>
</Box>
</div>
)
}
export default AddressAndPayment

View File

@ -0,0 +1,251 @@
import React, { useEffect, useState } from 'react'
import { Box, Button, colors, Container, IconButton, Tab, Tabs, Typography } from '@mui/material'
import CheckIcon from '@mui/icons-material/Check'
// import ShoppingCart from '../../Components/ShoppingCart'
// import CheckoutDetails from '../../Components/CheckoutDetails'
// import OrderComplete from '../../Components/OrderComplete'
import PropTypes from 'prop-types'
import Checkout from './addressAndPayment'
import ShopingCart from './shopingCart'
import OrderComplete from './orderConfirmation'
import AddressAndPayment from './addressAndPayment'
import Swal from 'sweetalert2'
import ReviewOrder from './reviewOrder'
import OrderConfirmation from './orderConfirmation'
import { useDispatch, useSelector } from 'react-redux'
import {
clearCart,
loadCart,
selectCartItemCount,
selectCartItems,
selectCartSubtotal,
} from '../../../redux-store/CartStore/ducs'
const TabItem = ({ label, active, complete, onClick, reference, stepNumber }) => (
<Box
ref={reference}
onClick={onClick}
sx={{
minWidth: '256px',
display: 'flex',
alignItems: 'center',
pb: '1rem',
cursor: 'pointer',
borderBottom: complete ? '2px solid #45B26B' : active ? '2px solid black' : '',
}}
>
<IconButton
sx={{
background: complete ? '#45B26B' : active ? '#000' : '#B1B5C3',
marginRight: '1rem',
}}
>
{complete ? (
<CheckIcon sx={{ color: 'white' }} />
) : (
<Typography
color={active ? 'white' : complete ? '#45B26B' : '#FCFCFD'}
width={30}
height={30}
borderRadius={'50%'}
>
{stepNumber}
</Typography>
)}
</IconButton>
<Typography
sx={{
fontFamily: 'inter',
fontSize: '1rem',
fontWeight: 'bold',
color: complete ? '#45B26B' : active ? 'black' : '#B1B5C3',
}}
>
{label}
</Typography>
</Box>
)
TabItem.propTypes = {
label: PropTypes.string.isRequired,
active: PropTypes.bool.isRequired,
complete: PropTypes.bool.isRequired,
onClick: PropTypes.func.isRequired,
reference: PropTypes.object.isRequired,
stepNumber: PropTypes.number.isRequired,
}
const styles = {
tab: {
backgroundColor: '#FFFFFF',
color: 'black',
padding: '0px 25px',
textAlign: 'center',
textTransform: 'none',
textDecoration: 'none',
minWidth: 50,
'&.Mui-selected': {
backgroundColor: '#007FFF',
border: 'none',
color: 'white',
textAlign: 'center',
textDecoration: 'none',
},
},
tabs: {
// maxWidth: 355,
borderRadius: 20,
maxHeight: 50,
'&.MuiTabs-root': {
minHeight: 0,
},
},
}
const Cart = () => {
const [shipTo, setShipTo] = useState('')
const [billTo, setBillTo] = useState('')
const dispatch = useDispatch()
const [paymentMode, setPaymentMode] = useState('')
const cartItems = useSelector(selectCartItems)
const totalItemCount = useSelector(selectCartItemCount)
const cartSubtotal = useSelector(selectCartSubtotal)
const [value, setValue] = useState(0)
const handleTabChange = (event, newValue) => {
console.log(newValue)
if (value === 3 && newValue !== 3) {
setPaymentMode('')
setBillTo('')
setShipTo('')
dispatch(clearCart())
}
if (newValue === 1 && cartItems.length === 0) {
Swal.fire('Cart is emplty ,can not move to next screen pls add items to cart ')
return
}
if (newValue === 2 && (shipTo === '' || billTo === '' || paymentMode === '')) {
Swal.fire('Fill all the details ,can not move to next screen ')
return
}
if (newValue === 3 && (shipTo === '' || billTo === '' || paymentMode === '')) {
Swal.fire('Fill all the details ,can not move to next screen ')
return
}
setValue(newValue)
}
// const handleAddressAndOrderClick = () => {
// if (cartItems.length === 0) {
// Swal.fire('Cart is emplty can not move to next screen pls add items to cart ')
// return
// }
// setValue(1)
// }
const [orderId, setOrderId] = useState(null)
// const [cartItem, setCartItem] = useState([
// {
// product: {
// _id: '1',
// name: 'Product 1',
// image: [
// {
// url: 'https://images.pexels.com/photos/341523/pexels-photo-341523.jpeg?auto=compress&cs=tinysrgb&w=600',
// },
// ],
// variant: { price: 100, gst_Id: { tax: 5 } },
// hsn: '1234',
// },
// quantity: 2,
// subtotal: 200,
// },
// {
// product: {
// _id: '2',
// name: 'Product 2',
// image: [
// {
// url: 'https://images.pexels.com/photos/341523/pexels-photo-341523.jpeg?auto=compress&cs=tinysrgb&w=600',
// },
// ],
// variant: { price: 150, gst_Id: { tax: 5 } },
// hsn: '5678',
// },
// quantity: 1,
// subtotal: 150,
// },
// ])
return (
<Container>
<Tabs
value={value}
indicatorColor="primary"
textColor="primary"
onChange={handleTabChange}
TabIndicatorProps={{
style: {
backgroundColor: 'white',
display: 'none',
},
}}
sx={styles.tabs}
>
<Tab label="Cart " value={0} sx={styles.tab} />
<Tab label="Address and Payment Mode" value={1} sx={styles.tab} />
<Tab label="Review Order" value={2} sx={styles.tab} />
<Tab label="Order Confirmation" value={3} sx={styles.tab} />
</Tabs>
{value === 0 &&
(cartItems.length === 0 ? (
<Typography sx={{ m: '2rem ', background: '#FFF', padding: '1rem' }}>
No items available. Please add items to the cart.
</Typography>
) : (
<Box>
<ShopingCart cartItems={cartItems} handleTabChange={handleTabChange} />
</Box>
))}
{value === 1 && (
<Box>
<AddressAndPayment
billTo={billTo}
setBillTo={setBillTo}
shipTo={shipTo}
setShipTo={setShipTo}
paymentMode={paymentMode}
setPaymentMode={setPaymentMode}
handleTabChange={handleTabChange}
/>
</Box>
)}
{value === 2 && (
<Box>
<ReviewOrder
billTo={billTo}
shipTo={shipTo}
paymentMode={paymentMode}
orderId={orderId}
setOrderId={setOrderId}
cartItem={cartItems}
handleTabChange={handleTabChange}
/>
</Box>
)}
{value === 3 && (
<Box>
<OrderConfirmation
billTo={billTo}
shipTo={shipTo}
paymentMode={paymentMode}
orderId={orderId}
cartItem={cartItems}
/>
</Box>
)}
</Container>
)
}
export default Cart

View File

@ -0,0 +1,159 @@
import {
Box,
Container,
Grid,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Typography,
} from '@mui/material'
import React, { useEffect, useState } from 'react'
import { useSelector } from 'react-redux'
import { selectCartSubtotal } from '../../../redux-store/CartStore/ducs'
import { isAutheticated } from '../../../auth'
import Axios from '../../../axios'
const OrderConfirmation = ({ orderId, billTo, shipTo, paymentMode, cartItem }) => {
const subtotal = useSelector(selectCartSubtotal)
const [ownerDetails, setOwnerDetails] = useState()
const token = isAutheticated()
const getData = async () => {
let res = await Axios.get(`/api/v1/user/details`, {
headers: {
Authorization: `Bearer ${token}`,
},
})
if (res.data.success) {
setOwnerDetails({ ...res.data.user })
}
}
useEffect(() => {
getData()
}, [])
// Calculate total GST for the entire cart
const totalGST = cartItem.reduce((acc, item) => {
// console.log(item)
const gstAmount = (item.price * item.GST) / 100
return acc + gstAmount * item.count
}, 0)
return (
<Box>
<Box sx={{ my: '2rem', background: '#fff', padding: '1rem', borderRadius: '0.8rem' }}>
<Typography variant="h4" textAlign={'center'} marginBottom={2}>
OrderId : {orderId}
</Typography>
<Grid container spacing={2}>
<Grid item sm={6} md={6} lg={6}>
<Typography variant="h5" sx={{ mb: '0.5rem' }}>
Bill Address
</Typography>
<Typography sx={{ mb: '0.5rem' }}>{billTo}</Typography>
</Grid>
<Grid item sm={6} md={6} lg={6}>
<Typography variant="h5" sx={{ mb: '0.5rem' }}>
Ship Address
</Typography>
<Typography sx={{ mb: '0.5rem' }}>{shipTo}</Typography>
</Grid>
<Grid item sm={6} md={6} lg={6}>
<Typography variant="h5" sx={{ mb: '0.5rem' }}>
Payment mode : <strong>{paymentMode}</strong>
</Typography>
</Grid>
<Grid item sm={12} md={12} lg={12}>
<Typography variant="h5" sx={{ mb: '0.5rem' }}>
SBU: <strong>{ownerDetails?.SBU}</strong>
</Typography>
</Grid>
</Grid>
</Box>
<Box my={8}>
<Grid container spacing={5}>
<Grid item xs={12}>
<Box>
<TableContainer
component={Paper}
elevation={0}
sx={{ borderBottom: '1.5px solid #CFCFD5', borderRadius: 0 }}
>
<Table sx={{ minWidth: 650 }} size="large">
<TableHead>
<TableRow>
<TableCell sx={{ fontWeight: 'bold' }}>Product</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Category</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Brand</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>SKU</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Quantity</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Price</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>GST</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Subtotal</TableCell>
</TableRow>
</TableHead>
<TableBody>
{cartItem.map((row, index) => {
const gstAmount = (row.price * row.GST) / 100
return (
<TableRow
key={index}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell>
<Box sx={{ display: 'flex' }}>
{/* <img
src={row.product.image[0].url}
alt={row.product.name}
style={{ width: '100px', height: '70px', marginRight: '1rem' }}
/> */}
<Box>
<Typography fontWeight={600}>{row.name}</Typography>
</Box>
</Box>
</TableCell>
<TableCell>{row.category.categoryName}</TableCell>
<TableCell>{row.brand.brandName}</TableCell>
<TableCell>{row.SKU}</TableCell>
<TableCell>{row.count}</TableCell>
<TableCell>{row.price}</TableCell>
<TableCell>{gstAmount}</TableCell>
<TableCell>{row.price * row.count}</TableCell>
</TableRow>
)
})}
<TableRow>
<TableCell colSpan={7} />
<TableCell>
Subtotal:<strong> {subtotal}</strong>
</TableCell>
</TableRow>
<TableRow>
<TableCell colSpan={7} />
<TableCell>
Total GST:<strong> {totalGST} </strong>
</TableCell>
</TableRow>
<TableRow>
<TableCell colSpan={7} />
<TableCell>
Grand Total: <strong> {subtotal + totalGST}</strong>
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Box>
</Grid>
</Grid>
</Box>
</Box>
)
}
export default OrderConfirmation

View File

@ -0,0 +1,196 @@
import {
Box,
Button,
Container,
Grid,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Typography,
} from '@mui/material'
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { clearCart, selectCartSubtotal } from '../../../redux-store/CartStore/ducs'
import Axios from '../../../axios'
import { isAutheticated } from '../../../auth'
import Swal from 'sweetalert2'
const ReviewOrder = ({
orderId,
billTo,
shipTo,
paymentMode,
cartItem,
handleTabChange,
setOrderId,
}) => {
const subtotal = useSelector(selectCartSubtotal)
const token = isAutheticated()
const dispatch = useDispatch()
// Calculate total GST for the entire cart
const totalGST = cartItem.reduce((acc, item) => {
// console.log(item)
const gstAmount = (item.price * item.GST) / 100
return acc + gstAmount * item.count
}, 0)
const handleConfirmOrder = async (e) => {
e.preventDefault()
if (!billTo || !shipTo || !paymentMode || !cartItem || !subtotal || !totalGST) {
Swal.fire('All fields are required ')
}
try {
const res = await Axios.post(
'/api/order-place',
{
billTo,
shipTo,
paymentMode,
grandTotal: subtotal + totalGST,
gstTotal: totalGST,
subtotal: subtotal,
orderItems: cartItem,
},
{
headers: {
'Access-Control-Allow-Origin': '*',
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
},
)
console.log(res)
if (res.status === 201) {
setOrderId(res?.data?.placedOrder?.uniqueId)
console.log(res)
Swal.fire('Success!', 'Your order has been placed successfully.', 'success')
handleTabChange(e, 3)
}
} catch (error) {
Swal.fire('Something went wrong', error.message, 'error')
}
}
console.log('cartitems', cartItem)
return (
<Box>
<Box sx={{ my: '2rem', background: '#fff', padding: '1rem', borderRadius: '0.8rem' }}>
<Grid container spacing={2}>
<Grid item sm={6} md={6} lg={6}>
<Typography variant="h5" sx={{ mb: '0.5rem' }}>
Bill Address
</Typography>
<Typography sx={{ mb: '0.5rem' }}>{billTo}</Typography>
</Grid>
<Grid item sm={6} md={6} lg={6}>
<Typography variant="h5" sx={{ mb: '0.5rem' }}>
Ship Address
</Typography>
<Typography sx={{ mb: '0.5rem' }}>{shipTo}</Typography>
</Grid>
<Grid item sm={6} md={6} lg={6}>
<Typography variant="h5" sx={{ mb: '0.5rem' }}>
Payment mode : <strong>{paymentMode}</strong>
</Typography>
</Grid>
</Grid>
</Box>
<Box my={4}>
<Grid container spacing={5}>
<Grid item xs={12}>
<Box>
<TableContainer
component={Paper}
elevation={0}
sx={{ borderBottom: '1.5px solid #CFCFD5', borderRadius: 0 }}
>
<Table sx={{ minWidth: 650 }} size="large">
<TableHead>
<TableRow>
<TableCell sx={{ fontWeight: 'bold' }}>Product</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Category</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Brand</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>SKU</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Quantity</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Price</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>GST</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Subtotal</TableCell>
</TableRow>
</TableHead>
<TableBody>
{cartItem.map((row, index) => {
const gstAmount = (row.price * row.GST) / 100
return (
<TableRow
key={index}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell>
<Box sx={{ display: 'flex' }}>
{/* <img
src={row.product.image[0].url}
alt={row.product.name}
style={{ width: '100px', height: '70px', marginRight: '1rem' }}
/> */}
<Box>
<Typography fontWeight={600}>{row.name}</Typography>
</Box>
</Box>
</TableCell>
<TableCell>{row.category.categoryName}</TableCell>
<TableCell>{row.brand.brandName}</TableCell>
<TableCell>{row.SKU}</TableCell>
<TableCell>{row.count}</TableCell>
<TableCell>{row.price}</TableCell>
<TableCell>{gstAmount}</TableCell>
<TableCell>{row.price * row.count}</TableCell>
</TableRow>
)
})}
<TableRow>
<TableCell colSpan={7} />
<TableCell>
Subtotal:<strong> {subtotal}</strong>
</TableCell>
</TableRow>
<TableRow>
<TableCell colSpan={7} />
<TableCell>
Total GST:<strong> {totalGST} </strong>
</TableCell>
</TableRow>
<TableRow>
<TableCell colSpan={7} />
<TableCell>
Grand Total: <strong> {subtotal + totalGST}</strong>
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Box>
</Grid>
</Grid>
<Box display={'flex'} justifyContent={'center'} mt={4} gap={10}>
<Button variant="contained" color="primary" onClick={(e) => handleTabChange(e, 0)}>
Update Order
</Button>
<Button variant="contained" color="success" onClick={handleConfirmOrder}>
Confirm Order
</Button>
</Box>
</Box>
</Box>
)
}
export default ReviewOrder

View File

@ -0,0 +1,191 @@
import React from 'react'
import {
Box,
Container,
Grid,
Paper,
Typography,
Button,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
useMediaQuery,
} from '@mui/material'
import ClearIcon from '@mui/icons-material/Clear'
import { useDispatch, useSelector } from 'react-redux'
import {
selectCartItems,
selectCartSubtotal,
increaseCount,
decreaseCount,
removeFromCart,
selectCartItemCount,
} from '../../../redux-store/CartStore/ducs' // Adjust this path as per your project structure
const ShoppingCart = ({ handleTabChange }) => {
const matches = useMediaQuery('(min-width:1200px)')
const dispatch = useDispatch()
// Fetching cart items and subtotal from the Redux store
const cartItems = useSelector(selectCartItems)
const subtotal = useSelector(selectCartSubtotal)
console.log(cartItems)
// Calculate total GST for the entire cart
const totalGST = cartItems.reduce((acc, item) => {
// console.log(item)
const gstAmount = (item.price * item.GST) / 100
return acc + gstAmount * item.count
}, 0)
const handleDecrease = (productId) => {
dispatch(decreaseCount(productId))
}
const handleIncrease = (productId) => {
dispatch(increaseCount(productId))
}
const removeCartItemHandler = (productId) => {
dispatch(removeFromCart(productId))
}
return (
<Box>
<Container>
<Box my={8}>
<Grid container spacing={5}>
<Grid item xs={12}>
<Box>
<TableContainer
component={Paper}
elevation={0}
sx={{ borderBottom: '1.5px solid #CFCFD5', borderRadius: 0 }}
>
<Table sx={{ minWidth: 650 }} size="large">
<TableHead>
<TableRow>
<TableCell sx={{ fontWeight: 'bold' }}>Product</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>SKU</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Quantity</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Price</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>GST</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Subtotal</TableCell>
</TableRow>
</TableHead>
<TableBody>
{cartItems.map((row, index) => {
const gstAmount = (row.price * row.GST) / 100
return (
<TableRow
key={index}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell>
<Box sx={{ display: 'flex' }}>
{/* <img
src={row.product.image[0].url}
alt={row.product.name}
style={{ width: '100px', height: '70px', marginRight: '1rem' }}
/> */}
<Box>
<Typography fontWeight={600}>{row.name}</Typography>
<Box
onClick={() => removeCartItemHandler(row._id)}
sx={{
display: 'flex',
alignItems: 'center',
cursor: 'pointer',
color: '#6C7275',
marginTop: '0.5rem',
}}
>
<ClearIcon fontSize="small" />
<Typography>Remove</Typography>
</Box>
</Box>
</Box>
</TableCell>
<TableCell>{row.SKU}</TableCell>
<TableCell>
<Box
sx={{
display: 'flex',
alignItems: 'center',
border: '1px solid #6C7275',
borderRadius: '4px',
width: '80px',
justifyContent: 'space-between',
}}
>
<Typography
onClick={() => handleDecrease(row._id)}
sx={{ cursor: 'pointer', padding: '0.5rem' }}
>
-
</Typography>
<Typography>{row.count}</Typography>
<Typography
onClick={() => handleIncrease(row._id)}
sx={{ cursor: 'pointer', padding: '0.5rem' }}
>
+
</Typography>
</Box>
</TableCell>
<TableCell>{row.price}</TableCell>
<TableCell>{gstAmount}</TableCell>
<TableCell>{row.price * row.count}</TableCell>
</TableRow>
)
})}
<TableRow>
<TableCell colSpan={5} />
<TableCell>
Subtotal:<strong> {subtotal}</strong>
</TableCell>
</TableRow>
<TableRow>
<TableCell colSpan={5} />
<TableCell>
Total GST:<strong> {totalGST} </strong>
</TableCell>
</TableRow>
<TableRow>
<TableCell colSpan={5} />
<TableCell>
Grand total: <strong> {subtotal + totalGST}</strong>
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Box>
</Grid>
</Grid>
</Box>
</Container>
<Box sx={{ display: 'flex', justifyContent: 'center', my: 4 }}>
<Button
variant="contained"
size="large"
color="primary"
onClick={(e) => handleTabChange(e, 1)}
sx={{
width: '200px',
}}
>
Address and payment mode
</Button>
</Box>
</Box>
)
}
export default ShoppingCart

View File

@ -19,10 +19,13 @@ import { cilLockLocked, cilUser } from '@coreui/icons'
// import axios from 'axios'
import Axios from '../../../axios'
import Swal from 'sweetalert2'
import { clearCart } from '../../../redux-store/CartStore/ducs'
import { useDispatch } from 'react-redux'
const Login = () => {
const [loading, setLoading] = useState(false)
const [validForm, setValidForm] = useState(false)
const dispatch = useDispatch()
const navigate = useNavigate()
const [auth, setAuth] = useState({
email: '',
@ -95,27 +98,26 @@ const Login = () => {
if (!(auth.email && auth.password)) {
return Swal.fire('Error!', 'All fields are required', 'error')
}
setLoading({ loading: true })
setLoading(true)
try {
const res = await Axios.post('/api/v1/user/login/', auth)
console.log(res)
if (res.data.success == true && res.data.user.role === 'principal-Distributor') {
if (res.data.success === true && res.data.user.role === 'principal-Distributor') {
localStorage.setItem('authToken', res.data.token)
dispatch(clearCart())
navigate('/dashboard')
setLoading(false)
Swal.fire('success', 'logged in successfuly ', 'success')
// console.log(response.data)
} else if (res.data.success == true && res.data.user.role !== 'principal-Distributor') {
Swal.fire('success', 'logged in successfully ', 'success')
} else if (res.data.success === true && res.data.user.role !== 'principal-Distributor') {
setLoading(false)
Swal.fire('error', 'Please login through the PD Credentials ', 'error')
}
} catch (error) {
setLoading(false)
Swal.fire('Error!', 'Invalid Credentials', 'error')
}
}
return (
<div className="bg-body-tertiary min-vh-100 d-flex flex-row align-items-center">
<CContainer>

View File

@ -0,0 +1,117 @@
import React, { useEffect, useState, useRef } from 'react'
import Axios from '../../../axios'
import { isAutheticated } from '../../../auth'
import {
Box,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
Button,
TextField,
CircularProgress,
Pagination,
} from '@mui/material'
import { Link } from 'react-router-dom'
const ProductManual = () => {
const [loading, setLoading] = useState(true)
const token = isAutheticated()
const [productManuals, setProductManuals] = useState([])
const [currentPage, setCurrentPage] = useState(1)
const [itemsPerPage, setItemsPerPage] = useState(10)
const [totalData, setTotalData] = useState(0)
const titleRef = useRef('')
const getProductManuals = async () => {
try {
setLoading(true)
const response = await Axios.get(`/api/productmanual/getall`, {
headers: { Authorization: `Bearer ${token}` },
params: {
page: currentPage,
show: itemsPerPage,
title: titleRef.current.value || '',
},
})
if (response.status === 200) {
const { productManuals, total } = response.data
setProductManuals(productManuals)
setTotalData(total)
}
} catch (error) {
console.error('Failed to fetch product manuals:', error)
} finally {
setLoading(false)
}
}
useEffect(() => {
getProductManuals()
}, [currentPage, itemsPerPage])
const handleFilterChange = () => {
setCurrentPage(1)
getProductManuals()
}
const handlePageChange = (event, page) => {
setCurrentPage(page)
}
return (
<Box sx={{ padding: 2 }}>
{/* Filter Text Field */}
{/* Table and Pagination */}
{loading ? (
<CircularProgress />
) : (
<>
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell sx={{ fontWeight: 'bold' }}>Name</TableCell>
<TableCell sx={{ fontWeight: 'bold' }}>Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
{productManuals.map((manual) => (
<TableRow key={manual._id}>
<TableCell>{manual.title}</TableCell>
<TableCell>
<Button
variant="contained"
color="primary"
component={Link}
to={`/product-manual/${manual._id}`}
>
View
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
{/* Pagination */}
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 2 }}>
<Pagination
count={Math.ceil(totalData / itemsPerPage)}
page={currentPage}
onChange={handlePageChange}
color="primary"
/>
</Box>
</>
)}
</Box>
)
}
export default ProductManual

View File

@ -0,0 +1,107 @@
import React, { useEffect, useState } from 'react'
import Button from '@mui/material/Button' // Updated import for MUI v5
import { Link, useParams } from 'react-router-dom'
import Swal from 'sweetalert2' // Import SweetAlert2
import axios from 'axios'
import Axios from '../../../axios'
import { isAutheticated } from '../../../auth'
const ViewProductManual = () => {
const [title, setTitle] = useState('')
const [image, setImage] = useState('')
const token = isAutheticated()
const { id } = useParams()
const getproductmanual = async () => {
try {
const res = await Axios.get(`/api/productmanual/getone/${id}`, {
headers: { Authorization: `Bearer ${token}` },
})
setTitle(res?.data?.productManual?.title)
setImage(res?.data?.productManual?.product_manual)
} catch (err) {
console.error(err)
// SweetAlert2 configuration
Swal.fire({
title: 'Error',
text: 'Unable to fetch the product manual',
icon: 'error',
confirmButtonText: 'Retry',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
cancelButtonText: 'Cancel',
})
}
}
useEffect(() => {
getproductmanual()
}, [])
return (
<div className="container">
<div className="row">
<div className="col-12">
<div className="page-title-box d-flex align-items-center justify-content-between">
<div style={{ fontSize: '22px' }} className="fw-bold">
View Product Manual
</div>
<div style={{ display: 'flex', gap: '1rem' }}>
<h4 className="mb-0"></h4>
</div>
<div className="page-title-right">
<Link to="/product-manual">
<Button
variant="contained"
color="secondary"
sx={{
fontWeight: 'bold',
mb: 1,
textTransform: 'capitalize',
}}
>
Back
</Button>
</Link>
</div>
</div>
</div>
</div>
<div className="row">
<div className="col-lg-12 col-md-12 col-sm-12 my-1">
<div className="card h-100">
<div className="card-body px-5">
<h4
className="card-title"
style={{
fontWeight: 'bold',
fontSize: '3rem',
marginBottom: '1rem',
textTransform: 'capitalize',
}}
>
{title}
</h4>
<div className="mb-3">
{image &&
(image.url.endsWith('.pdf') ? (
<iframe
src={image.url}
title="Product Manual"
style={{ width: '100%', height: '80vh', border: 'none' }}
/>
) : (
<img src={image.url} alt="blog" style={{ width: '100%', height: '50vh' }} />
))}
</div>
</div>
</div>
</div>
</div>
</div>
)
}
export default ViewProductManual

View File

@ -1,59 +1,138 @@
import { useEffect, useRef, useState } from 'react'
import { useDispatch } from 'react-redux'
import Swal from 'sweetalert2'
import Axios from '../../axios'
import {
Box,
Card,
CircularProgress,
Container,
FormControl,
Grid,
MenuItem,
Pagination,
Select,
Typography,
} from '@mui/material'
import cardData from '../../assets/data/cardjson.json'
import React, { useState } from 'react'
import ShopCard from './shopCard'
const Shop = () => {
const [option, setOption] = useState('all')
const [products, setProducts] = useState([])
const [categories, setCategories] = useState([])
const [loading, setLoading] = useState(true)
const [currentPage, setCurrentPage] = useState(1)
const [totalData, setTotalData] = useState(0)
const itemsPerPage = 10 // Adjust this according to your requirements
const nameRef = useRef('')
const categoryRef = useRef('')
const dispatch = useDispatch()
const handleChange = (event) => {
setOption(event.target.value)
const selectedValue = event.target.value
setOption(selectedValue)
categoryRef.current = selectedValue === 'all' ? '' : selectedValue // Set to an empty string if "All" is selected
setCurrentPage(1) // Reset to first page when filter changes
}
const filteredData =
option === 'all' ? cardData : cardData.filter((item) => item.categoryName === option)
const getCategories = async () => {
try {
const response = await Axios.get('/api/category/getCategories')
setCategories(response.data?.categories || []) // Assuming the response has a categories property
} catch (err) {
Swal.fire({
title: 'Error',
text: 'Failed to fetch categories',
icon: 'error',
button: 'Retry',
dangerMode: true,
})
}
}
const getProductsData = async () => {
setLoading(true)
try {
const response = await Axios.get('/api/product/getAll/user/', {
params: {
page: currentPage,
show: itemsPerPage,
name: nameRef.current || '',
category: categoryRef.current || '', // Send category only if it's not empty
},
})
setProducts(response.data?.products || [])
setTotalData(response.data?.total_data || 0)
} catch (err) {
const msg = err?.response?.data?.msg || 'Something went wrong!'
Swal.fire({
title: 'Error',
text: msg,
icon: 'error',
button: 'Retry',
dangerMode: true,
})
} finally {
setLoading(false)
}
}
useEffect(() => {
getCategories() // Fetch categories on component mount
getProductsData()
}, [currentPage, option])
const handlePageChange = (event, value) => {
setCurrentPage(value)
}
return (
<>
<Container>
<Typography sx={{ fontWeight: 'bold' }} variant="h4">
Categories
</Typography>
<Container>
<Typography sx={{ fontWeight: 'bold' }} variant="h4">
Categories
</Typography>
<FormControl sx={{ width: '400px', mt: '1rem' }}>
<Select
sx={{ width: '400px', mt: '1rem' }}
labelId="demo-simple-select-label"
id="demo-simple-select"
labelId="category-select-label"
id="category-select"
value={option}
label=""
onChange={handleChange}
>
<MenuItem value="all">All</MenuItem>
<MenuItem value="camera">Camera</MenuItem>
<MenuItem value="phone">Phone</MenuItem>
{categories.map((category) => (
<MenuItem key={category._id} value={category.categoryName}>
{category.categoryName}
</MenuItem>
))}
</Select>
</FormControl>
<Box mt={3}>
{/* Cards */}
<Grid container spacing={2}>
{filteredData.map((item, i) => (
<Grid key={i} item xs={12} sm={6} md={4} lg={4}>
<ShopCard item={item} />
</Grid>
))}
</Grid>
</Box>
</Container>
</>
<Box mt={3}>
{loading ? (
<CircularProgress />
) : (
<>
<Grid container spacing={2}>
{products.map((item, i) => (
<Grid key={i} item xs={12} sm={6} md={4} lg={4}>
<ShopCard item={item} />
</Grid>
))}
</Grid>
<Box mt={3} display="flex" justifyContent="center">
<Pagination
count={Math.ceil(totalData / itemsPerPage)}
page={currentPage}
onChange={handlePageChange}
color="primary"
/>
</Box>
</>
)}
</Box>
</Container>
)
}

View File

@ -1,9 +1,21 @@
/* eslint-disable react/prop-types */
import { Button, Card, CardContent, CardMedia, Typography } from '@mui/material'
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { addToCart, selectIsProductInCart } from '../../redux-store/CartStore/ducs'
import Swal from 'sweetalert2'
// eslint-disable-next-line react/prop-types
const ShopCard = ({ item }) => {
const dispatch = useDispatch()
const isProductInCart = useSelector(selectIsProductInCart(item._id))
const handleAddToCart = () => {
if (!isProductInCart) {
dispatch(addToCart(item))
Swal.fire('Product added to cart ')
}
}
return (
<div>
<Card>
@ -19,10 +31,11 @@ const ShopCard = ({ item }) => {
variant="contained"
color="primary"
fullWidth
// onClick={handleAddToCart}
disabled={isProductInCart}
onClick={handleAddToCart}
sx={{ marginTop: '10px' }}
>
Add to Cart
{isProductInCart ? 'Already in Cart' : 'Add to Cart'}
</Button>
</CardContent>
</Card>