initial commit

This commit is contained in:
npalOmega 2025-03-09 19:45:02 -05:00
parent 17f3ec643d
commit 41d7c99226
137 changed files with 31605 additions and 15633 deletions

3
jsconfig.json Normal file
View file

@ -0,0 +1,3 @@
{
"compilerOptions": { "baseUrl": "src" }
}

41545
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -2,25 +2,28 @@
"name": "shoc-ui",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
"devDependencies": {
"@emotion/react": "^11.7.1",
"@emotion/styled": "^11.6.0",
"@mui/icons-material": "^5.2.5",
"@mui/material": "^5.2.5",
"@mui/styles": "^5.2.3",
"@mui/x-data-grid": "^5.2.1",
"http-proxy-middleware": "^3.0.3",
"mui-datatables": "^4.2.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "^6.3.0",
"react-scripts": "4.0.3"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"clean": "rimraf --glob node_modules && rimraf package-lock.json"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
"react-app"
]
},
"browserslist": {
@ -34,5 +37,10 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"dependencies": {
"chart.js": "^3.9.1",
"moment": "^2.30.1",
"react-chartjs-2": "^3.3.0"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -1,43 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
<title>Sea Haven Industries</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View file

@ -1,25 +0,0 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View file

@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View file

@ -1,38 +0,0 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View file

@ -1,25 +0,0 @@
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;

View file

@ -1,8 +0,0 @@
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

45
src/Site.Layout.js Normal file
View file

@ -0,0 +1,45 @@
import React from "react";
import { Container } from "@mui/material";
import { ThemeProvider, createTheme } from "@mui/material/styles";
import "./site.css";
import TopNav from "./components/TopNav";
import LeftNav from "./components/LeftNav";
import Main from "./components/Main";
const THEME = createTheme({
typography: {
fontFamily: `"Dosis", "Nunito", sans-serif`,
fontSize: 14,
fontWeightLight: 300,
fontWeightRegular: 400,
fontWeightMedium: 500,
},
palette: {
header: {
main: "#4D81B7",
contrastText: "header background",
},
},
flexBox: {
display: "flex",
},
});
const Layout = (props) => {
const [open, setOpen] = React.useState(false);
const handleDrawerOpen = () => {
setOpen(true);
};
const handleDrawerClose = () => {
setOpen(false);
};
return (
<ThemeProvider theme={THEME}>
<TopNav handleDrawerOpen={handleDrawerOpen}/>
<LeftNav handleDrawerClose= {handleDrawerClose} open={open}/>
<Main open={open} maxWidth="lg">{props.children}</Main>
</ThemeProvider>
)};
export default Layout;

62
src/apiUtil.js Normal file
View file

@ -0,0 +1,62 @@
import { API_ERROR_MESSAGE } from "./constants";
import { getAccessToken, setToken } from "./tokenUtility";
const STANDARD_HEADERS = {
Accept: "application/json",
"Content-Type": "application/json",
pragma: "no-cache",
"cache-control": "no-cache",
Authorization: getAccessToken()
};
export const get = (url) =>
fetch(url, {
headers: { ...STANDARD_HEADERS, Authorization: getAccessToken() },
method: "GET",
})
.then(handleHttpResponse)
.catch((ex) => {
throw new Error(ex.message || API_ERROR_MESSAGE);
});
export const post = (url, payload) =>
fetch(url, {
headers: STANDARD_HEADERS,
method: "POST",
body: JSON.stringify(payload),
})
.then(handleHttpResponse)
.catch((ex) => {
throw new Error(ex.message || API_ERROR_MESSAGE);
});
export const put = (url, payload) =>
fetch(url, {
headers: STANDARD_HEADERS,
method: "PUT",
body: JSON.stringify(payload),
})
.then(handleHttpResponse)
.catch((ex) => {
throw new Error(ex.message || API_ERROR_MESSAGE);
});
export const deleteItem = (url, payload) =>
fetch(url, {
headers: STANDARD_HEADERS,
method: "Delete",
body: JSON.stringify(payload),
})
.then(handleHttpResponse)
.catch((ex) => {
throw new Error(ex.message || API_ERROR_MESSAGE);
});
const handleHttpResponse = (response) => {
if (response.ok)
return response.json();
else if (response.status === 401) {
throw new Error("You are not authorized to access this page.");
}
throw new Error(API_ERROR_MESSAGE);
};

37
src/components/Banner.js Normal file
View file

@ -0,0 +1,37 @@
import React from "react";
import Alert from "@mui/material/Alert";
import Stack from "@mui/material/Stack";
export default function Banner({ apiStatus, setApiStatus }) {
React.useEffect(() => {
if (apiStatus.show) {
let timeOutId = setTimeout(
() =>
setApiStatus({
show: false,
status: "",
message: "",
}),
3000
);
return () => {
if (timeOutId) {
clearInterval(timeOutId);
}
};
}
}, [apiStatus]);
return (
apiStatus.show && (
<Stack sx={{ width: "100%" }} spacing={2}>
{apiStatus.status == "failed" && (
<Alert severity="error">{apiStatus.message}</Alert>
)}
{apiStatus.status == "success" && (
<Alert severity="success">{apiStatus.message}</Alert>
)}
</Stack>
)
);
}

View file

@ -0,0 +1,13 @@
import { makeStyles } from "@mui/styles";
const ListHeaderStyles = makeStyles((theme) => ({
box: {
display: "flex",
flexDirection: "column",
},
initialFont: {
fontFamily: "initial!important",
},
}));
export default ListHeaderStyles;

View file

@ -0,0 +1,33 @@
import React from "react";
import Typography from "@mui/material/Typography";
import { Button, Box } from "@mui/material";
import EmptyListStyles from "./EmptyList.Styles";
const EmptyList = ({ handleClickFormModalOpen, isEmpty }) => {
const classes = EmptyListStyles();
return (
isEmpty && (
<Box
border={1}
borderColor="gray"
height={400}
width={400}
m={1}
justifyContent="center"
alignItems="center"
className={classes.box}
>
<Typography variant="h6" component="h6">
Your list is empty <span className={classes.initialFont}>:(</span>
</Typography>
<Button
variant="contained"
color="primary"
onClick={handleClickFormModalOpen}
>
Add your first Item
</Button>
</Box>
)
);
};
export default EmptyList;

View file

@ -0,0 +1,13 @@
import { makeStyles } from "@mui/styles";
const HeaderStyles = makeStyles((theme) => ({
header: {
background: theme.palette.header.main,
marginBottom: 30
},
title: {
fontSize: '30px'
}
}));
export default HeaderStyles;

20
src/components/Header.js Normal file
View file

@ -0,0 +1,20 @@
import React from "react";
import { AppBar, Typography, Toolbar } from "@mui/material";
import HeaderStyles from "./Header.Styles";
const Header = () => {
const classes = HeaderStyles();
return (
<div className={classes.header}>
<AppBar color="primary" position="static">
<Toolbar>
<Typography className={classes.title} variant="title" color="inherit">
Sea Haven
</Typography>
</Toolbar>
</AppBar>
</div>
);
};
export default Header;

145
src/components/LeftNav.js Normal file
View file

@ -0,0 +1,145 @@
import * as React from 'react';
import { styled, useTheme } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Drawer from '@mui/material/Drawer';
import List from '@mui/material/List';
import Typography from '@mui/material/Typography';
import Divider from '@mui/material/Divider';
import IconButton from '@mui/material/IconButton';
import MenuIcon from '@mui/icons-material/Menu';
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import ListItem from '@mui/material/ListItem';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import MonetizationOnIcon from '@mui/icons-material/MonetizationOn';
import PersonIcon from '@mui/icons-material/Person';
import TextSnippetIcon from '@mui/icons-material/TextSnippet';
import { makeStyles } from "@mui/styles";
import { Link } from "react-router-dom";
import { useLocation } from 'react-router-dom';
const drawerWidth = 240;
const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })(({ theme }) => ({
flexGrow: 1,
padding: theme.spacing(3),
transition: theme.transitions.create('margin', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
marginLeft: `-${drawerWidth}px`,
variants: [
{
props: ({ open }) => open,
style: {
transition: theme.transitions.create('margin', {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen,
}),
marginLeft: 0,
},
},
],
}));
const NavStyles = makeStyles((theme) => ({
header: {
borderBottom: "2px solid rgba(0,0,0,0.1)",
display: "flex",
},
title:{
fontWeight: "600!important",
fontSize: "1.25rem!important",
}
}));
const DrawerHeader = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
padding: theme.spacing(0, 1),
background: 'rgb(246 248 249)',
// necessary for content to be below app bar
...theme.mixins.toolbar,
justifyContent: 'flex-end',
}));
export default function PersistentDrawerLeft({ handleDrawerClose, open}) {
const location = useLocation();
const theme = useTheme();
return (
<Drawer
sx={{
width: drawerWidth,
flexShrink: 0,
'& .MuiDrawer-paper': {
width: drawerWidth,
boxSizing: 'border-box',
},
}}
variant="persistent"
anchor="left"
open={open}
>
<DrawerHeader>
<IconButton onClick={handleDrawerClose}>
{theme.direction === 'ltr' ? <ChevronLeftIcon /> : <ChevronRightIcon />}
</IconButton>
</DrawerHeader>
<Divider />
<List>
<ListItem key={'Dashboard'} disablePadding component={Link} to={'dashboard'} selected={location.pathname=='/dashboard'}>
<ListItemButton>
<ListItemIcon>
<PersonIcon />
</ListItemIcon>
<ListItemText primary={'Dashboard'} />
</ListItemButton>
</ListItem>
<ListItem key={'User'} disablePadding component={Link} to={'user'} selected={location.pathname=='/user'}>
<ListItemButton>
<ListItemIcon>
<PersonIcon />
</ListItemIcon>
<ListItemText primary={'User'} />
</ListItemButton>
</ListItem>
<ListItem key={'Quotes'} disablePadding component={Link} to={'quotes'} selected={location.pathname=='/quotes'}>
<ListItemButton>
<ListItemIcon>
<MonetizationOnIcon />
</ListItemIcon>
<ListItemText primary={'Quotes'} />
</ListItemButton>
</ListItem>
<ListItem key={'WorkOrder'} disablePadding component={Link} to={'workOrder'} selected={location.pathname=='/workOrder'}>
<ListItemButton>
<ListItemIcon>
<TextSnippetIcon />
</ListItemIcon>
<ListItemText primary={'Work Order'} />
</ListItemButton>
</ListItem>
<ListItem key={'Contacts'} disablePadding component={Link} to={'contacts'} selected={location.pathname=='/contacts'}>
<ListItemButton>
<ListItemIcon>
<TextSnippetIcon />
</ListItemIcon>
<ListItemText primary={'Contacts'} />
</ListItemButton>
</ListItem>
<ListItem key={'AddressBook'} disablePadding component={Link} to={'addressBook'} selected={location.pathname=='/addressBook'}>
<ListItemButton>
<ListItemIcon>
<TextSnippetIcon />
</ListItemIcon>
<ListItemText primary={'Address Book'} />
</ListItemButton>
</ListItem>
</List>
</Drawer>
);
}

View file

@ -0,0 +1,9 @@
import { makeStyles } from "@mui/styles";
const ListHeaderStyles = makeStyles((theme) => ({
box: {
display: "flex",
},
}));
export default ListHeaderStyles;

View file

@ -0,0 +1,26 @@
import React from "react";
import Typography from "@mui/material/Typography";
import { Button, Box } from "@mui/material";
import ListHeaderStyles from "./ListHeader.Styles";
const ListHeader = ({ handleClickFormModalOpen, isEmpty, text }) => {
const classes = ListHeaderStyles();
return (
!isEmpty && (
<Box component="span" m={1} className={classes.box}>
<Typography variant="h6" component="h6">
{text}
</Typography>
<Button
variant="contained"
color="primary"
style={{ marginLeft: "auto" }}
onClick={handleClickFormModalOpen}
>
Add
</Button>
</Box>
)
);
};
export default ListHeader;

46
src/components/Main.js Normal file
View file

@ -0,0 +1,46 @@
import * as React from 'react';
import { styled, useTheme } from '@mui/material/styles';
const drawerWidth = 240;
const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })(({ theme }) => ({
flexGrow: 1,
padding: theme.spacing(3),
transition: theme.transitions.create('margin', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
marginLeft: `0px`,
variants: [
{
props: ({ open }) => open,
style: {
transition: theme.transitions.create('margin', {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen,
}),
marginLeft: 0,
},
},
],
}));
const DrawerHeader = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
padding: theme.spacing(0, 1),
// necessary for content to be below app bar
...theme.mixins.toolbar,
justifyContent: 'flex-end',
}));
export default function MainContainer({ open, children}) {
const theme = useTheme();
return (
<Main open={open}>
{children}
</Main>
);
}

View file

@ -0,0 +1,18 @@
import React from "react";
import { Button, CircularProgress } from "@mui/material";
const SubmitButtonWithLoader = (props) => {
const { buttonText, loading } = props;
return (
<Button
variant="contained"
color="primary"
type="submit"
disabled={loading}
>
{loading && <CircularProgress size={14} />}
{!loading && buttonText}
</Button>
);
};
export default SubmitButtonWithLoader;

45
src/components/TopNav.js Normal file
View file

@ -0,0 +1,45 @@
import * as React from 'react';
import AppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import MenuIcon from '@mui/icons-material/Menu';
import Header from './Header';
import { removeToken } from '../tokenUtility'
import { useNavigate, useLocation } from 'react-router-dom';
export default function TopNav({handleDrawerOpen}) {
const navigate = useNavigate();
const onLogout = () => {
removeToken();
navigate('login');
}
const location = useLocation();
const isLoginPage = location.pathname == '/';
return (
<Box sx={{ flexGrow: 1 }}>
{isLoginPage && <Header />}
{!isLoginPage && <AppBar position="static">
<Toolbar>
<IconButton
size="large"
edge="start"
color="inherit"
aria-label="menu"
sx={{ mr: 2 }}
onClick={handleDrawerOpen}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
Sea Haven
</Typography>
<Button color="inherit" onClick={onLogout}>Logout</Button>
</Toolbar>
</AppBar>}
</Box>
);
}

30
src/constants.js Normal file
View file

@ -0,0 +1,30 @@
export const ADD = "add";
export const EDIT = "edit";
export const API_ERROR_MESSAGE =
"Sorry, something went wrong. Please try again later.";
export const API_SUCCESS_MESSAGE =
"You changes has been successfully saved.";
export const API_DELETE_SUCCESS_MESSAGE =
"Item is deleted";
//export const API_URL = "http://console.seahavenind.com/api";
export const API_URL = "https://localhost:7195/api";
export const form_text_builder = (mode) => {
let PRIMARY_TEXT = "";
let SECONDARY_TEXT = "";
let BUTTON_TEXT = "";
if (mode == ADD) {
PRIMARY_TEXT = "Add an Item";
SECONDARY_TEXT = "Add your new item below";
BUTTON_TEXT = "Add Item";
} else {
PRIMARY_TEXT = "Edit an Item";
SECONDARY_TEXT = "Edit your item below";
BUTTON_TEXT = "Save Item";
}
return { PRIMARY_TEXT, SECONDARY_TEXT, BUTTON_TEXT };
};

72
src/hooks/useForm.js Normal file
View file

@ -0,0 +1,72 @@
import { useState, useEffect } from "react";
export const formIsValid = (e) => Object.keys(e).length === 0;
const useForm = (props) => {
const { initialValues, onSubmit, validate } = props;
const [values, setValues] = useState(null);
const [errors, setErrors] = useState({});
const [touchedField, setTouchedField] = useState("");
const [valid, setValid] = useState(false);
const [touched, setTouched] = useState(false);
useEffect(() => {
if (validate && typeof validate === "function" && touched) {
const _errors = validate(values);
setValid(formIsValid(_errors));
if (_errors.hasOwnProperty(touchedField)) {
setErrors({ [touchedField]: _errors[touchedField] });
} else {
setErrors({});
}
}
}, [values]);
useEffect(() => {
if (initialValues && !Object.is(initialValues, values))
setValues((values) => ({
...values,
...initialValues,
}));
}, [initialValues]);
function handleSubmit(event) {
event.preventDefault();
const errors = validate(values);
setErrors({ ...errors });
if (formIsValid(errors)) {
onSubmit(values, errors);
}
return Boolean(Object.keys(errors).length);
}
function handleChangeKV(name, value, resetProps = {}) {
setTouchedField(name);
setValues({
...values,
...resetProps,
[name]: value,
});
setTouched(true);
}
function setFormErrors() {
const errors = validate(values);
setErrors({ ...errors });
return Boolean(Object.keys(errors).length);
}
return {
handleSubmit,
values,
errors,
valid,
touched,
handleChangeKV,
setErrors,
setValues,
setFormErrors,
};
};
export default useForm;

View file

@ -1,13 +0,0 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

1
src/index.html Normal file
View file

@ -0,0 +1 @@
<div id="root"></div>

View file

@ -1,17 +1,34 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import React from "react";
import ReactDOM from "react-dom";
import Layout from "Site.Layout";
import LoginPage from "pages/login";
import UserPage from "pages/user/list";
import QuotesPage from "pages/quotes/list";
import WorkOrder from "pages/workorder/list";
import Contacts from "pages/contacts/list";
import AddressBook from "pages/address/list";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
import { HashRouter as Router, Routes, Route, Link } from 'react-router-dom';
import Dashboard from "pages/Dashboard";
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
const App = () => {
return (
<Layout>
<Routes>
<Route index element={<LoginPage />} />
<Route exact path="/login" element={<LoginPage />} />
<Route exact path="/dashboard" element={<Dashboard />} />
<Route exact path="/user" element={<UserPage />} />
<Route exact path="/quotes" element={<QuotesPage />} />
<Route exact path="/workOrder" element={<WorkOrder />} />
<Route exact path="/contacts" element={<Contacts />} />
<Route exact path="/addressBook" element={<AddressBook />} />
<Route path="*" element={<p>Not Found: 404!</p>} />
</Routes>
</Layout>
);
};
ReactDOM.render(<Router><App /></Router>, document.getElementById("root"));

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

109
src/pages/Dashboard.js Normal file
View file

@ -0,0 +1,109 @@
// Dashboard.js
import React from 'react';
import { Bar, Line, Pie } from 'react-chartjs-2';
import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ArcElement } from 'chart.js';
// Register the required components for Chart.js
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ArcElement);
const Dashboard = () => {
// Data for Bar Chart
const barData = {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [
{
label: 'Sales Data',
data: [65, 59, 80, 81, 56, 55, 40],
backgroundColor: 'rgba(75, 192, 192, 0.6)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1,
},
],
};
// Options for Bar Chart
const barOptions = {
responsive: true,
plugins: {
legend: {
position: 'top',
},
title: {
display: true,
text: 'Bar Chart Example',
},
},
};
// Data for Line Chart
const lineData = {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [
{
label: 'Revenue',
data: [0, 20, 50, 70, 100, 130, 160],
fill: false,
borderColor: 'rgba(255, 99, 132, 1)',
tension: 0.1,
},
],
};
// Options for Line Chart
const lineOptions = {
responsive: true,
plugins: {
legend: {
position: 'top',
},
title: {
display: true,
text: 'Line Chart Example',
},
},
};
// Data for Pie Chart
const pieData = {
labels: ['Red', 'Blue', 'Yellow'],
datasets: [
{
label: 'Colors',
data: [300, 50, 100],
backgroundColor: ['rgba(255, 99, 132, 0.6)', 'rgba(54, 162, 235, 0.6)', 'rgba(255, 206, 86, 0.6)'],
borderColor: ['rgba(255, 99, 132, 1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 206, 86, 1)'],
borderWidth: 1,
},
],
};
// Options for Pie Chart
const pieOptions = {
responsive: true,
plugins: {
legend: {
position: 'top',
},
title: {
display: true,
text: 'Pie Chart Example',
},
},
};
return (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '20px', padding: '20px' }}>
<div style={{ width: '600px', height: '400px' }}>
<Bar data={barData} options={barOptions} />
</div>
<div style={{ width: '600px', height: '400px' }}>
<Line data={lineData} options={lineOptions} />
</div>
<div style={{ width: '600px', height: '400px' }}>
<Pie data={pieData} options={pieOptions} />
</div>
</div>
);
};
export default Dashboard;

View file

@ -0,0 +1,11 @@
export const initialValues = {
FirstName: "",
};
export const formValidation = (values) => {
const errors = {};
if (values.FirstName === "") {
errors.FirstName = "FirstName is required";
}
return errors;
};

View file

@ -0,0 +1,17 @@
import { makeStyles } from "@mui/styles";
const DeleteStyles = makeStyles((theme) => ({
header: {
borderBottom: "2px solid rgba(0,0,0,0.1)",
display: "flex",
},
title: {
fontWeight: "600!important",
fontSize: "1.25rem!important",
},
text: {
fontSize: "1.25rem!important",
},
}));
export default DeleteStyles;

View file

@ -0,0 +1,85 @@
import React, { useState } from "react";
import CloseIcon from "@mui/icons-material/Close";
import DeleteStyles from "./Delete.Styles";
import {
Button,
IconButton,
Typography,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
Box,
} from "@mui/material";
import SubmitButtonWithLoader from "../../../components/SubmitButtonWithLoader";
import {
form_text_builder,
API_DELETE_SUCCESS_MESSAGE,
API_ERROR_MESSAGE,
} from "../../../constants";
const DeleteModal = ({
item,
open,
setOpen,
apiCall,
setApiStatus,
}) => {
const [isFormLoading, setIsFormLoading] = useState(false);
const classes = DeleteStyles();
const handleClose = () => {
setOpen({ open: false });
};
const handleSubmit = () => {
setIsFormLoading(true);
apiCall(item)
.then((response) => {
setApiStatus({
status: response.status,
message: response.message,
show: true,
});
handleClose();
})
.catch((e) => {
setApiStatus({
status: "failed",
message: API_ERROR_MESSAGE,
show: true,
});
handleClose();
});
};
return (
<div style={{ display: "flex", justifyContent: "center" }}>
<Dialog fullWidth maxWidth={"sm"} open={open} onClose={handleClose}>
<DialogTitle className={classes.header}>
<Typography className={classes.title}>Delete Item?</Typography>
<IconButton onClick={handleClose} style={{ marginLeft: "auto" }}>
<CloseIcon />
</IconButton>
</DialogTitle>
<Box component="form" onSubmit={handleSubmit} noValidate>
<DialogContent>
<DialogContentText className={classes.text}>
Are you sure you want to delete this item?This can not be undone.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<SubmitButtonWithLoader
buttonText={"Delete"}
loading={isFormLoading}
/>
</DialogActions>
</Box>
</Dialog>
</div>
);
};
export default DeleteModal;

View file

@ -0,0 +1,14 @@
import { makeStyles } from "@mui/styles";
const FormStyles = makeStyles((theme) => ({
header: {
borderBottom: "2px solid rgba(0,0,0,0.1)",
display: "flex",
},
title:{
fontWeight: "600!important",
fontSize: "1.25rem!important",
}
}));
export default FormStyles;

View file

@ -0,0 +1,129 @@
import React, { useState } from "react";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle";
import Box from "@mui/material/Box";
import InputLabel from "@mui/material/InputLabel";
import MenuItem from "@mui/material/MenuItem";
import Select from "@mui/material/Select";
import CloseIcon from "@mui/icons-material/Close";
import FormStyles from "./Form.Styles";
import IconButton from "@mui/material/IconButton";
import Typography from "@mui/material/Typography";
import useForm from "../../../hooks/useForm";
import { formValidation } from "../_utility";
import SubmitButtonWithLoader from "../../../components/SubmitButtonWithLoader";
import { form_text_builder, API_SUCCESS_MESSAGE } from "../../../constants";
const ItemForm = ({
userItem,
mode,
open,
setOpen,
apiCall,
setApiStatus,
}) => {
const [isFormLoading, setIsFormLoading] = useState(false);
const [isFormSubmit, setIsFormSubmit] = useState(false);
const classes = FormStyles();
const { PRIMARY_TEXT, SECONDARY_TEXT, BUTTON_TEXT } = form_text_builder(mode);
const { values, errors, handleChangeKV, handleSubmit } = useForm({
initialValues: userItem,
onSubmit(values) {
setIsFormLoading(true);
apiCall(values)
.then((response) => {
setApiStatus({
status: 'success',
message: API_SUCCESS_MESSAGE,
show: true,
});
handleClose();
})
.catch((e) => {
setApiStatus({
status: "failed",
message: e.message,
show: true,
});
handleClose();
});
},
validate: (formValues) => {
if (isFormSubmit) setIsFormSubmit(false);
return formValidation(formValues);
},
});
const handleClose = () => {
setOpen({ open: false });
};
return (
<div style={{ display: "flex", justifyContent: "center" }}>
{values && (
<Dialog fullWidth maxWidth={"sm"} open={open} onClose={handleClose}>
<DialogTitle className={classes.header}>
<Typography className={classes.title}>Address</Typography>
<IconButton onClick={handleClose} style={{ marginLeft: "auto" }}>
<CloseIcon />
</IconButton>
</DialogTitle>
<Box component="form" onSubmit={handleSubmit} noValidate>
<DialogContent>
<DialogContentText className={classes.title}>
{PRIMARY_TEXT}
</DialogContentText>
<TextField
id="Name"
label="Name"
variant="outlined"
style={{ width: '100%' }}
margin="normal"
required
name="Name"
value={values.name}
onChange={(event) => {
const { value } = event.target;
handleChangeKV("name", value);
}}
error={!!errors.FirstName}
helperText={errors.FirstName}
/>
<TextField
id="Title"
label="Title"
variant="outlined"
style={{ width: '100%' }}
margin="normal"
required
name="Title"
value={values.title}
onChange={(event) => {
const { value } = event.target;
handleChangeKV("title", value);
}}
error={!!errors.LastName}
helperText={errors.LastName}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<SubmitButtonWithLoader
buttonText={BUTTON_TEXT}
loading={isFormLoading}
/>
</DialogActions>
</Box>
</Dialog>
)}
</div>
);
};
export default ItemForm;

View file

@ -0,0 +1,42 @@
import { makeStyles } from "@mui/styles";
const ListStyles = makeStyles((theme) => ({
listRoot: {
width: "100%",
marginTop: 12,
"& .MuiListItem-root": {
"&.MuiListItem-divider": {
borderBottom: "2px solid rgba(0,0,0,0.1)",
},
},
"& .MuiListItem-container": {
borderRadius: 3,
border: "1px solid #EAEFF4",
marginBottom: 8,
marginRight: 8,
},
},
listItemText: {
"& .MuiListItemText-primary": {
fontSize: "1rem",
fontWeight: "bold",
},
"& .MuiListItemText-secondary": {
fontSize: "0.875rem",
},
},
IconButton: {
"& .MuiSvgIcon-root": {
fontSize: "1.4rem",
marginRight: "10px",
},
},
box: {
display: "flex",
},
lineThrough: {
textDecoration: "line-through",
},
}));
export default ListStyles;

View file

@ -0,0 +1,94 @@
import React from "react";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";
import Checkbox from "@mui/material/Checkbox";
import IconButton from "@mui/material/IconButton";
import ListItemSecondaryAction from "@mui/material/ListItemSecondaryAction";
import EditOutlined from "@mui/icons-material/EditOutlined";
import DeleteIcon from "@mui/icons-material/Delete";
import ListStyles from "./List.Styles";
const ListItems = ({ items, onEditClick, onDeleteClick }) => {
const classes = ListStyles();
const [checked, setChecked] = React.useState([]);
const handleRowClick = (value) => () => {
const currentIndex = checked.indexOf(value);
const newChecked = [...checked];
if (currentIndex === -1) {
newChecked.push(value);
} else {
newChecked.splice(currentIndex, 1);
}
setChecked(newChecked);
};
const isChecked = (value) => checked.indexOf(value) !== -1;
return (
<>
<List className={classes.listRoot}>
{items && items.length > 0 && items.map((value, i) => {
const { _id, name, description, quantity, isPurchased } = value;
const labelId = `checkbox-list-label-${i}`;
return (
<ListItem
key={i}
role={undefined}
dense
button
onClick={handleRowClick(value)}
>
<ListItemIcon>
<Checkbox
edge="start"
checked={isChecked(value)}
tabIndex={-1}
disableRipple
inputProps={{ "aria-labelledby": labelId }}
/>
</ListItemIcon>
<ListItemText
className={`${classes.listItemText} ${isChecked(value) ? classes.lineThrough : ""
}`}
id={labelId}
primary={name}
secondary={description}
/>
<ListItemSecondaryAction>
<IconButton
edge="end"
aria-label="delete"
className={classes.IconButton}
onClick={() => {
onDeleteClick({ open: true, selectedItem: value });
}}
>
<DeleteIcon />
</IconButton>
<IconButton
edge="end"
aria-label="edit"
className={classes.IconButton}
onClick={() => {
onEditClick({ open: true, selectedItem: value });
}}
>
<EditOutlined />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
);
})}
</List>
</>
);
};
export default ListItems;

View file

@ -0,0 +1,151 @@
import React from "react";
import MUIDataTable from "mui-datatables";
import IconButton from "@mui/material/IconButton";
import EditOutlined from "@mui/icons-material/EditOutlined";
import DeleteIcon from "@mui/icons-material/Delete";
import TableStyles from './TableStyles';
import { createTheme, ThemeProvider } from '@mui/material/styles';
const TableItems = ({ items, onEditClick, onDeleteClick }) => {
const classes = TableStyles();
const getMuiTheme = () => {
return createTheme({
components: {
MUIDataTableHeadCell: {
styleOverrides: {
root: {
backgroundColor: '#98acbf',
color: '#fff',
}
},
},
MUIDataTableSelectCell: {
styleOverrides: {
headerCell: {
backgroundColor: '#98acbf',
color: '#fff',
}
}
},
MUIDataTableBodyRow: {
styleOverrides: {
root: {
'&:nth-of-type(odd)': {
backgroundColor: 'rgba(0, 0, 0, 0.02)'
}
}
},
},
MUIDataTableBodyCell: {
styleOverrides: {
root: {
padding: 6,
wordBreak: 'break-all'
}
},
},
},
})
}
const columns = [
{
name: "id",
label: "id",
options: {
display: false,
filter: false,
sort: false,
viewColumns:false,
}
},
{
name: "name",
label: "Name",
options: {
display: true,
filter: false,
sort: true,
}
},
{
name: "title",
label: "Title",
options: {
display: true,
filter: true,
sort: false,
}
},
{
name: "",
label: "",
options: {
filter: false,
sort: false,
empty: true,
customBodyRender: (value, tableMeta, updateValue) => {
return (
<>
<IconButton
edge="end"
aria-label="edit"
className={classes.IconButton}
onClick={(e) => {
e.stopPropagation();
const id = tableMeta.rowData[0];
const _selectedItem = tableMeta.tableData
.find(obj => obj.id == id);
onEditClick({ open: true, selectedItem: _selectedItem });
}}
>
<EditOutlined />
</IconButton>
<IconButton
edge="end"
aria-label="delete"
className={classes.IconButton}
onClick={(e) => {
e.stopPropagation();
const id = tableMeta.rowData[0];
const _selectedItem = tableMeta.tableData
.find(obj => obj.id == id);
onDeleteClick({ open: true, selectedItem: _selectedItem });
}}
>
<DeleteIcon />
</IconButton>
</>
);
}
}
}
];
const handleRowClick = (rowData, rowMeta) => {
console.log(rowData, rowMeta);
};
const options = {
filterType: "checkbox",
rowsPerPage: 50,
rowsPerPageOptions: [50, 100, 200],
jumpToPage: true,
onRowClick: handleRowClick,
};
return (
<ThemeProvider theme={getMuiTheme()}>
<MUIDataTable
title={""}
data={items}
columns={columns}
options={options}
/>
</ThemeProvider>
);
}
export default React.memo(TableItems);

View file

@ -0,0 +1,22 @@
import { makeStyles } from "@mui/styles";
const TableStyles = makeStyles((theme) => ({
listRoot: {
width: "100%",
marginTop: 12,
},
IconButton: {
"& .MuiSvgIcon-root": {
fontSize: "1.4rem",
marginRight: "10px",
},
},
box: {
display: "flex",
},
lineThrough: {
textDecoration: "line-through",
},
}));
export default TableStyles;

View file

@ -0,0 +1,6 @@
import { deleteItem } from '../../../apiUtil';
import { API_URL } from '../../../constants';
export const deleteItemApi = (item) => deleteItem(`${API_URL}/Aircraft`, item);

View file

@ -0,0 +1,17 @@
import React from "react";
import { deleteItemApi } from "./api";
import Delete from "../components/Delete";
const DeleteItemModal = ({ aircraftItem, open, setOpen, setApiStatus }) => {
return (
<Delete
item={aircraftItem}
open={open}
setOpen={setOpen}
apiCall={deleteItemApi}
setApiStatus={setApiStatus}
/>
);
};
export default DeleteItemModal;

View file

View file

@ -0,0 +1,4 @@
import { put } from '../../../apiUtil';
import { API_URL } from '../../../constants';
export const editItems = (item) => put(`${API_URL}/Contact/EditLocation`, item);

View file

@ -0,0 +1,21 @@
import React from "react";
import Form from "../components/Form";
import { EDIT } from "../../../constants";
import { editItems } from "./api";
const EditItemModal = ({ userItem, open, setOpen, isEdit, setApiStatus }) => {
return (
isEdit && open && (
<Form
userItem={{ ...userItem }}
open={open}
setOpen={setOpen}
mode={EDIT}
apiCall={editItems}
setApiStatus={setApiStatus}
/>
)
);
};
export default EditItemModal;

View file

@ -0,0 +1,4 @@
import { get } from '../../../apiUtil';
import { API_URL } from '../../../constants';
export const getItems = () => get(`${API_URL}/Contact/GetAddressbook`);

View file

@ -0,0 +1,95 @@
import React from "react";
import TableItems from "../components/Table";
import ListHeader from "../../../components/ListHeader";
import EmptyList from "../../../components/EmptyList";
import Banner from "../../../components/Banner";
import AddModal from "../new";
import EditModal from "../edit";
import DeleteModal from "../delete";
import useItems from "./useItems";
import { CircularProgress, Box } from "@mui/material";
import Alert from "@mui/material/Alert";
import Stack from "@mui/material/Stack";
import { getUser } from '../../../tokenUtility'
const List = () => {
const [formModalopen, setFormModalopen] = React.useState({
selectedItem: {},
open: false,
});
const [deleteModalopen, setDeleteModalopen] = React.useState({
selectedItem: {},
open: false,
});
const [apiStatus, setApiStatus] = React.useState({
show: false,
status: "",
message: "",
});
const handleClickFormModalOpen = () => {
setFormModalopen({ open: true });
};
const isEdit = !!formModalopen.selectedItem;
const user = getUser();
const {
list: items,
isError,
isLoading,
errorMessage,
} = useItems(apiStatus, setApiStatus);
if (isLoading) {
return (
<Box component="span" m={1}>
<CircularProgress />
</Box>
);
}
const isEmptyList = !items || items.length == 0;
return (
<>
<Banner apiStatus={apiStatus} setApiStatus={setApiStatus} />
<Stack sx={{ width: "100%", mb: 1 }} spacing={2}>
{errorMessage && (
<Alert severity="error">{errorMessage}</Alert>
)}
</Stack>
<ListHeader
handleClickFormModalOpen={handleClickFormModalOpen}
isEmpty={isEmptyList}
text={'AddressBook'}
/>
<TableItems
items={items}
onEditClick={setFormModalopen}
onDeleteClick={setDeleteModalopen}
isEmpty={isEmptyList}
/>
<AddModal
open={formModalopen.open}
setOpen={setFormModalopen}
isEdit={isEdit}
setApiStatus={setApiStatus}
/>
<EditModal
userItem={{ ...formModalopen.selectedItem }}
open={formModalopen.open}
setOpen={setFormModalopen}
isEdit={isEdit}
setApiStatus={setApiStatus}
/>
<DeleteModal
userItem={{ ...deleteModalopen.selectedItem }}
open={deleteModalopen.open}
setOpen={setDeleteModalopen}
setApiStatus={setApiStatus}
/>
</>
);
};
export default List;

View file

@ -0,0 +1,43 @@
import React from "react";
import { getItems } from "./api";
import { API_ERROR_MESSAGE } from '../../../constants'
function useItems(apiStatus, setApiStatus) {
const [items, setItems] = React.useState({
list: [],
isError: false,
isLoading: true,
errorMessage: '',
});
React.useEffect(() => {
if (apiStatus.show)
getItems().then(handleSuccess, handleFailure);
}, [apiStatus]);
React.useEffect(() => {
getItems().then(handleSuccess, handleFailure);
}, []);
function handleSuccess(response) {
setItems({
list: response?.data || [],
isError: false,
isLoading: false,
errorMessage: '',
});
}
function handleFailure(error) {
setItems({
list: [],
isError: true,
isLoading: false,
errorMessage: error.message,
});
}
return items;
}
export default useItems;

View file

@ -0,0 +1,4 @@
import { post } from '../../../apiUtil';
import { API_URL } from '../../../constants';
export const addItems = (item) => post(`${API_URL}/Contact/EditLocation`, item);

View file

@ -0,0 +1,23 @@
import React from "react";
import Form from "../components/Form";
import { initialValues } from "../_utility";
import { ADD } from "../../../constants";
import { addItems } from "./api";
const AddItemModal = ({ open, setOpen, isEdit, setApiStatus }) => {
return (
!isEdit &&
open && (
<Form
userItem={{ ...initialValues }}
open={open}
setOpen={setOpen}
mode={ADD}
apiCall={addItems}
setApiStatus={setApiStatus}
/>
)
);
};
export default AddItemModal;

View file

@ -0,0 +1,17 @@
export const initialValues = {
FirstName: "",
};
export const formValidation = (values) => {
const errors = {};
if (values.name=== "") {
errors.name = "FirstName is required";
}
if (values.email=== "") {
errors.email = "Email is required";
}
if (values.phoneNumber=== "") {
errors.name = "Phone Number is required";
}
return errors;
};

View file

@ -0,0 +1,17 @@
import { makeStyles } from "@mui/styles";
const DeleteStyles = makeStyles((theme) => ({
header: {
borderBottom: "2px solid rgba(0,0,0,0.1)",
display: "flex",
},
title: {
fontWeight: "600!important",
fontSize: "1.25rem!important",
},
text: {
fontSize: "1.25rem!important",
},
}));
export default DeleteStyles;

View file

@ -0,0 +1,85 @@
import React, { useState } from "react";
import CloseIcon from "@mui/icons-material/Close";
import DeleteStyles from "./Delete.Styles";
import {
Button,
IconButton,
Typography,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
Box,
} from "@mui/material";
import SubmitButtonWithLoader from "../../../components/SubmitButtonWithLoader";
import {
form_text_builder,
API_DELETE_SUCCESS_MESSAGE,
API_ERROR_MESSAGE,
} from "../../../constants";
const DeleteModal = ({
item,
open,
setOpen,
apiCall,
setApiStatus,
}) => {
const [isFormLoading, setIsFormLoading] = useState(false);
const classes = DeleteStyles();
const handleClose = () => {
setOpen({ open: false });
};
const handleSubmit = () => {
setIsFormLoading(true);
apiCall(item)
.then((response) => {
setApiStatus({
status: response.status,
message: response.message,
show: true,
});
handleClose();
})
.catch((e) => {
setApiStatus({
status: "failed",
message: API_ERROR_MESSAGE,
show: true,
});
handleClose();
});
};
return (
<div style={{ display: "flex", justifyContent: "center" }}>
<Dialog fullWidth maxWidth={"sm"} open={open} onClose={handleClose}>
<DialogTitle className={classes.header}>
<Typography className={classes.title}>Delete Item?</Typography>
<IconButton onClick={handleClose} style={{ marginLeft: "auto" }}>
<CloseIcon />
</IconButton>
</DialogTitle>
<Box component="form" onSubmit={handleSubmit} noValidate>
<DialogContent>
<DialogContentText className={classes.text}>
Are you sure you want to delete this item?This can not be undone.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<SubmitButtonWithLoader
buttonText={"Delete"}
loading={isFormLoading}
/>
</DialogActions>
</Box>
</Dialog>
</div>
);
};
export default DeleteModal;

View file

@ -0,0 +1,14 @@
import { makeStyles } from "@mui/styles";
const FormStyles = makeStyles((theme) => ({
header: {
borderBottom: "2px solid rgba(0,0,0,0.1)",
display: "flex",
},
title:{
fontWeight: "600!important",
fontSize: "1.25rem!important",
}
}));
export default FormStyles;

View file

@ -0,0 +1,147 @@
import React, { useState } from "react";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle";
import Box from "@mui/material/Box";
import InputLabel from "@mui/material/InputLabel";
import MenuItem from "@mui/material/MenuItem";
import Select from "@mui/material/Select";
import CloseIcon from "@mui/icons-material/Close";
import FormStyles from "./Form.Styles";
import IconButton from "@mui/material/IconButton";
import Typography from "@mui/material/Typography";
import useForm from "../../../hooks/useForm";
import { formValidation } from "../_utility";
import SubmitButtonWithLoader from "../../../components/SubmitButtonWithLoader";
import { form_text_builder, API_SUCCESS_MESSAGE } from "../../../constants";
const ItemForm = ({
userItem,
mode,
open,
setOpen,
apiCall,
setApiStatus,
}) => {
const [isFormLoading, setIsFormLoading] = useState(false);
const [isFormSubmit, setIsFormSubmit] = useState(false);
const classes = FormStyles();
const { PRIMARY_TEXT, SECONDARY_TEXT, BUTTON_TEXT } = form_text_builder(mode);
const { values, errors, handleChangeKV, handleSubmit } = useForm({
initialValues: userItem,
onSubmit(values) {
setIsFormLoading(true);
apiCall(values)
.then((response) => {
setApiStatus({
status: 'success',
message: API_SUCCESS_MESSAGE,
show: true,
});
handleClose();
})
.catch((e) => {
setApiStatus({
status: "failed",
message: e.message,
show: true,
});
handleClose();
});
},
validate: (formValues) => {
if (isFormSubmit) setIsFormSubmit(false);
return formValidation(formValues);
},
});
const handleClose = () => {
setOpen({ open: false });
};
return (
<div style={{ display: "flex", justifyContent: "center" }}>
{values && (
<Dialog fullWidth maxWidth={"sm"} open={open} onClose={handleClose}>
<DialogTitle className={classes.header}>
<Typography className={classes.title}>Contact</Typography>
<IconButton onClick={handleClose} style={{ marginLeft: "auto" }}>
<CloseIcon />
</IconButton>
</DialogTitle>
<Box component="form" onSubmit={handleSubmit} noValidate>
<DialogContent>
<DialogContentText className={classes.title}>
{PRIMARY_TEXT}
</DialogContentText>
<TextField
id="Name"
label="Name"
variant="outlined"
style={{ width: '100%' }}
margin="normal"
required
name="Name"
value={values.name}
onChange={(event) => {
const { value } = event.target;
handleChangeKV("name", value);
}}
error={!!errors.name}
helperText={errors.name}
/>
<TextField
id="Email"
label="Email"
variant="outlined"
style={{ width: '100%' }}
margin="normal"
required
name="Email"
value={values.email}
onChange={(event) => {
const { value } = event.target;
handleChangeKV("email", value);
}}
error={!!errors.email}
helperText={errors.email}
/>
<TextField
id="PhoneNumber"
label="Phone Number"
variant="outlined"
style={{ width: '100%' }}
margin="normal"
required
name="PhoneNumber"
value={values.phoneNumber}
onChange={(event) => {
const { value } = event.target;
handleChangeKV("phoneNumber", value);
}}
error={!!errors.phoneNumber}
helperText={errors.phoneNumber}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<SubmitButtonWithLoader
buttonText={BUTTON_TEXT}
loading={isFormLoading}
/>
</DialogActions>
</Box>
</Dialog>
)}
</div>
);
};
export default ItemForm;

View file

@ -0,0 +1,42 @@
import { makeStyles } from "@mui/styles";
const ListStyles = makeStyles((theme) => ({
listRoot: {
width: "100%",
marginTop: 12,
"& .MuiListItem-root": {
"&.MuiListItem-divider": {
borderBottom: "2px solid rgba(0,0,0,0.1)",
},
},
"& .MuiListItem-container": {
borderRadius: 3,
border: "1px solid #EAEFF4",
marginBottom: 8,
marginRight: 8,
},
},
listItemText: {
"& .MuiListItemText-primary": {
fontSize: "1rem",
fontWeight: "bold",
},
"& .MuiListItemText-secondary": {
fontSize: "0.875rem",
},
},
IconButton: {
"& .MuiSvgIcon-root": {
fontSize: "1.4rem",
marginRight: "10px",
},
},
box: {
display: "flex",
},
lineThrough: {
textDecoration: "line-through",
},
}));
export default ListStyles;

View file

@ -0,0 +1,94 @@
import React from "react";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";
import Checkbox from "@mui/material/Checkbox";
import IconButton from "@mui/material/IconButton";
import ListItemSecondaryAction from "@mui/material/ListItemSecondaryAction";
import EditOutlined from "@mui/icons-material/EditOutlined";
import DeleteIcon from "@mui/icons-material/Delete";
import ListStyles from "./List.Styles";
const ListItems = ({ items, onEditClick, onDeleteClick }) => {
const classes = ListStyles();
const [checked, setChecked] = React.useState([]);
const handleRowClick = (value) => () => {
const currentIndex = checked.indexOf(value);
const newChecked = [...checked];
if (currentIndex === -1) {
newChecked.push(value);
} else {
newChecked.splice(currentIndex, 1);
}
setChecked(newChecked);
};
const isChecked = (value) => checked.indexOf(value) !== -1;
return (
<>
<List className={classes.listRoot}>
{items && items.length > 0 && items.map((value, i) => {
const { _id, name, description, quantity, isPurchased } = value;
const labelId = `checkbox-list-label-${i}`;
return (
<ListItem
key={i}
role={undefined}
dense
button
onClick={handleRowClick(value)}
>
<ListItemIcon>
<Checkbox
edge="start"
checked={isChecked(value)}
tabIndex={-1}
disableRipple
inputProps={{ "aria-labelledby": labelId }}
/>
</ListItemIcon>
<ListItemText
className={`${classes.listItemText} ${isChecked(value) ? classes.lineThrough : ""
}`}
id={labelId}
primary={name}
secondary={description}
/>
<ListItemSecondaryAction>
<IconButton
edge="end"
aria-label="delete"
className={classes.IconButton}
onClick={() => {
onDeleteClick({ open: true, selectedItem: value });
}}
>
<DeleteIcon />
</IconButton>
<IconButton
edge="end"
aria-label="edit"
className={classes.IconButton}
onClick={() => {
onEditClick({ open: true, selectedItem: value });
}}
>
<EditOutlined />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
);
})}
</List>
</>
);
};
export default ListItems;

View file

@ -0,0 +1,160 @@
import React from "react";
import MUIDataTable from "mui-datatables";
import IconButton from "@mui/material/IconButton";
import EditOutlined from "@mui/icons-material/EditOutlined";
import DeleteIcon from "@mui/icons-material/Delete";
import TableStyles from './TableStyles';
import { createTheme, ThemeProvider } from '@mui/material/styles';
const TableItems = ({ items, onEditClick, onDeleteClick }) => {
const classes = TableStyles();
const getMuiTheme = () => {
return createTheme({
components: {
MUIDataTableHeadCell: {
styleOverrides: {
root: {
backgroundColor: '#98acbf',
color: '#fff',
}
},
},
MUIDataTableSelectCell: {
styleOverrides: {
headerCell: {
backgroundColor: '#98acbf',
color: '#fff',
}
}
},
MUIDataTableBodyRow: {
styleOverrides: {
root: {
'&:nth-of-type(odd)': {
backgroundColor: 'rgba(0, 0, 0, 0.02)'
}
}
},
},
MUIDataTableBodyCell: {
styleOverrides: {
root: {
padding: 6,
wordBreak: 'break-all'
}
},
},
},
})
}
const columns = [
{
name: "id",
label: "id",
options: {
display: false,
filter: false,
sort: false,
viewColumns:false,
}
},
{
name: "name",
label: "Name",
options: {
display: true,
filter: false,
sort: true,
}
},
{
name: "phoneNumber",
label: "Phone Number",
options: {
display: true,
filter: true,
sort: false,
}
},
{
name: "email",
label: "Email",
options: {
display: true,
filter: true,
sort: false,
}
},
{
name: "",
label: "",
options: {
filter: false,
sort: false,
empty: true,
customBodyRender: (value, tableMeta, updateValue) => {
return (
<>
<IconButton
edge="end"
aria-label="edit"
className={classes.IconButton}
onClick={(e) => {
e.stopPropagation();
const id = tableMeta.rowData[0];
const _selectedItem = tableMeta.tableData
.find(obj => obj.id == id);
onEditClick({ open: true, selectedItem: _selectedItem });
}}
>
<EditOutlined />
</IconButton>
<IconButton
edge="end"
aria-label="delete"
className={classes.IconButton}
onClick={(e) => {
e.stopPropagation();
const id = tableMeta.rowData[0];
const _selectedItem = tableMeta.tableData
.find(obj => obj.id == id);
onDeleteClick({ open: true, selectedItem: _selectedItem });
}}
>
<DeleteIcon />
</IconButton>
</>
);
}
}
}
];
const handleRowClick = (rowData, rowMeta) => {
console.log(rowData, rowMeta);
};
const options = {
filterType: "checkbox",
rowsPerPage: 50,
rowsPerPageOptions: [50, 100, 200],
jumpToPage: true,
onRowClick: handleRowClick,
};
return (
<ThemeProvider theme={getMuiTheme()}>
<MUIDataTable
title={""}
data={items}
columns={columns}
options={options}
/>
</ThemeProvider>
);
}
export default React.memo(TableItems);

View file

@ -0,0 +1,22 @@
import { makeStyles } from "@mui/styles";
const TableStyles = makeStyles((theme) => ({
listRoot: {
width: "100%",
marginTop: 12,
},
IconButton: {
"& .MuiSvgIcon-root": {
fontSize: "1.4rem",
marginRight: "10px",
},
},
box: {
display: "flex",
},
lineThrough: {
textDecoration: "line-through",
},
}));
export default TableStyles;

View file

@ -0,0 +1,6 @@
import { deleteItem } from '../../../apiUtil';
import { API_URL } from '../../../constants';
export const deleteItemApi = (item) => deleteItem(`${API_URL}/Aircraft`, item);

View file

@ -0,0 +1,17 @@
import React from "react";
import { deleteItemApi } from "./api";
import Delete from "../components/Delete";
const DeleteItemModal = ({ aircraftItem, open, setOpen, setApiStatus }) => {
return (
<Delete
item={aircraftItem}
open={open}
setOpen={setOpen}
apiCall={deleteItemApi}
setApiStatus={setApiStatus}
/>
);
};
export default DeleteItemModal;

View file

View file

@ -0,0 +1,4 @@
import { put } from '../../../apiUtil';
import { API_URL } from '../../../constants';
export const editItems = (item) => put(`${API_URL}/Contact/EditContacts`, item);

View file

@ -0,0 +1,21 @@
import React from "react";
import Form from "../components/Form";
import { EDIT } from "../../../constants";
import { editItems } from "./api";
const EditItemModal = ({ userItem, open, setOpen, isEdit, setApiStatus }) => {
return (
isEdit && open && (
<Form
userItem={{ ...userItem }}
open={open}
setOpen={setOpen}
mode={EDIT}
apiCall={editItems}
setApiStatus={setApiStatus}
/>
)
);
};
export default EditItemModal;

View file

@ -0,0 +1,8 @@
import { get } from '../../../apiUtil';
import { API_URL } from '../../../constants';
export const getItems = () => get(`${API_URL}/Contact/GetContacts`);
export const getLocations = () => get(`${API_URL}/Contact/GetLocations`);
export const getContacts = () => get(`${API_URL}/Contact/GetContacts`);
export const getUsers = () => get(`${API_URL}/Contact/GetUsers`);
export const getCategories = () => get(`${API_URL}/Contact/GetCategories`);

View file

@ -0,0 +1,95 @@
import React from "react";
import TableItems from "../components/Table";
import ListHeader from "../../../components/ListHeader";
import EmptyList from "../../../components/EmptyList";
import Banner from "../../../components/Banner";
import AddModal from "../new";
import EditModal from "../edit";
import DeleteModal from "../delete";
import useItems from "./useItems";
import { CircularProgress, Box } from "@mui/material";
import Alert from "@mui/material/Alert";
import Stack from "@mui/material/Stack";
import { getUser } from '../../../tokenUtility'
const List = () => {
const [formModalopen, setFormModalopen] = React.useState({
selectedItem: {},
open: false,
});
const [deleteModalopen, setDeleteModalopen] = React.useState({
selectedItem: {},
open: false,
});
const [apiStatus, setApiStatus] = React.useState({
show: false,
status: "",
message: "",
});
const handleClickFormModalOpen = () => {
setFormModalopen({ open: true });
};
const isEdit = !!formModalopen.selectedItem;
const user = getUser();
const {
list: items,
isError,
isLoading,
errorMessage,
} = useItems(apiStatus, setApiStatus);
if (isLoading) {
return (
<Box component="span" m={1}>
<CircularProgress />
</Box>
);
}
const isEmptyList = !items || items.length == 0;
return (
<>
<Banner apiStatus={apiStatus} setApiStatus={setApiStatus} />
<Stack sx={{ width: "100%", mb: 1 }} spacing={2}>
{errorMessage && (
<Alert severity="error">{errorMessage}</Alert>
)}
</Stack>
<ListHeader
handleClickFormModalOpen={handleClickFormModalOpen}
isEmpty={isEmptyList}
text={'Contacts'}
/>
<TableItems
items={items}
onEditClick={setFormModalopen}
onDeleteClick={setDeleteModalopen}
isEmpty={isEmptyList}
/>
<AddModal
open={formModalopen.open}
setOpen={setFormModalopen}
isEdit={isEdit}
setApiStatus={setApiStatus}
/>
<EditModal
userItem={{ ...formModalopen.selectedItem }}
open={formModalopen.open}
setOpen={setFormModalopen}
isEdit={isEdit}
setApiStatus={setApiStatus}
/>
<DeleteModal
userItem={{ ...deleteModalopen.selectedItem }}
open={deleteModalopen.open}
setOpen={setDeleteModalopen}
setApiStatus={setApiStatus}
/>
</>
);
};
export default List;

View file

@ -0,0 +1,43 @@
import React from "react";
import { getItems } from "./api";
import { API_ERROR_MESSAGE } from '../../../constants'
function useItems(apiStatus, setApiStatus) {
const [items, setItems] = React.useState({
list: [],
isError: false,
isLoading: true,
errorMessage: '',
});
React.useEffect(() => {
if (apiStatus.show)
getItems().then(handleSuccess, handleFailure);
}, [apiStatus]);
React.useEffect(() => {
getItems().then(handleSuccess, handleFailure);
}, []);
function handleSuccess(response) {
setItems({
list: response || [],
isError: false,
isLoading: false,
errorMessage: '',
});
}
function handleFailure(error) {
setItems({
list: [],
isError: true,
isLoading: false,
errorMessage: error.message,
});
}
return items;
}
export default useItems;

View file

@ -0,0 +1,4 @@
import { post } from '../../../apiUtil';
import { API_URL } from '../../../constants';
export const addItems = (item) => post(`${API_URL}/Contact/AddContacts`, item);

View file

@ -0,0 +1,23 @@
import React from "react";
import Form from "../components/Form";
import { initialValues } from "../_utility";
import { ADD } from "../../../constants";
import { addItems } from "./api";
const AddItemModal = ({ open, setOpen, isEdit, setApiStatus }) => {
return (
!isEdit &&
open && (
<Form
userItem={{ ...initialValues }}
open={open}
setOpen={setOpen}
mode={ADD}
apiCall={addItems}
setApiStatus={setApiStatus}
/>
)
);
};
export default AddItemModal;

View file

@ -0,0 +1,15 @@
export const initialValues = {
username: "",
password: "",
};
export const formValidation = (values) => {
const errors = {};
if (values.username === "") {
errors.username = "Username is required";
}
if (values.password === "") {
errors.password = "Password is required";
}
return errors;
};

4
src/pages/login/api.js Normal file
View file

@ -0,0 +1,4 @@
import { post } from '../../apiUtil';
import { API_URL } from '../../constants';
export const login = (item) => post(`${API_URL}/Authentication/login`, item);

View file

@ -0,0 +1,14 @@
import { makeStyles } from "@mui/styles";
const FormStyles = makeStyles((theme) => ({
header: {
borderBottom: "2px solid rgba(0,0,0,0.1)",
display: "flex",
},
title:{
fontWeight: "600!important",
fontSize: "1.25rem!important",
}
}));
export default FormStyles;

View file

@ -0,0 +1,128 @@
import React, { useState } from "react";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
import Box from "@mui/material/Box";
import Checkbox from "@mui/material/Checkbox";
import FormControlLabel from "@mui/material/FormControlLabel";
import FormStyles from "./Form.Styles";
import Typography from "@mui/material/Typography";
import useForm from "../../../hooks/useForm";
import { formValidation } from "../_utility";
import SubmitButtonWithLoader from "../../../components/SubmitButtonWithLoader";
import { API_ERROR_MESSAGE } from "../../../constants";
import Avatar from '@mui/material/Avatar';
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
import Container from '@mui/material/Container';
import { useNavigate } from 'react-router-dom';
import { setUser } from '../../../tokenUtility'
import Alert from "@mui/material/Alert";
import Stack from "@mui/material/Stack";
const LoginForm = ({
initialValues,
apiCall,
setApiStatus,
}) => {
const [isFormSubmit, setIsFormSubmit] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const classes = FormStyles();
const navigate = useNavigate();
const { values, errors, handleChangeKV, handleSubmit } = useForm({
initialValues,
onSubmit(values) {
apiCall(values)
.then((response) => {
setUser(response)
navigate('/user');
})
.catch((e) => {
setErrorMessage('You have entered an invalid username or password')
});
},
validate: (formValues) => {
if (isFormSubmit) setIsFormSubmit(false);
return formValidation(formValues);
},
});
return (
<Container component="main" maxWidth="xs">
<Stack sx={{ width: "100%" }} spacing={2}>
{errorMessage && (
<Alert severity="error">{errorMessage}</Alert>
)}
</Stack>
{values && (
<Box
sx={{
marginTop: 8,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Avatar sx={{ m: 1, bgcolor: 'primary.main' }}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Sign in
</Typography>
<Box component="form" onSubmit={handleSubmit} noValidate sx={{ mt: 1 }}>
<TextField
id="username"
label="username"
variant="outlined"
fullWidth
margin="normal"
required
name="username"
value={values.username}
onChange={(event) => {
const { value } = event.target;
handleChangeKV("username", value);
}}
error={!!errors.username}
helperText={errors.username}
/>
<TextField
id="password"
label="password"
variant="outlined"
fullWidth
margin="normal"
required
name="password"
value={values.password}
type="password"
onChange={(event) => {
const { value } = event.target;
handleChangeKV("password", value);
}}
error={!!errors.password}
helperText={errors.password}
/>
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
>
Sign In
</Button>
</Box>
</Box>
)}
</Container>
);
};
export default LoginForm;

26
src/pages/login/index.js Normal file
View file

@ -0,0 +1,26 @@
import React from "react";
import LoginForm from "./components/Form";
import { initialValues } from "./_utility";
import { login } from "./api";
import Banner from "../../components/Banner";
const Login = () => {
const [apiStatus, setApiStatus] = React.useState({
show: false,
status: "",
message: "",
});
return (
<>
<Banner apiStatus={apiStatus} setApiStatus={setApiStatus} />
<LoginForm
initialValues={{ ...initialValues }}
apiCall={login}
setApiStatus={setApiStatus}
/>
</>
);
};
export default Login;

View file

@ -0,0 +1,11 @@
export const initialValues = {
projectName: "",
};
export const formValidation = (values) => {
const errors = {};
if (values.projectName === "") {
//errors.FirstName = "FirstName is required";
}
return errors;
};

View file

@ -0,0 +1,17 @@
import { makeStyles } from "@mui/styles";
const DeleteStyles = makeStyles((theme) => ({
header: {
borderBottom: "2px solid rgba(0,0,0,0.1)",
display: "flex",
},
title: {
fontWeight: "600!important",
fontSize: "1.25rem!important",
},
text: {
fontSize: "1.25rem!important",
},
}));
export default DeleteStyles;

View file

@ -0,0 +1,85 @@
import React, { useState } from "react";
import CloseIcon from "@mui/icons-material/Close";
import DeleteStyles from "./Delete.Styles";
import {
Button,
IconButton,
Typography,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
Box,
} from "@mui/material";
import SubmitButtonWithLoader from "../../../components/SubmitButtonWithLoader";
import {
form_text_builder,
API_DELETE_SUCCESS_MESSAGE,
API_ERROR_MESSAGE,
} from "../../../constants";
const DeleteModal = ({
item,
open,
setOpen,
apiCall,
setApiStatus,
}) => {
const [isFormLoading, setIsFormLoading] = useState(false);
const classes = DeleteStyles();
const handleClose = () => {
setOpen({ open: false });
};
const handleSubmit = () => {
setIsFormLoading(true);
apiCall(item)
.then((response) => {
setApiStatus({
status: response.status,
message: response.message,
show: true,
});
handleClose();
})
.catch((e) => {
setApiStatus({
status: "failed",
message: API_ERROR_MESSAGE,
show: true,
});
handleClose();
});
};
return (
<div style={{ display: "flex", justifyContent: "center" }}>
<Dialog fullWidth maxWidth={"sm"} open={open} onClose={handleClose}>
<DialogTitle className={classes.header}>
<Typography className={classes.title}>Delete Item?</Typography>
<IconButton onClick={handleClose} style={{ marginLeft: "auto" }}>
<CloseIcon />
</IconButton>
</DialogTitle>
<Box component="form" onSubmit={handleSubmit} noValidate>
<DialogContent>
<DialogContentText className={classes.text}>
Are you sure you want to delete this item?This can not be undone.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<SubmitButtonWithLoader
buttonText={"Delete"}
loading={isFormLoading}
/>
</DialogActions>
</Box>
</Dialog>
</div>
);
};
export default DeleteModal;

View file

@ -0,0 +1,14 @@
import { makeStyles } from "@mui/styles";
const FormStyles = makeStyles((theme) => ({
header: {
borderBottom: "2px solid rgba(0,0,0,0.1)",
display: "flex",
},
title:{
fontWeight: "600!important",
fontSize: "1.25rem!important",
}
}));
export default FormStyles;

View file

@ -0,0 +1,336 @@
import React, { useEffect, useState } from "react";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle";
import Box from "@mui/material/Box";
import InputLabel from "@mui/material/InputLabel";
import MenuItem from "@mui/material/MenuItem";
import Select from "@mui/material/Select";
import CloseIcon from "@mui/icons-material/Close";
import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline';
import FormStyles from "./Form.Styles";
import IconButton from "@mui/material/IconButton";
import Typography from "@mui/material/Typography";
import useForm from "../../../hooks/useForm";
import { formValidation } from "../_utility";
import SubmitButtonWithLoader from "../../../components/SubmitButtonWithLoader";
import { form_text_builder, API_SUCCESS_MESSAGE } from "../../../constants";
import { getItems } from '../../workorder/list/api';
import { getContacts } from '../../contacts/list/api';
const ItemForm = ({
item,
mode,
open,
setOpen,
apiCall,
setApiStatus,
}) => {
const [isFormLoading, setIsFormLoading] = useState(false);
const [workOrders, setWorkOrders] = useState([]);
const [contacts, setContacts] = useState([]);
const [isFormSubmit, setIsFormSubmit] = useState(false);
const classes = FormStyles();
const { PRIMARY_TEXT, SECONDARY_TEXT, BUTTON_TEXT } = form_text_builder(mode);
const { values, errors, handleChangeKV, handleSubmit, setValues } = useForm({
initialValues: item,
onSubmit(values) {
setIsFormLoading(true);
apiCall(values)
.then((response) => {
setApiStatus({
status: 'success',
message: API_SUCCESS_MESSAGE,
show: true,
});
handleClose();
})
.catch((e) => {
setApiStatus({
status: "failed",
message: e.message,
show: true,
});
handleClose();
});
},
validate: (formValues) => {
if (isFormSubmit) setIsFormSubmit(false);
return formValidation(formValues);
},
});
const handleClose = () => {
setOpen({ open: false });
};
const handlelineItemIncrements = () => {
setValues((values) => ({
...values,
...{lineitems:[...values.lineitems||[], {descriptions:'', lineitem:0}]},
}));
};
useEffect(() => {
getItems().then((response) => { handleSuccess(response, setWorkOrders) });
getContacts().then((response) => { handleSuccess(response, setContacts) });
}, []);
function handleSuccess(response, cb) {
cb(response || []);
}
return (
<div style={{ display: "flex", justifyContent: "center" }}>
{values && (
<Dialog fullWidth maxWidth={"lg"} open={open} onClose={handleClose}>
<DialogTitle className={classes.header}>
<Typography className={classes.title}>Quote</Typography>
<IconButton onClick={handleClose} style={{ marginLeft: "auto" }}>
<CloseIcon />
</IconButton>
</DialogTitle>
<Box component="form" onSubmit={handleSubmit} noValidate>
<DialogContent>
<DialogContentText className={classes.title}>
{PRIMARY_TEXT}
</DialogContentText>
<TextField
id="projectName"
label="Project Name"
variant="outlined"
style={{ width: '100%' }}
margin="normal"
required
name="projectName"
value={values.projectName}
onChange={(event) => {
const { value } = event.target;
handleChangeKV("projectName", value);
}}
error={!!errors.projectName}
helperText={errors.projectName}
/>
<div>
<InputLabel id="Wo-select-date-label">Date*</InputLabel>
<TextField
id="date"
label=""
variant="outlined"
style={{ width: '100%' }}
margin="normal"
required
name="date"
type="date"
value={values.date}
onChange={(event) => {
const { value } = event.target;
handleChangeKV("date", value);
}}
error={!!errors.LastName}
helperText={errors.LastName}
/>
</div>
<div>
<InputLabel id="Wo-select-contact-label">POC*</InputLabel>
<Select
labelId="Wo-select-contact-label"
id="wo"
fullWidth
value={values.contactId}
label=""
onChange={(event) => {
const { value } = event.target;
handleChangeKV("contactId", value);
}}
>
{contacts.map((s, i) => (
<MenuItem value={s.id} key={i}>
{s.name}
</MenuItem>
))}
</Select>
</div>
<div>
<InputLabel id="Wo-select-label">Personal For*</InputLabel>
<Select
labelId="Wo-select-label"
id="wo"
fullWidth
value={values.workorderId}
label="Personal For*"
onChange={(event) => {
const { value } = event.target;
const wo = workOrders.find(x => x.id = value)
setValues((old) => ({
...old,
po:wo.po,
tt:wo.tt,
siteId:wo?.locationId?.toString(),
siteName: wo.locationName,
workorderId:value
}));
}}
>
{workOrders.map((s, i) => (
<MenuItem value={s.id} key={i}>
{s.workerOrderTitle}
</MenuItem>
))}
</Select>
</div>
<div>
<InputLabel id="PO-label">PO # :</InputLabel>
<TextField
id="po"
label=""
variant="outlined"
style={{ width: '100%' }}
margin="normal"
required
name="po"
value={values?.po}
onChange={(event) => {
const { value } = event.target;
handleChangeKV("po", value);
}}
error={!!errors.po}
helperText={errors.po}
/>
</div>
<div>
<InputLabel id="TT-label">TT #</InputLabel>
<TextField
id="tt"
label=""
variant="outlined"
style={{ width: '100%' }}
margin="normal"
required
name="tt"
value={values?.tt}
onChange={(event) => {
const { value } = event.target;
handleChangeKV("tt", value);
}}
error={!!errors.tt}
helperText={errors.tt}
/>
</div>
<div>
<InputLabel id="siteName-label">Site Name</InputLabel>
<TextField
id="siteName"
label=""
variant="outlined"
style={{ width: '100%' }}
margin="normal"
required
name="siteName"
value={values?.siteName}
onChange={(event) => {
const { value } = event.target;
handleChangeKV("siteName", value);
}}
error={!!errors.siteName}
helperText={errors.siteName}
/>
</div>
<div>
<InputLabel id="siteId-label">Site Id</InputLabel>
<TextField
id="siteId"
label=""
variant="outlined"
style={{ width: '100%' }}
margin="normal"
required
name="siteId"
value={values?.siteId}
onChange={(event) => {
const { value } = event.target;
handleChangeKV("siteId", value);
}}
error={!!errors.siteId}
helperText={errors.siteId}
/>
</div>
<div style={{marginTop:'10px'}}>
<Typography className={classes.title}>List Items</Typography>
<IconButton onClick={handlelineItemIncrements} style={{ marginLeft: "auto" }}>
<AddCircleOutlineIcon />
</IconButton>
{values?.lineitems?.map((li,i) => {
return (
<div className="listItem" key={'listItem'+i} style={{display:'flex'}}>
<div className="description" key={'descriptionWrapper'+i} style={{width:"60%", paddingRight:'2px'}}>
<InputLabel id="description-label">Description</InputLabel>
<TextField
id={'description'+i}
key={'description'+i}
label=""
variant="outlined"
style={{ width: '100%' }}
margin="normal"
required
name="description"
value={li?.descriptions}
onChange={(event) => {
const { value } = event.target;
li.descriptions = value;
setValues((oldValues) => ({
...oldValues,
...values,
}));
}}
error={!!errors.descriptions}
helperText={errors.descriptions}
/>
</div>
<div className="lineitem" key={'lineitemWrapper'+i} style={{width:"40%"}}>
<InputLabel id="lineitem-label">Amount</InputLabel>
<TextField
id={'lineitem'+i}
key={'lineitem'+i}
label=""
variant="outlined"
style={{ width: '100%' }}
margin="normal"
required
name="lineitem"
value={li?.lineitem}
onChange={(event) => {
const { value } = event.target;
li.lineitem = value;
setValues((oldValues) => ({
...oldValues,
...values,
}));
}}
error={!!errors.lineitem}
helperText={errors.lineitem}
/>
</div>
</div>)
})}
</div>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<SubmitButtonWithLoader
buttonText={BUTTON_TEXT}
loading={isFormLoading}
/>
</DialogActions>
</Box>
</Dialog>
)}
</div>
);
};
export default ItemForm;

View file

@ -0,0 +1,42 @@
import { makeStyles } from "@mui/styles";
const ListStyles = makeStyles((theme) => ({
listRoot: {
width: "100%",
marginTop: 12,
"& .MuiListItem-root": {
"&.MuiListItem-divider": {
borderBottom: "2px solid rgba(0,0,0,0.1)",
},
},
"& .MuiListItem-container": {
borderRadius: 3,
border: "1px solid #EAEFF4",
marginBottom: 8,
marginRight: 8,
},
},
listItemText: {
"& .MuiListItemText-primary": {
fontSize: "1rem",
fontWeight: "bold",
},
"& .MuiListItemText-secondary": {
fontSize: "0.875rem",
},
},
IconButton: {
"& .MuiSvgIcon-root": {
fontSize: "1.4rem",
marginRight: "10px",
},
},
box: {
display: "flex",
},
lineThrough: {
textDecoration: "line-through",
},
}));
export default ListStyles;

View file

@ -0,0 +1,94 @@
import React from "react";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";
import Checkbox from "@mui/material/Checkbox";
import IconButton from "@mui/material/IconButton";
import ListItemSecondaryAction from "@mui/material/ListItemSecondaryAction";
import EditOutlined from "@mui/icons-material/EditOutlined";
import DeleteIcon from "@mui/icons-material/Delete";
import ListStyles from "./List.Styles";
const ListItems = ({ items, onEditClick, onDeleteClick }) => {
const classes = ListStyles();
const [checked, setChecked] = React.useState([]);
const handleRowClick = (value) => () => {
const currentIndex = checked.indexOf(value);
const newChecked = [...checked];
if (currentIndex === -1) {
newChecked.push(value);
} else {
newChecked.splice(currentIndex, 1);
}
setChecked(newChecked);
};
const isChecked = (value) => checked.indexOf(value) !== -1;
return (
<>
<List className={classes.listRoot}>
{items && items.length > 0 && items.map((value, i) => {
const { _id, name, description, quantity, isPurchased } = value;
const labelId = `checkbox-list-label-${i}`;
return (
<ListItem
key={i}
role={undefined}
dense
button
onClick={handleRowClick(value)}
>
<ListItemIcon>
<Checkbox
edge="start"
checked={isChecked(value)}
tabIndex={-1}
disableRipple
inputProps={{ "aria-labelledby": labelId }}
/>
</ListItemIcon>
<ListItemText
className={`${classes.listItemText} ${isChecked(value) ? classes.lineThrough : ""
}`}
id={labelId}
primary={name}
secondary={description}
/>
<ListItemSecondaryAction>
<IconButton
edge="end"
aria-label="delete"
className={classes.IconButton}
onClick={() => {
onDeleteClick({ open: true, selectedItem: value });
}}
>
<DeleteIcon />
</IconButton>
<IconButton
edge="end"
aria-label="edit"
className={classes.IconButton}
onClick={() => {
onEditClick({ open: true, selectedItem: value });
}}
>
<EditOutlined />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
);
})}
</List>
</>
);
};
export default ListItems;

View file

@ -0,0 +1,169 @@
import React from "react";
import MUIDataTable from "mui-datatables";
import IconButton from "@mui/material/IconButton";
import EditOutlined from "@mui/icons-material/EditOutlined";
import DeleteIcon from "@mui/icons-material/Delete";
import TableStyles from './TableStyles';
import { createTheme, ThemeProvider } from '@mui/material/styles';
const TableItems = ({ items, onEditClick, onDeleteClick }) => {
const classes = TableStyles();
const getMuiTheme = () => {
return createTheme({
components: {
MUIDataTableHeadCell: {
styleOverrides: {
root: {
backgroundColor: '#98acbf',
color: '#fff',
}
},
},
MUIDataTableSelectCell: {
styleOverrides: {
headerCell: {
backgroundColor: '#98acbf',
color: '#fff',
}
}
},
MUIDataTableBodyRow: {
styleOverrides: {
root: {
'&:nth-of-type(odd)': {
backgroundColor: 'rgba(0, 0, 0, 0.02)'
}
}
},
},
MUIDataTableBodyCell: {
styleOverrides: {
root: {
padding: 6,
wordBreak: 'break-all'
}
},
},
},
})
}
const columns = [
{
name: "id",
label: "id",
options: {
display: false,
filter: false,
sort: false,
viewColumns:false,
}
},
{
name: "projectName",
label: "Project Name",
options: {
display: true,
filter: true,
sort: true,
}
},
{
name: "quoteId",
label: "Quote Id",
options: {
display: true,
filter: true,
sort: false,
}
},
{
name: "siteName",
label: "Site Name",
options: {
display: true,
filter: true,
sort: false,
}
},
{
name: "totalAmount",
label: "Total Amount",
options: {
display: true,
filter: true,
sort: false,
}
},
{
name: "",
label: "",
options: {
filter: false,
sort: false,
empty: true,
customBodyRender: (value, tableMeta, updateValue) => {
return (
<>
<IconButton
edge="end"
aria-label="edit"
className={classes.IconButton}
onClick={(e) => {
e.stopPropagation();
const id = tableMeta.rowData[0];
const _selectedItem = tableMeta.tableData
.find(obj => obj.id == id);
onEditClick({ open: true, selectedItem: _selectedItem });
}}
>
<EditOutlined />
</IconButton>
<IconButton
edge="end"
aria-label="delete"
className={classes.IconButton}
onClick={(e) => {
e.stopPropagation();
const id = tableMeta.rowData[0];
const _selectedItem = tableMeta.tableData
.find(obj => obj.id == id);
onDeleteClick({ open: true, selectedItem: _selectedItem });
}}
>
<DeleteIcon />
</IconButton>
</>
);
}
}
}
];
const handleRowClick = (rowData, rowMeta) => {
console.log(rowData, rowMeta);
};
const options = {
filterType: "checkbox",
rowsPerPage: 50,
rowsPerPageOptions: [50, 100, 200],
jumpToPage: true,
onRowClick: handleRowClick,
};
return (
<ThemeProvider theme={getMuiTheme()}>
<MUIDataTable
title={""}
data={items}
columns={columns}
options={options}
/>
</ThemeProvider>
);
}
export default React.memo(TableItems);

View file

@ -0,0 +1,22 @@
import { makeStyles } from "@mui/styles";
const TableStyles = makeStyles((theme) => ({
listRoot: {
width: "100%",
marginTop: 12,
},
IconButton: {
"& .MuiSvgIcon-root": {
fontSize: "1.4rem",
marginRight: "10px",
},
},
box: {
display: "flex",
},
lineThrough: {
textDecoration: "line-through",
},
}));
export default TableStyles;

View file

@ -0,0 +1,6 @@
import { deleteItem } from '../../../apiUtil';
import { API_URL } from '../../../constants';
export const deleteItemApi = (item) => deleteItem(`${API_URL}/Quotes/DeleteQuote`, item);

View file

@ -0,0 +1,17 @@
import React from "react";
import { deleteItemApi } from "./api";
import Delete from "../components/Delete";
const DeleteItemModal = ({ item, open, setOpen, setApiStatus }) => {
return (
<Delete
item={item}
open={open}
setOpen={setOpen}
apiCall={deleteItemApi}
setApiStatus={setApiStatus}
/>
);
};
export default DeleteItemModal;

View file

View file

@ -0,0 +1,4 @@
import { put } from '../../../apiUtil';
import { API_URL } from '../../../constants';
export const editItems = (item) => put(`${API_URL}/Quotes/EditQuote`, item);

View file

@ -0,0 +1,21 @@
import React from "react";
import Form from "../components/Form";
import { EDIT } from "../../../constants";
import { editItems } from "./api";
const EditItemModal = ({ item, open, setOpen, isEdit, setApiStatus }) => {
return (
isEdit && open && (
<Form
item={{ ...item }}
open={open}
setOpen={setOpen}
mode={EDIT}
apiCall={editItems}
setApiStatus={setApiStatus}
/>
)
);
};
export default EditItemModal;

View file

@ -0,0 +1,4 @@
import { get } from '../../../apiUtil';
import { API_URL } from '../../../constants';
export const getItems = () => get(`${API_URL}/Quotes/GetQuoutes`);

View file

@ -0,0 +1,95 @@
import React from "react";
import TableItems from "../components/Table";
import ListHeader from "../../../components/ListHeader";
import EmptyList from "../../../components/EmptyList";
import Banner from "../../../components/Banner";
import AddModal from "../new";
import EditModal from "../edit";
import DeleteModal from "../delete";
import useItems from "./useItems";
import { CircularProgress, Box } from "@mui/material";
import Alert from "@mui/material/Alert";
import Stack from "@mui/material/Stack";
import { getUser } from '../../../tokenUtility'
const List = () => {
const [formModalopen, setFormModalopen] = React.useState({
selectedItem: {},
open: false,
});
const [deleteModalopen, setDeleteModalopen] = React.useState({
selectedItem: {},
open: false,
});
const [apiStatus, setApiStatus] = React.useState({
show: false,
status: "",
message: "",
});
const handleClickFormModalOpen = () => {
setFormModalopen({ open: true });
};
const isEdit = !!formModalopen.selectedItem;
const user = getUser();
const {
list: items,
isError,
isLoading,
errorMessage,
} = useItems(apiStatus, setApiStatus);
if (isLoading) {
return (
<Box component="span" m={1}>
<CircularProgress />
</Box>
);
}
const isEmptyList = !items || items.length == 0;
return (
<>
<Banner apiStatus={apiStatus} setApiStatus={setApiStatus} />
<Stack sx={{ width: "100%", mb: 1 }} spacing={2}>
{errorMessage && (
<Alert severity="error">{errorMessage}</Alert>
)}
</Stack>
<ListHeader
handleClickFormModalOpen={handleClickFormModalOpen}
isEmpty={isEmptyList}
text={'Quotes'}
/>
<TableItems
items={items}
onEditClick={setFormModalopen}
onDeleteClick={setDeleteModalopen}
isEmpty={isEmptyList}
/>
<AddModal
open={formModalopen.open}
setOpen={setFormModalopen}
isEdit={isEdit}
setApiStatus={setApiStatus}
/>
<EditModal
item={{ ...formModalopen.selectedItem }}
open={formModalopen.open}
setOpen={setFormModalopen}
isEdit={isEdit}
setApiStatus={setApiStatus}
/>
<DeleteModal
item={{ ...deleteModalopen.selectedItem }}
open={deleteModalopen.open}
setOpen={setDeleteModalopen}
setApiStatus={setApiStatus}
/>
</>
);
};
export default List;

View file

@ -0,0 +1,43 @@
import React from "react";
import { getItems } from "./api";
import { API_ERROR_MESSAGE } from '../../../constants'
function useItems(apiStatus, setApiStatus) {
const [items, setItems] = React.useState({
list: [],
isError: false,
isLoading: true,
errorMessage: '',
});
React.useEffect(() => {
if (apiStatus.show)
getItems().then(handleSuccess, handleFailure);
}, [apiStatus]);
React.useEffect(() => {
getItems().then(handleSuccess, handleFailure);
}, []);
function handleSuccess(response) {
setItems({
list: response?.data || [],
isError: false,
isLoading: false,
errorMessage: '',
});
}
function handleFailure(error) {
setItems({
list: [],
isError: true,
isLoading: false,
errorMessage: error.message,
});
}
return items;
}
export default useItems;

View file

@ -0,0 +1,4 @@
import { post } from '../../../apiUtil';
import { API_URL } from '../../../constants';
export const addItems = (item) => post(`${API_URL}/Quotes/AddQuote`, item);

View file

@ -0,0 +1,23 @@
import React from "react";
import Form from "../components/Form";
import { initialValues } from "../_utility";
import { ADD } from "../../../constants";
import { addItems } from "./api";
const AddItemModal = ({ open, setOpen, isEdit, setApiStatus }) => {
return (
!isEdit &&
open && (
<Form
item={{ ...initialValues }}
open={open}
setOpen={setOpen}
mode={ADD}
apiCall={addItems}
setApiStatus={setApiStatus}
/>
)
);
};
export default AddItemModal;

View file

@ -0,0 +1,19 @@
export const initialValues = {
name: "",
roleName: "",
email: "",
};
export const formValidation = (values) => {
const errors = {};
if (values.name === "") {
errors.name = "Name is required";
}
if (values.roleName === "") {
errors.roleName = "RoleName is required";
}
if (values.email === "") {
errors.email = "Email is required";
}
return errors;
};

View file

@ -0,0 +1,17 @@
import { makeStyles } from "@mui/styles";
const DeleteStyles = makeStyles((theme) => ({
header: {
borderBottom: "2px solid rgba(0,0,0,0.1)",
display: "flex",
},
title: {
fontWeight: "600!important",
fontSize: "1.25rem!important",
},
text: {
fontSize: "1.25rem!important",
},
}));
export default DeleteStyles;

View file

@ -0,0 +1,87 @@
import React, { useState } from "react";
import CloseIcon from "@mui/icons-material/Close";
import DeleteStyles from "./Delete.Styles";
import {
Button,
IconButton,
Typography,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
Box,
} from "@mui/material";
import SubmitButtonWithLoader from "../../../components/SubmitButtonWithLoader";
import {
form_text_builder,
API_DELETE_SUCCESS_MESSAGE,
API_ERROR_MESSAGE,
} from "../../../constants";
const DeleteModal = ({
item,
open,
setOpen,
apiCall,
setApiStatus,
}) => {
const [isFormLoading, setIsFormLoading] = useState(false);
const classes = DeleteStyles();
const handleClose = () => {
setOpen({ open: false });
};
const handleSubmit = (event) => {
event.preventDefault();
setIsFormLoading(true);
apiCall(item)
.then((response) => {
setApiStatus({
status: response.status,
message: response.message,
show: true,
});
handleClose();
window.location.reload();
})
.catch((e) => {
setApiStatus({
status: "failed",
message: API_ERROR_MESSAGE,
show: true,
});
handleClose();
});
};
return (
<div style={{ display: "flex", justifyContent: "center" }}>
<Dialog fullWidth maxWidth={"sm"} open={open} onClose={handleClose}>
<DialogTitle className={classes.header}>
<Typography className={classes.title}>Delete Item?</Typography>
<IconButton onClick={handleClose} style={{ marginLeft: "auto" }}>
<CloseIcon />
</IconButton>
</DialogTitle>
<Box component="form" onSubmit={handleSubmit} noValidate>
<DialogContent>
<DialogContentText className={classes.text}>
Are you sure you want to delete this item?This can not be undone.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<SubmitButtonWithLoader
buttonText={"Delete"}
loading={isFormLoading}
/>
</DialogActions>
</Box>
</Dialog>
</div>
);
};
export default DeleteModal;

View file

@ -0,0 +1,14 @@
import { makeStyles } from "@mui/styles";
const FormStyles = makeStyles((theme) => ({
header: {
borderBottom: "2px solid rgba(0,0,0,0.1)",
display: "flex",
},
title:{
fontWeight: "600!important",
fontSize: "1.25rem!important",
}
}));
export default FormStyles;

View file

@ -0,0 +1,152 @@
import React, { useState } from "react";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle";
import Box from "@mui/material/Box";
import InputLabel from "@mui/material/InputLabel";
import MenuItem from "@mui/material/MenuItem";
import Select from "@mui/material/Select";
import CloseIcon from "@mui/icons-material/Close";
import FormStyles from "./Form.Styles";
import IconButton from "@mui/material/IconButton";
import Typography from "@mui/material/Typography";
import useForm from "../../../hooks/useForm";
import { formValidation } from "../_utility";
import SubmitButtonWithLoader from "../../../components/SubmitButtonWithLoader";
import { form_text_builder, API_SUCCESS_MESSAGE } from "../../../constants";
const ItemForm = ({
userItem,
mode,
open,
setOpen,
apiCall,
setApiStatus,
}) => {
const [isFormLoading, setIsFormLoading] = useState(false);
const [isFormSubmit, setIsFormSubmit] = useState(false);
const classes = FormStyles();
const { PRIMARY_TEXT, SECONDARY_TEXT, BUTTON_TEXT } = form_text_builder(mode);
const { values, errors, handleChangeKV, handleSubmit } = useForm({
initialValues: userItem,
onSubmit(values) {
setIsFormLoading(true);
const req = {role: values.roleName, ...values}
apiCall(req)
.then((response) => {
setApiStatus({
status: 'success',
message: API_SUCCESS_MESSAGE,
show: true,
});
handleClose();
})
.catch((e) => {
setApiStatus({
status: "failed",
message: e.message,
show: true,
});
handleClose();
});
},
validate: (formValues) => {
if (isFormSubmit) setIsFormSubmit(false);
return formValidation(formValues);
},
});
const handleClose = () => {
setOpen({ open: false });
};
return (
<div style={{ display: "flex", justifyContent: "center" }}>
{values && (
<Dialog fullWidth maxWidth={"sm"} open={open} onClose={handleClose}>
<DialogTitle className={classes.header}>
<Typography className={classes.title}>User</Typography>
<IconButton onClick={handleClose} style={{ marginLeft: "auto" }}>
<CloseIcon />
</IconButton>
</DialogTitle>
<Box component="form" onSubmit={handleSubmit} noValidate>
<DialogContent>
<DialogContentText className={classes.title}>
{PRIMARY_TEXT}
</DialogContentText>
<TextField
id="Name"
label="Name"
variant="outlined"
style={{ width: '100%' }}
margin="normal"
required
name="Name"
value={values.name}
onChange={(event) => {
const { value } = event.target;
handleChangeKV("name", value);
}}
error={!!errors.name}
helperText={errors.name}
/>
<TextField
id="Email"
label="Email"
variant="outlined"
style={{ width: '100%' }}
margin="normal"
required
name="Email"
value={values.email}
onChange={(event) => {
const { value } = event.target;
handleChangeKV("email", value);
}}
error={!!errors.LastName}
helperText={errors.LastName}
/>
<div>
<InputLabel id="Role-select-label">Role*</InputLabel>
<Select
labelId="Role-select-label"
id="Role"
fullWidth
value={values.roleName}
label="Role"
onChange={(event) => {
const { value } = event.target;
handleChangeKV("roleName", value);
}}
>
{['Supervisor', 'Admin', 'Manager', 'User'].map((s, i) => (
<MenuItem value={s} key={i}>
{s}
</MenuItem>
))}
</Select>
</div>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<SubmitButtonWithLoader
buttonText={BUTTON_TEXT}
loading={isFormLoading}
/>
</DialogActions>
</Box>
</Dialog>
)}
</div>
);
};
export default ItemForm;

View file

@ -0,0 +1,42 @@
import { makeStyles } from "@mui/styles";
const ListStyles = makeStyles((theme) => ({
listRoot: {
width: "100%",
marginTop: 12,
"& .MuiListItem-root": {
"&.MuiListItem-divider": {
borderBottom: "2px solid rgba(0,0,0,0.1)",
},
},
"& .MuiListItem-container": {
borderRadius: 3,
border: "1px solid #EAEFF4",
marginBottom: 8,
marginRight: 8,
},
},
listItemText: {
"& .MuiListItemText-primary": {
fontSize: "1rem",
fontWeight: "bold",
},
"& .MuiListItemText-secondary": {
fontSize: "0.875rem",
},
},
IconButton: {
"& .MuiSvgIcon-root": {
fontSize: "1.4rem",
marginRight: "10px",
},
},
box: {
display: "flex",
},
lineThrough: {
textDecoration: "line-through",
},
}));
export default ListStyles;

Some files were not shown because too many files have changed in this diff Show more