mirror of
https://github.com/yzernik/squeaknode.git
synced 2026-08-17 13:07:32 +02:00
Use react material template for frontend (#98)
* Use react material design template for frontend * Successfully make rpc request inside material design template * Make rpc request in tables page * Run react frontend in development mode for now * successfully get squeaks in tables page * Use production server in frontend dockerfile * Update gitignore to ignore generated proto files * Organize npm package
This commit is contained in:
parent
b4fa706f73
commit
4d84dcb90e
84 changed files with 45609 additions and 1 deletions
|
|
@ -14,7 +14,8 @@ RUN curl -sSL https://github.com/grpc/grpc-web/releases/download/1.2.0/\
|
|||
protoc-gen-grpc-web-1.2.0-linux-x86_64 -o /usr/local/bin/protoc-gen-grpc-web && \
|
||||
chmod +x /usr/local/bin/protoc-gen-grpc-web
|
||||
|
||||
COPY frontend/squeak-frontend .
|
||||
#COPY frontend/squeak-frontend .
|
||||
COPY frontend/react-material-admin .
|
||||
|
||||
# Copy the proto files
|
||||
RUN mkdir proto
|
||||
|
|
|
|||
27
frontend/react-material-admin/.gitignore
vendored
Executable file
27
frontend/react-material-admin/.gitignore
vendored
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.idea/
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# protos
|
||||
/src/proto
|
||||
6
frontend/react-material-admin/.prettierrc
Normal file
6
frontend/react-material-admin/.prettierrc
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"tabWidth": 2,
|
||||
"singleQuote": false,
|
||||
"semi": true,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
21
frontend/react-material-admin/LICENSE.txt
Normal file
21
frontend/react-material-admin/LICENSE.txt
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License
|
||||
|
||||
Copyright (c) 2019 Flatlogic LLC.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
106
frontend/react-material-admin/README.md
Executable file
106
frontend/react-material-admin/README.md
Executable file
|
|
@ -0,0 +1,106 @@
|
|||
# React Material Admin — Material-UI Dashboard Template
|
||||
|
||||
Built with [React](https://facebook.github.io/react/), [Material-UI](https://material-ui.com), [React Router](https://reacttraining.com/react-router/).
|
||||
**No jQuery and Bootstrap!**
|
||||
|
||||
**This version uses React 16.8.6, React Router v5, MaterialUI v4, built with React Hooks and React Context (No Redux)**
|
||||
|
||||
[Demo](https://flatlogic.com/admin-dashboards/react-material-admin/demo). Use any credentials to log in.
|
||||
|
||||
[](https://flatlogic.com/admin-dashboards/react-material-admin/demo)
|
||||
|
||||
## Full Version
|
||||
|
||||
This is a limited version of [**Full React Material Admin**](https://flatlogic.com/templates/react-material-admin-full/demo) with more components, pages and theme support.
|
||||
|
||||
## Features
|
||||
|
||||
- React (**16.8.6**)
|
||||
- React Hooks
|
||||
- React Context
|
||||
- **No jQuery and Bootstrap!**
|
||||
- Mobile friendly layout (responsive)
|
||||
- Create-react-app under the hood
|
||||
- React Router v5
|
||||
- Material-UI v4
|
||||
- Modular Architecture
|
||||
- CSS-in-JS styles
|
||||
- Webpack build
|
||||
- Stylish, clean, responsive layout
|
||||
- Authentication
|
||||
|
||||
## Pages
|
||||
|
||||
We have implemented some basic pages, so you can see our template in action.
|
||||
|
||||
- Dashboard
|
||||
- Typography
|
||||
- Tables
|
||||
- Notifications
|
||||
- Charts
|
||||
- Icons
|
||||
- Maps
|
||||
- Login
|
||||
- Error
|
||||
|
||||
## Quick Start
|
||||
|
||||
#### 1. Get the latest version
|
||||
|
||||
You can start by cloning the latest version of React Dashboard on your
|
||||
local machine by running:
|
||||
|
||||
```shell
|
||||
$ git clone https://github.com/flatlogic/react-material-admin.git MyApp
|
||||
$ cd MyApp
|
||||
```
|
||||
|
||||
#### 2. Run `yarn install`
|
||||
|
||||
This will install both run-time project dependencies and developer tools listed
|
||||
in [package.json](package.json) file.
|
||||
|
||||
#### 3. Run `yarn start`
|
||||
|
||||
Runs the app in the development mode.
|
||||
|
||||
Open http://localhost:3000 to view it in the browser. Whenever you modify any of the source files inside the `/src` folder,
|
||||
the module bundler ([Webpack](http://webpack.github.io/)) will recompile the
|
||||
app on the fly and refresh all the connected browsers.
|
||||
|
||||
#### 4. Run `yarn build`
|
||||
|
||||
Builds the app for production to the build folder.
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.
|
||||
Your app is ready to be deployed!
|
||||
|
||||
## Support
|
||||
|
||||
For any additional information please refer to [Flatlogic homepage](https://flatlogic.com).
|
||||
|
||||
## How can I support developers?
|
||||
|
||||
- Star our GitHub repo :star:
|
||||
- [Tweet about it](https://twitter.com/intent/tweet?text=Amazing%20dashboard%20built%20with%20NodeJS,%20React%20and%20Bootstrap!&url=https://github.com/flatlogic/react-material-template&via=flatlogic).
|
||||
- Create pull requests, submit bugs, suggest new features or documentation updates :wrench:
|
||||
- Follow [@flatlogic on Twitter](https://twitter.com/flatlogic).
|
||||
- Subscribe to Flatlogic newsletter at [flatlogic.com](https://flatlogic.com/)
|
||||
- Like our page on [Facebook](https://www.facebook.com/flatlogic/) :thumbsup:
|
||||
|
||||
## More from Flatlogic
|
||||
|
||||
- [React Native Starter](https://github.com/flatlogic/react-native-starter) - 🚀 A powerful react native starter template that bootstraps development of your mobile application
|
||||
- [Sing App](https://github.com/flatlogic/sing-app) - 💥 Free and open-source admin dashboard template built with Bootstrap 4
|
||||
- [Awesome Bootstrap Checkboxes & Radios](https://github.com/flatlogic/awesome-bootstrap-checkbox) - ✅ Pure css way to make inputs look prettier
|
||||
- [React Dashboard](https://github.com/flatlogic/react-dashboard) - 🔥 React Dashboard - isomorphic admin dashboard template with GraphQL
|
||||
- [Light Blue Dashboard](https://github.com/flatlogic/light-blue-dashboard) - 💦 Free and open-source admin dashboard template built with Bootstrap
|
||||
|
||||
## Premium themes
|
||||
|
||||
Looking for premium themes and templates? Check out more [admin dashboard templates at flatlogic.com](https://flatlogic.com/admin-dashboards).
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/flatlogic/react-material-dashboard/blob/master/LICENSE.txt).
|
||||
35
frontend/react-material-admin/changelog.md
Normal file
35
frontend/react-material-admin/changelog.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# Changelog
|
||||
|
||||
# [1.2.1]
|
||||
|
||||
### Updated
|
||||
- Packages updated
|
||||
|
||||
### Fixed
|
||||
- Sign up name type of input
|
||||
- Dot component size prop
|
||||
- Performance errors
|
||||
|
||||
# [1.2.0]
|
||||
|
||||
### Updated
|
||||
- Packages update
|
||||
- Link to Full version
|
||||
|
||||
### Fixed
|
||||
- User login state improvements
|
||||
|
||||
## [1.1.0]
|
||||
|
||||
### New Feactures
|
||||
|
||||
- React v16.8.6
|
||||
- React Router v5
|
||||
- new React Hooks
|
||||
- Material UI v4.3
|
||||
|
||||
Bug fixes
|
||||
|
||||
## [1.0.0]
|
||||
|
||||
Initial version of the project
|
||||
14774
frontend/react-material-admin/package-lock.json
generated
Normal file
14774
frontend/react-material-admin/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
56
frontend/react-material-admin/package.json
Normal file
56
frontend/react-material-admin/package.json
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
"name": "react-material-admin",
|
||||
"version": "1.1.0",
|
||||
"private": true,
|
||||
"resolutions": {
|
||||
"websocket-extensions": "^0.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "~1.0.5",
|
||||
"@grpc/proto-loader": "~0.5.4",
|
||||
"@material-ui/core": "^4.3.0",
|
||||
"@material-ui/icons": "^4.2.1",
|
||||
"@material-ui/styles": "^4.3.0",
|
||||
"apexcharts": "^3.8.3",
|
||||
"async": "~1.5.2",
|
||||
"classnames": "^2.2.6",
|
||||
"font-awesome": "^4.7.0",
|
||||
"google-protobuf": "~3.12.0",
|
||||
"grpc-web": "~1.2.0",
|
||||
"mui-datatables": "^2.6.4",
|
||||
"react": "^16.8.6",
|
||||
"react-apexcharts": "^1.3.3",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-google-maps": "^9.4.5",
|
||||
"react-router-dom": "^5.0.1",
|
||||
"react-scripts": "^3.4.1",
|
||||
"react-syntax-highlighter": "^11.0.2",
|
||||
"react-toastify": "^5.3.2",
|
||||
"recharts": "^1.6.2",
|
||||
"tinycolor2": "^1.4.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^1.19.1"
|
||||
}
|
||||
}
|
||||
BIN
frontend/react-material-admin/public/favicon.ico
Executable file
BIN
frontend/react-material-admin/public/favicon.ico
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
44
frontend/react-material-admin/public/index.html
Executable file
44
frontend/react-material-admin/public/index.html
Executable file
|
|
@ -0,0 +1,44 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||
/>
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<!--
|
||||
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 Material Admin</title>
|
||||
<meta name="description" content="React Material Admin is a React Template built with Material-UI">
|
||||
<meta name="keywords" content="react material, material ui admin, react template, react material admin, react material dashboard">
|
||||
<meta name="author" content="Flatlogic LLC.">
|
||||
</head>
|
||||
<body style="font-family: 'Roboto', sans-serif;">
|
||||
<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>
|
||||
15
frontend/react-material-admin/public/manifest.json
Executable file
15
frontend/react-material-admin/public/manifest.json
Executable file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"short_name": "React Material Admin",
|
||||
"name": "React Material Admin is a React Template built with Material-UI",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#536DFE",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
76
frontend/react-material-admin/src/components/App.js
vendored
Normal file
76
frontend/react-material-admin/src/components/App.js
vendored
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import React from "react";
|
||||
import { HashRouter, Route, Switch, Redirect } from "react-router-dom";
|
||||
|
||||
// components
|
||||
import Layout from "./Layout";
|
||||
|
||||
// pages
|
||||
import Error from "../pages/error";
|
||||
import Login from "../pages/login";
|
||||
|
||||
// context
|
||||
import { useUserState } from "../context/UserContext";
|
||||
|
||||
export default function App() {
|
||||
// global
|
||||
var { isAuthenticated } = useUserState();
|
||||
|
||||
return (
|
||||
<HashRouter>
|
||||
<Switch>
|
||||
<Route exact path="/" render={() => <Redirect to="/app/dashboard" />} />
|
||||
<Route
|
||||
exact
|
||||
path="/app"
|
||||
render={() => <Redirect to="/app/dashboard" />}
|
||||
/>
|
||||
<PrivateRoute path="/app" component={Layout} />
|
||||
<PublicRoute path="/login" component={Login} />
|
||||
<Route component={Error} />
|
||||
</Switch>
|
||||
</HashRouter>
|
||||
);
|
||||
|
||||
// #######################################################################
|
||||
|
||||
function PrivateRoute({ component, ...rest }) {
|
||||
return (
|
||||
<Route
|
||||
{...rest}
|
||||
render={props =>
|
||||
isAuthenticated ? (
|
||||
React.createElement(component, props)
|
||||
) : (
|
||||
<Redirect
|
||||
to={{
|
||||
pathname: "/login",
|
||||
state: {
|
||||
from: props.location,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function PublicRoute({ component, ...rest }) {
|
||||
return (
|
||||
<Route
|
||||
{...rest}
|
||||
render={props =>
|
||||
isAuthenticated ? (
|
||||
<Redirect
|
||||
to={{
|
||||
pathname: "/",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
React.createElement(component, props)
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
340
frontend/react-material-admin/src/components/Header/Header.js
vendored
Normal file
340
frontend/react-material-admin/src/components/Header/Header.js
vendored
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
import React, { useState } from "react";
|
||||
import {
|
||||
AppBar,
|
||||
Toolbar,
|
||||
IconButton,
|
||||
InputBase,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Fab,
|
||||
Link
|
||||
} from "@material-ui/core";
|
||||
import {
|
||||
Menu as MenuIcon,
|
||||
MailOutline as MailIcon,
|
||||
NotificationsNone as NotificationsIcon,
|
||||
Person as AccountIcon,
|
||||
Search as SearchIcon,
|
||||
Send as SendIcon,
|
||||
ArrowBack as ArrowBackIcon,
|
||||
} from "@material-ui/icons";
|
||||
import classNames from "classnames";
|
||||
|
||||
// styles
|
||||
import useStyles from "./styles";
|
||||
|
||||
// components
|
||||
import { Badge, Typography, Button } from "../Wrappers/Wrappers";
|
||||
import Notification from "../Notification/Notification";
|
||||
import UserAvatar from "../UserAvatar/UserAvatar";
|
||||
|
||||
// context
|
||||
import {
|
||||
useLayoutState,
|
||||
useLayoutDispatch,
|
||||
toggleSidebar,
|
||||
} from "../../context/LayoutContext";
|
||||
import { useUserDispatch, signOut } from "../../context/UserContext";
|
||||
|
||||
const messages = [
|
||||
{
|
||||
id: 0,
|
||||
variant: "warning",
|
||||
name: "Jane Hew",
|
||||
message: "Hey! How is it going?",
|
||||
time: "9:32",
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
variant: "success",
|
||||
name: "Lloyd Brown",
|
||||
message: "Check out my new Dashboard",
|
||||
time: "9:18",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
variant: "primary",
|
||||
name: "Mark Winstein",
|
||||
message: "I want rearrange the appointment",
|
||||
time: "9:15",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
variant: "secondary",
|
||||
name: "Liana Dutti",
|
||||
message: "Good news from sale department",
|
||||
time: "9:09",
|
||||
},
|
||||
];
|
||||
|
||||
const notifications = [
|
||||
{ id: 0, color: "warning", message: "Check out this awesome ticket" },
|
||||
{
|
||||
id: 1,
|
||||
color: "success",
|
||||
type: "info",
|
||||
message: "What is the best way to get ...",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
color: "secondary",
|
||||
type: "notification",
|
||||
message: "This is just a simple notification",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
color: "primary",
|
||||
type: "e-commerce",
|
||||
message: "12 new orders has arrived today",
|
||||
},
|
||||
];
|
||||
|
||||
export default function Header(props) {
|
||||
var classes = useStyles();
|
||||
|
||||
// global
|
||||
var layoutState = useLayoutState();
|
||||
var layoutDispatch = useLayoutDispatch();
|
||||
var userDispatch = useUserDispatch();
|
||||
|
||||
// local
|
||||
var [mailMenu, setMailMenu] = useState(null);
|
||||
var [isMailsUnread, setIsMailsUnread] = useState(true);
|
||||
var [notificationsMenu, setNotificationsMenu] = useState(null);
|
||||
var [isNotificationsUnread, setIsNotificationsUnread] = useState(true);
|
||||
var [profileMenu, setProfileMenu] = useState(null);
|
||||
var [isSearchOpen, setSearchOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<AppBar position="fixed" className={classes.appBar}>
|
||||
<Toolbar className={classes.toolbar}>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
onClick={() => toggleSidebar(layoutDispatch)}
|
||||
className={classNames(
|
||||
classes.headerMenuButton,
|
||||
classes.headerMenuButtonCollapse,
|
||||
)}
|
||||
>
|
||||
{layoutState.isSidebarOpened ? (
|
||||
<ArrowBackIcon
|
||||
classes={{
|
||||
root: classNames(
|
||||
classes.headerIcon,
|
||||
classes.headerIconCollapse,
|
||||
),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<MenuIcon
|
||||
classes={{
|
||||
root: classNames(
|
||||
classes.headerIcon,
|
||||
classes.headerIconCollapse,
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</IconButton>
|
||||
<Typography variant="h6" weight="medium" className={classes.logotype}>
|
||||
React Material Admin
|
||||
</Typography>
|
||||
<div className={classes.grow} />
|
||||
<Button component={Link} href="https://flatlogic.com/templates/react-material-admin-full" variant={"outlined"} color={"secondary"} style={{marginRight: 24}}>Unlock full version</Button>
|
||||
<div
|
||||
className={classNames(classes.search, {
|
||||
[classes.searchFocused]: isSearchOpen,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={classNames(classes.searchIcon, {
|
||||
[classes.searchIconOpened]: isSearchOpen,
|
||||
})}
|
||||
onClick={() => setSearchOpen(!isSearchOpen)}
|
||||
>
|
||||
<SearchIcon classes={{ root: classes.headerIcon }} />
|
||||
</div>
|
||||
<InputBase
|
||||
placeholder="Search…"
|
||||
classes={{
|
||||
root: classes.inputRoot,
|
||||
input: classes.inputInput,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
aria-haspopup="true"
|
||||
aria-controls="mail-menu"
|
||||
onClick={e => {
|
||||
setNotificationsMenu(e.currentTarget);
|
||||
setIsNotificationsUnread(false);
|
||||
}}
|
||||
className={classes.headerMenuButton}
|
||||
>
|
||||
<Badge
|
||||
badgeContent={isNotificationsUnread ? notifications.length : null}
|
||||
color="warning"
|
||||
>
|
||||
<NotificationsIcon classes={{ root: classes.headerIcon }} />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
aria-haspopup="true"
|
||||
aria-controls="mail-menu"
|
||||
onClick={e => {
|
||||
setMailMenu(e.currentTarget);
|
||||
setIsMailsUnread(false);
|
||||
}}
|
||||
className={classes.headerMenuButton}
|
||||
>
|
||||
<Badge
|
||||
badgeContent={isMailsUnread ? messages.length : null}
|
||||
color="secondary"
|
||||
>
|
||||
<MailIcon classes={{ root: classes.headerIcon }} />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-haspopup="true"
|
||||
color="inherit"
|
||||
className={classes.headerMenuButton}
|
||||
aria-controls="profile-menu"
|
||||
onClick={e => setProfileMenu(e.currentTarget)}
|
||||
>
|
||||
<AccountIcon classes={{ root: classes.headerIcon }} />
|
||||
</IconButton>
|
||||
<Menu
|
||||
id="mail-menu"
|
||||
open={Boolean(mailMenu)}
|
||||
anchorEl={mailMenu}
|
||||
onClose={() => setMailMenu(null)}
|
||||
MenuListProps={{ className: classes.headerMenuList }}
|
||||
className={classes.headerMenu}
|
||||
classes={{ paper: classes.profileMenu }}
|
||||
disableAutoFocusItem
|
||||
>
|
||||
<div className={classes.profileMenuUser}>
|
||||
<Typography variant="h4" weight="medium">
|
||||
New Messages
|
||||
</Typography>
|
||||
<Typography
|
||||
className={classes.profileMenuLink}
|
||||
component="a"
|
||||
color="secondary"
|
||||
>
|
||||
{messages.length} New Messages
|
||||
</Typography>
|
||||
</div>
|
||||
{messages.map(message => (
|
||||
<MenuItem key={message.id} className={classes.messageNotification}>
|
||||
<div className={classes.messageNotificationSide}>
|
||||
<UserAvatar color={message.variant} name={message.name} />
|
||||
<Typography size="sm" color="text" colorBrightness="secondary">
|
||||
{message.time}
|
||||
</Typography>
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
classes.messageNotificationSide,
|
||||
classes.messageNotificationBodySide,
|
||||
)}
|
||||
>
|
||||
<Typography weight="medium" gutterBottom>
|
||||
{message.name}
|
||||
</Typography>
|
||||
<Typography color="text" colorBrightness="secondary">
|
||||
{message.message}
|
||||
</Typography>
|
||||
</div>
|
||||
</MenuItem>
|
||||
))}
|
||||
<Fab
|
||||
variant="extended"
|
||||
color="primary"
|
||||
aria-label="Add"
|
||||
className={classes.sendMessageButton}
|
||||
>
|
||||
Send New Message
|
||||
<SendIcon className={classes.sendButtonIcon} />
|
||||
</Fab>
|
||||
</Menu>
|
||||
<Menu
|
||||
id="notifications-menu"
|
||||
open={Boolean(notificationsMenu)}
|
||||
anchorEl={notificationsMenu}
|
||||
onClose={() => setNotificationsMenu(null)}
|
||||
className={classes.headerMenu}
|
||||
disableAutoFocusItem
|
||||
>
|
||||
{notifications.map(notification => (
|
||||
<MenuItem
|
||||
key={notification.id}
|
||||
onClick={() => setNotificationsMenu(null)}
|
||||
className={classes.headerMenuItem}
|
||||
>
|
||||
<Notification {...notification} typographyVariant="inherit" />
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
<Menu
|
||||
id="profile-menu"
|
||||
open={Boolean(profileMenu)}
|
||||
anchorEl={profileMenu}
|
||||
onClose={() => setProfileMenu(null)}
|
||||
className={classes.headerMenu}
|
||||
classes={{ paper: classes.profileMenu }}
|
||||
disableAutoFocusItem
|
||||
>
|
||||
<div className={classes.profileMenuUser}>
|
||||
<Typography variant="h4" weight="medium">
|
||||
John Smith
|
||||
</Typography>
|
||||
<Typography
|
||||
className={classes.profileMenuLink}
|
||||
component="a"
|
||||
color="primary"
|
||||
href="https://flatlogic.com"
|
||||
>
|
||||
Flalogic.com
|
||||
</Typography>
|
||||
</div>
|
||||
<MenuItem
|
||||
className={classNames(
|
||||
classes.profileMenuItem,
|
||||
classes.headerMenuItem,
|
||||
)}
|
||||
>
|
||||
<AccountIcon className={classes.profileMenuIcon} /> Profile
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className={classNames(
|
||||
classes.profileMenuItem,
|
||||
classes.headerMenuItem,
|
||||
)}
|
||||
>
|
||||
<AccountIcon className={classes.profileMenuIcon} /> Tasks
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className={classNames(
|
||||
classes.profileMenuItem,
|
||||
classes.headerMenuItem,
|
||||
)}
|
||||
>
|
||||
<AccountIcon className={classes.profileMenuIcon} /> Messages
|
||||
</MenuItem>
|
||||
<div className={classes.profileMenuUser}>
|
||||
<Typography
|
||||
className={classes.profileMenuLink}
|
||||
color="primary"
|
||||
onClick={() => signOut(userDispatch, props.history)}
|
||||
>
|
||||
Sign Out
|
||||
</Typography>
|
||||
</div>
|
||||
</Menu>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
);
|
||||
}
|
||||
456
frontend/react-material-admin/src/components/Header/HeaderView.js
vendored
Normal file
456
frontend/react-material-admin/src/components/Header/HeaderView.js
vendored
Normal file
|
|
@ -0,0 +1,456 @@
|
|||
import React from "react";
|
||||
import {
|
||||
AppBar,
|
||||
Toolbar,
|
||||
IconButton,
|
||||
InputBase,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Fab,
|
||||
withStyles
|
||||
} from "@material-ui/core";
|
||||
import {
|
||||
Menu as MenuIcon,
|
||||
MailOutline as MailIcon,
|
||||
NotificationsNone as NotificationsIcon,
|
||||
Person as AccountIcon,
|
||||
Search as SearchIcon,
|
||||
Send as SendIcon,
|
||||
ArrowBack as ArrowBackIcon
|
||||
} from "@material-ui/icons";
|
||||
import { fade } from "@material-ui/core/styles/colorManipulator";
|
||||
import classNames from "classnames";
|
||||
|
||||
import { Badge, Typography } from "../Wrappers";
|
||||
import Notification from "../Notification";
|
||||
import UserAvatar from "../UserAvatar";
|
||||
|
||||
const messages = [
|
||||
{
|
||||
id: 0,
|
||||
variant: "warning",
|
||||
name: "Jane Hew",
|
||||
message: "Hey! How is it going?",
|
||||
time: "9:32"
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
variant: "success",
|
||||
name: "Lloyd Brown",
|
||||
message: "Check out my new Dashboard",
|
||||
time: "9:18"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
variant: "primary",
|
||||
name: "Mark Winstein",
|
||||
message: "I want rearrange the appointment",
|
||||
time: "9:15"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
variant: "secondary",
|
||||
name: "Liana Dutti",
|
||||
message: "Good news from sale department",
|
||||
time: "9:09"
|
||||
}
|
||||
];
|
||||
|
||||
const notifications = [
|
||||
{ id: 0, color: "warning", message: "Check out this awesome ticket" },
|
||||
{
|
||||
id: 1,
|
||||
color: "success",
|
||||
type: "info",
|
||||
message: "What is the best way to get ..."
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
color: "secondary",
|
||||
type: "notification",
|
||||
message: "This is just a simple notification"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
color: "primary",
|
||||
type: "e-commerce",
|
||||
message: "12 new orders has arrived today"
|
||||
}
|
||||
];
|
||||
|
||||
const Header = ({ classes, isSidebarOpened, toggleSidebar, ...props }) => (
|
||||
<AppBar position="fixed" className={classes.appBar}>
|
||||
<Toolbar className={classes.toolbar}>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
onClick={toggleSidebar}
|
||||
className={classNames(
|
||||
classes.headerMenuButton,
|
||||
classes.headerMenuButtonCollapse
|
||||
)}
|
||||
>
|
||||
{isSidebarOpened ? (
|
||||
<ArrowBackIcon
|
||||
classes={{
|
||||
root: classNames(classes.headerIcon, classes.headerIconCollapse)
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<MenuIcon
|
||||
classes={{
|
||||
root: classNames(classes.headerIcon, classes.headerIconCollapse)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</IconButton>
|
||||
<Typography variant="h6" weight="medium" className={classes.logotype}>React Material Admin</Typography>
|
||||
<div className={classes.grow} />
|
||||
<div
|
||||
className={classNames(classes.search, {
|
||||
[classes.searchFocused]: props.isSearchOpen
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={classNames(classes.searchIcon, {
|
||||
[classes.searchIconOpened]: props.isSearchOpen
|
||||
})}
|
||||
onClick={props.toggleSearch}
|
||||
>
|
||||
<SearchIcon classes={{ root: classes.headerIcon }} />
|
||||
</div>
|
||||
<InputBase
|
||||
placeholder="Search…"
|
||||
classes={{
|
||||
root: classes.inputRoot,
|
||||
input: classes.inputInput
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
aria-haspopup="true"
|
||||
aria-controls="mail-menu"
|
||||
onClick={props.openNotificationsMenu}
|
||||
className={classes.headerMenuButton}
|
||||
>
|
||||
<Badge
|
||||
badgeContent={
|
||||
props.isNotificationsUnread ? notifications.length : null
|
||||
}
|
||||
colortheme="warning"
|
||||
>
|
||||
<NotificationsIcon classes={{ root: classes.headerIcon }} />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
aria-haspopup="true"
|
||||
aria-controls="mail-menu"
|
||||
onClick={props.openMailMenu}
|
||||
className={classes.headerMenuButton}
|
||||
>
|
||||
<Badge
|
||||
badgeContent={props.isMailsUnread ? messages.length : null}
|
||||
color="secondary"
|
||||
>
|
||||
<MailIcon classes={{ root: classes.headerIcon }} />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-haspopup="true"
|
||||
color="inherit"
|
||||
className={classes.headerMenuButton}
|
||||
aria-controls="profile-menu"
|
||||
onClick={props.openProfileMenu}
|
||||
>
|
||||
<AccountIcon classes={{ root: classes.headerIcon }} />
|
||||
</IconButton>
|
||||
<Menu
|
||||
id="mail-menu"
|
||||
open={Boolean(props.mailMenu)}
|
||||
anchorEl={props.mailMenu}
|
||||
onClose={props.closeMailMenu}
|
||||
MenuListProps={{ className: classes.headerMenuList }}
|
||||
className={classes.headerMenu}
|
||||
classes={{ paper: classes.profileMenu }}
|
||||
disableAutoFocusItem
|
||||
>
|
||||
<div className={classes.profileMenuUser}>
|
||||
<Typography variant="h4" weight="medium">
|
||||
New Messages
|
||||
</Typography>
|
||||
<Typography
|
||||
className={classes.profileMenuLink}
|
||||
component="a"
|
||||
color="secondary"
|
||||
>
|
||||
{messages.length} New Messages
|
||||
</Typography>
|
||||
</div>
|
||||
{messages.map(message => (
|
||||
<MenuItem key={message.id} className={classes.messageNotification}>
|
||||
<div className={classes.messageNotificationSide}>
|
||||
<UserAvatar color={message.variant} name={message.name} />
|
||||
<Typography size="sm" color="textSecondary">
|
||||
{message.time}
|
||||
</Typography>
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
classes.messageNotificationSide,
|
||||
classes.messageNotificationBodySide
|
||||
)}
|
||||
>
|
||||
<Typography weight="medium" gutterBottom>
|
||||
{message.name}
|
||||
</Typography>
|
||||
<Typography color="textSecondary">{message.message}</Typography>
|
||||
</div>
|
||||
</MenuItem>
|
||||
))}
|
||||
<Fab
|
||||
variant="extended"
|
||||
color="primary"
|
||||
aria-label="Add"
|
||||
className={classes.sendMessageButton}
|
||||
>
|
||||
Send New Message
|
||||
<SendIcon className={classes.sendButtonIcon} />
|
||||
</Fab>
|
||||
</Menu>
|
||||
<Menu
|
||||
id="notifications-menu"
|
||||
open={Boolean(props.notificationsMenu)}
|
||||
anchorEl={props.notificationsMenu}
|
||||
onClose={props.closeNotificationsMenu}
|
||||
className={classes.headerMenu}
|
||||
disableAutoFocusItem
|
||||
>
|
||||
{notifications.map(notification => (
|
||||
<MenuItem
|
||||
key={notification.id}
|
||||
onClick={props.closeNotificationsMenu}
|
||||
className={classes.headerMenuItem}
|
||||
>
|
||||
<Notification {...notification} typographyVariant="inherit" />
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
<Menu
|
||||
id="profile-menu"
|
||||
open={Boolean(props.profileMenu)}
|
||||
anchorEl={props.profileMenu}
|
||||
onClose={props.closeProfileMenu}
|
||||
className={classes.headerMenu}
|
||||
classes={{ paper: classes.profileMenu }}
|
||||
disableAutoFocusItem
|
||||
>
|
||||
<div className={classes.profileMenuUser}>
|
||||
<Typography variant="h4" weight="medium">
|
||||
John Smith
|
||||
</Typography>
|
||||
<Typography
|
||||
className={classes.profileMenuLink}
|
||||
component="a"
|
||||
color="primary"
|
||||
href="https://flatlogic.com"
|
||||
>
|
||||
Flalogic.com
|
||||
</Typography>
|
||||
</div>
|
||||
<MenuItem
|
||||
className={classNames(
|
||||
classes.profileMenuItem,
|
||||
classes.headerMenuItem
|
||||
)}
|
||||
>
|
||||
<AccountIcon className={classes.profileMenuIcon} /> Profile
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className={classNames(
|
||||
classes.profileMenuItem,
|
||||
classes.headerMenuItem
|
||||
)}
|
||||
>
|
||||
<AccountIcon className={classes.profileMenuIcon} /> Tasks
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className={classNames(
|
||||
classes.profileMenuItem,
|
||||
classes.headerMenuItem
|
||||
)}
|
||||
>
|
||||
<AccountIcon className={classes.profileMenuIcon} /> Messages
|
||||
</MenuItem>
|
||||
<div className={classes.profileMenuUser}>
|
||||
<Typography
|
||||
className={classes.profileMenuLink}
|
||||
color="primary"
|
||||
onClick={props.signOut}
|
||||
>
|
||||
Sign Out
|
||||
</Typography>
|
||||
</div>
|
||||
</Menu>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
);
|
||||
|
||||
const styles = theme => ({
|
||||
logotype: {
|
||||
color: "white",
|
||||
marginLeft: theme.spacing.unit * 2.5,
|
||||
marginRight: theme.spacing.unit * 2.5,
|
||||
fontWeight: 500,
|
||||
fontSize: 18,
|
||||
whiteSpace: "nowrap",
|
||||
[theme.breakpoints.down("xs")]: {
|
||||
display: "none"
|
||||
}
|
||||
},
|
||||
appBar: {
|
||||
width: "100vw",
|
||||
zIndex: theme.zIndex.drawer + 1,
|
||||
transition: theme.transitions.create(["margin"], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen
|
||||
})
|
||||
},
|
||||
toolbar: {
|
||||
paddingLeft: theme.spacing.unit * 2,
|
||||
paddingRight: theme.spacing.unit * 2
|
||||
},
|
||||
hide: {
|
||||
display: "none"
|
||||
},
|
||||
grow: {
|
||||
flexGrow: 1
|
||||
},
|
||||
search: {
|
||||
position: "relative",
|
||||
borderRadius: 25,
|
||||
paddingLeft: theme.spacing.unit * 2.5,
|
||||
width: 36,
|
||||
backgroundColor: fade(theme.palette.common.black, 0),
|
||||
transition: theme.transitions.create(["background-color", "width"]),
|
||||
"&:hover": {
|
||||
cursor: "pointer",
|
||||
backgroundColor: fade(theme.palette.common.black, 0.08)
|
||||
}
|
||||
},
|
||||
searchFocused: {
|
||||
backgroundColor: fade(theme.palette.common.black, 0.08),
|
||||
width: "100%",
|
||||
[theme.breakpoints.up("md")]: {
|
||||
width: 250
|
||||
}
|
||||
},
|
||||
searchIcon: {
|
||||
width: 36,
|
||||
right: 0,
|
||||
height: "100%",
|
||||
position: "absolute",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
transition: theme.transitions.create("right"),
|
||||
"&:hover": {
|
||||
cursor: "pointer"
|
||||
}
|
||||
},
|
||||
searchIconOpened: {
|
||||
right: theme.spacing.unit * 1.25
|
||||
},
|
||||
inputRoot: {
|
||||
color: "inherit",
|
||||
width: "100%"
|
||||
},
|
||||
inputInput: {
|
||||
height: 36,
|
||||
padding: 0,
|
||||
paddingRight: 36 + theme.spacing.unit * 1.25,
|
||||
width: "100%"
|
||||
},
|
||||
messageContent: {
|
||||
display: "flex",
|
||||
flexDirection: "column"
|
||||
},
|
||||
headerMenu: {
|
||||
marginTop: theme.spacing.unit * 7
|
||||
},
|
||||
headerMenuList: {
|
||||
display: "flex",
|
||||
flexDirection: "column"
|
||||
},
|
||||
headerMenuItem: {
|
||||
"&:hover, &:focus": {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: "white"
|
||||
}
|
||||
},
|
||||
headerMenuButton: {
|
||||
marginLeft: theme.spacing.unit * 2,
|
||||
padding: theme.spacing.unit / 2
|
||||
},
|
||||
headerMenuButtonCollapse: {
|
||||
marginRight: theme.spacing.unit * 2
|
||||
},
|
||||
headerIcon: {
|
||||
fontSize: 28,
|
||||
color: "rgba(255, 255, 255, 0.35)"
|
||||
},
|
||||
headerIconCollapse: {
|
||||
color: "white"
|
||||
},
|
||||
profileMenu: {
|
||||
minWidth: 265
|
||||
},
|
||||
profileMenuUser: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
padding: theme.spacing.unit * 2
|
||||
},
|
||||
profileMenuItem: {
|
||||
color: theme.palette.text.hint
|
||||
},
|
||||
profileMenuIcon: {
|
||||
marginRight: theme.spacing.unit * 2,
|
||||
color: theme.palette.text.hint
|
||||
},
|
||||
profileMenuLink: {
|
||||
fontSize: 16,
|
||||
textDecoration: "none",
|
||||
"&:hover": {
|
||||
cursor: "pointer"
|
||||
}
|
||||
},
|
||||
messageNotification: {
|
||||
height: "auto",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
"&:hover, &:focus": {
|
||||
backgroundColor: theme.palette.background.light
|
||||
}
|
||||
},
|
||||
messageNotificationSide: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
marginRight: theme.spacing.unit * 2
|
||||
},
|
||||
messageNotificationBodySide: {
|
||||
alignItems: "flex-start",
|
||||
marginRight: 0
|
||||
},
|
||||
sendMessageButton: {
|
||||
margin: theme.spacing.unit * 4,
|
||||
marginTop: theme.spacing.unit * 2,
|
||||
marginBottom: theme.spacing.unit * 2,
|
||||
textTransform: "none"
|
||||
},
|
||||
sendButtonIcon: {
|
||||
marginLeft: theme.spacing.unit * 2
|
||||
}
|
||||
});
|
||||
|
||||
export default withStyles(styles)(Header);
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "Header",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "Header.js"
|
||||
}
|
||||
159
frontend/react-material-admin/src/components/Header/styles.js
vendored
Normal file
159
frontend/react-material-admin/src/components/Header/styles.js
vendored
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
import { makeStyles } from "@material-ui/styles";
|
||||
import { fade } from "@material-ui/core/styles/colorManipulator";
|
||||
|
||||
export default makeStyles(theme => ({
|
||||
logotype: {
|
||||
color: "white",
|
||||
marginLeft: theme.spacing(2.5),
|
||||
marginRight: theme.spacing(2.5),
|
||||
fontWeight: 500,
|
||||
fontSize: 18,
|
||||
whiteSpace: "nowrap",
|
||||
[theme.breakpoints.down("xs")]: {
|
||||
display: "none",
|
||||
},
|
||||
},
|
||||
appBar: {
|
||||
width: "100vw",
|
||||
zIndex: theme.zIndex.drawer + 1,
|
||||
transition: theme.transitions.create(["margin"], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
},
|
||||
toolbar: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(2),
|
||||
},
|
||||
hide: {
|
||||
display: "none",
|
||||
},
|
||||
grow: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
search: {
|
||||
position: "relative",
|
||||
borderRadius: 25,
|
||||
paddingLeft: theme.spacing(2.5),
|
||||
width: 36,
|
||||
backgroundColor: fade(theme.palette.common.black, 0),
|
||||
transition: theme.transitions.create(["background-color", "width"]),
|
||||
"&:hover": {
|
||||
cursor: "pointer",
|
||||
backgroundColor: fade(theme.palette.common.black, 0.08),
|
||||
},
|
||||
},
|
||||
searchFocused: {
|
||||
backgroundColor: fade(theme.palette.common.black, 0.08),
|
||||
width: "100%",
|
||||
[theme.breakpoints.up("md")]: {
|
||||
width: 250,
|
||||
},
|
||||
},
|
||||
searchIcon: {
|
||||
width: 36,
|
||||
right: 0,
|
||||
height: "100%",
|
||||
position: "absolute",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
transition: theme.transitions.create("right"),
|
||||
"&:hover": {
|
||||
cursor: "pointer",
|
||||
},
|
||||
},
|
||||
searchIconOpened: {
|
||||
right: theme.spacing(1.25),
|
||||
},
|
||||
inputRoot: {
|
||||
color: "inherit",
|
||||
width: "100%",
|
||||
},
|
||||
inputInput: {
|
||||
height: 36,
|
||||
padding: 0,
|
||||
paddingRight: 36 + theme.spacing(1.25),
|
||||
width: "100%",
|
||||
},
|
||||
messageContent: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
},
|
||||
headerMenu: {
|
||||
marginTop: theme.spacing(7),
|
||||
},
|
||||
headerMenuList: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
},
|
||||
headerMenuItem: {
|
||||
"&:hover, &:focus": {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: "white",
|
||||
},
|
||||
},
|
||||
headerMenuButton: {
|
||||
marginLeft: theme.spacing(2),
|
||||
padding: theme.spacing(0.5),
|
||||
},
|
||||
headerMenuButtonCollapse: {
|
||||
marginRight: theme.spacing(2),
|
||||
},
|
||||
headerIcon: {
|
||||
fontSize: 28,
|
||||
color: "rgba(255, 255, 255, 0.35)",
|
||||
},
|
||||
headerIconCollapse: {
|
||||
color: "white",
|
||||
},
|
||||
profileMenu: {
|
||||
minWidth: 265,
|
||||
},
|
||||
profileMenuUser: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
padding: theme.spacing(2),
|
||||
},
|
||||
profileMenuItem: {
|
||||
color: theme.palette.text.hint,
|
||||
},
|
||||
profileMenuIcon: {
|
||||
marginRight: theme.spacing(2),
|
||||
color: theme.palette.text.hint,
|
||||
},
|
||||
profileMenuLink: {
|
||||
fontSize: 16,
|
||||
textDecoration: "none",
|
||||
"&:hover": {
|
||||
cursor: "pointer",
|
||||
},
|
||||
},
|
||||
messageNotification: {
|
||||
height: "auto",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
"&:hover, &:focus": {
|
||||
backgroundColor: theme.palette.background.light,
|
||||
},
|
||||
},
|
||||
messageNotificationSide: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
marginRight: theme.spacing(2),
|
||||
},
|
||||
messageNotificationBodySide: {
|
||||
alignItems: "flex-start",
|
||||
marginRight: 0,
|
||||
},
|
||||
sendMessageButton: {
|
||||
margin: theme.spacing(4),
|
||||
marginTop: theme.spacing(2),
|
||||
marginBottom: theme.spacing(2),
|
||||
textTransform: "none",
|
||||
},
|
||||
sendButtonIcon: {
|
||||
marginLeft: theme.spacing(2),
|
||||
},
|
||||
}));
|
||||
66
frontend/react-material-admin/src/components/Layout/Layout.js
vendored
Normal file
66
frontend/react-material-admin/src/components/Layout/Layout.js
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import React from "react";
|
||||
import {
|
||||
Route,
|
||||
Switch,
|
||||
Redirect,
|
||||
withRouter,
|
||||
} from "react-router-dom";
|
||||
import classnames from "classnames";
|
||||
|
||||
// styles
|
||||
import useStyles from "./styles";
|
||||
|
||||
// components
|
||||
import Header from "../Header";
|
||||
import Sidebar from "../Sidebar";
|
||||
|
||||
// pages
|
||||
import Dashboard from "../../pages/dashboard";
|
||||
import Typography from "../../pages/typography";
|
||||
import Notifications from "../../pages/notifications";
|
||||
import Maps from "../../pages/maps";
|
||||
import Tables from "../../pages/tables";
|
||||
import Icons from "../../pages/icons";
|
||||
import Charts from "../../pages/charts";
|
||||
|
||||
// context
|
||||
import { useLayoutState } from "../../context/LayoutContext";
|
||||
|
||||
function Layout(props) {
|
||||
var classes = useStyles();
|
||||
|
||||
// global
|
||||
var layoutState = useLayoutState();
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<>
|
||||
<Header history={props.history} />
|
||||
<Sidebar />
|
||||
<div
|
||||
className={classnames(classes.content, {
|
||||
[classes.contentShift]: layoutState.isSidebarOpened,
|
||||
})}
|
||||
>
|
||||
<div className={classes.fakeToolbar} />
|
||||
<Switch>
|
||||
<Route path="/app/dashboard" component={Dashboard} />
|
||||
<Route path="/app/typography" component={Typography} />
|
||||
<Route path="/app/tables" component={Tables} />
|
||||
<Route path="/app/notifications" component={Notifications} />
|
||||
<Route
|
||||
exact
|
||||
path="/app/ui"
|
||||
render={() => <Redirect to="/app/ui/icons" />}
|
||||
/>
|
||||
<Route path="/app/ui/maps" component={Maps} />
|
||||
<Route path="/app/ui/icons" component={Icons} />
|
||||
<Route path="/app/ui/charts" component={Charts} />
|
||||
</Switch>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default withRouter(Layout);
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "Layout",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "Layout.js"
|
||||
}
|
||||
25
frontend/react-material-admin/src/components/Layout/styles.js
vendored
Normal file
25
frontend/react-material-admin/src/components/Layout/styles.js
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { makeStyles } from "@material-ui/styles";
|
||||
|
||||
export default makeStyles(theme => ({
|
||||
root: {
|
||||
display: "flex",
|
||||
maxWidth: "100vw",
|
||||
overflowX: "hidden",
|
||||
},
|
||||
content: {
|
||||
flexGrow: 1,
|
||||
padding: theme.spacing(3),
|
||||
width: `calc(100vw - 240px)`,
|
||||
minHeight: "100vh",
|
||||
},
|
||||
contentShift: {
|
||||
width: `calc(100vw - ${240 + theme.spacing(6)}px)`,
|
||||
transition: theme.transitions.create(["width", "margin"], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
},
|
||||
fakeToolbar: {
|
||||
...theme.mixins.toolbar,
|
||||
},
|
||||
}));
|
||||
117
frontend/react-material-admin/src/components/Notification/Notification.js
vendored
Normal file
117
frontend/react-material-admin/src/components/Notification/Notification.js
vendored
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
import React from "react";
|
||||
import { Button } from "@material-ui/core";
|
||||
import {
|
||||
NotificationsNone as NotificationsIcon,
|
||||
ThumbUp as ThumbUpIcon,
|
||||
ShoppingCart as ShoppingCartIcon,
|
||||
LocalOffer as TicketIcon,
|
||||
BusinessCenter as DeliveredIcon,
|
||||
SmsFailed as FeedbackIcon,
|
||||
DiscFull as DiscIcon,
|
||||
Email as MessageIcon,
|
||||
Report as ReportIcon,
|
||||
Error as DefenceIcon,
|
||||
AccountBox as CustomerIcon,
|
||||
Done as ShippedIcon,
|
||||
Publish as UploadIcon,
|
||||
} from "@material-ui/icons";
|
||||
import { useTheme } from "@material-ui/styles";
|
||||
import classnames from "classnames";
|
||||
import tinycolor from "tinycolor2";
|
||||
|
||||
// styles
|
||||
import useStyles from "./styles";
|
||||
|
||||
// components
|
||||
import { Typography } from "../Wrappers";
|
||||
|
||||
const typesIcons = {
|
||||
"e-commerce": <ShoppingCartIcon />,
|
||||
notification: <NotificationsIcon />,
|
||||
offer: <TicketIcon />,
|
||||
info: <ThumbUpIcon />,
|
||||
message: <MessageIcon />,
|
||||
feedback: <FeedbackIcon />,
|
||||
customer: <CustomerIcon />,
|
||||
shipped: <ShippedIcon />,
|
||||
delivered: <DeliveredIcon />,
|
||||
defence: <DefenceIcon />,
|
||||
report: <ReportIcon />,
|
||||
upload: <UploadIcon />,
|
||||
disc: <DiscIcon />,
|
||||
};
|
||||
|
||||
export default function Notification({ variant, ...props }) {
|
||||
var classes = useStyles();
|
||||
var theme = useTheme();
|
||||
|
||||
const icon = getIconByType(props.type);
|
||||
const iconWithStyles = React.cloneElement(icon, {
|
||||
classes: {
|
||||
root: classes.notificationIcon,
|
||||
},
|
||||
style: {
|
||||
color:
|
||||
variant !== "contained" &&
|
||||
theme.palette[props.color] &&
|
||||
theme.palette[props.color].main,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames(classes.notificationContainer, props.className, {
|
||||
[classes.notificationContained]: variant === "contained",
|
||||
[classes.notificationContainedShadowless]: props.shadowless,
|
||||
})}
|
||||
style={{
|
||||
backgroundColor:
|
||||
variant === "contained" &&
|
||||
theme.palette[props.color] &&
|
||||
theme.palette[props.color].main,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={classnames(classes.notificationIconContainer, {
|
||||
[classes.notificationIconContainerContained]: variant === "contained",
|
||||
[classes.notificationIconContainerRounded]: variant === "rounded",
|
||||
})}
|
||||
style={{
|
||||
backgroundColor:
|
||||
variant === "rounded" &&
|
||||
theme.palette[props.color] &&
|
||||
tinycolor(theme.palette[props.color].main)
|
||||
.setAlpha(0.15)
|
||||
.toRgbString(),
|
||||
}}
|
||||
>
|
||||
{iconWithStyles}
|
||||
</div>
|
||||
<div className={classes.messageContainer}>
|
||||
<Typography
|
||||
className={classnames({
|
||||
[classes.containedTypography]: variant === "contained",
|
||||
})}
|
||||
variant={props.typographyVariant}
|
||||
size={variant !== "contained" && !props.typographyVariant && "md"}
|
||||
>
|
||||
{props.message}
|
||||
</Typography>
|
||||
{props.extraButton && props.extraButtonClick && (
|
||||
<Button
|
||||
onClick={props.extraButtonClick}
|
||||
disableRipple
|
||||
className={classes.extraButton}
|
||||
>
|
||||
{props.extraButton}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ####################################################################
|
||||
function getIconByType(type = "offer") {
|
||||
return typesIcons[type];
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "Notification",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "Notification.js"
|
||||
}
|
||||
47
frontend/react-material-admin/src/components/Notification/styles.js
vendored
Normal file
47
frontend/react-material-admin/src/components/Notification/styles.js
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import { makeStyles } from "@material-ui/styles";
|
||||
|
||||
export default makeStyles(theme => ({
|
||||
notificationContainer: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
notificationContained: {
|
||||
borderRadius: 45,
|
||||
height: 45,
|
||||
boxShadow: theme.customShadows.widgetDark,
|
||||
},
|
||||
notificationContainedShadowless: {
|
||||
boxShadow: "none",
|
||||
},
|
||||
notificationIconContainer: {
|
||||
minWidth: 45,
|
||||
height: 45,
|
||||
borderRadius: 45,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
fontSize: 24,
|
||||
},
|
||||
notificationIconContainerContained: {
|
||||
fontSize: 18,
|
||||
color: "#FFFFFF80",
|
||||
},
|
||||
notificationIconContainerRounded: {
|
||||
marginRight: theme.spacing(2),
|
||||
},
|
||||
containedTypography: {
|
||||
color: "white",
|
||||
},
|
||||
messageContainer: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
flexGrow: 1,
|
||||
},
|
||||
extraButton: {
|
||||
color: "white",
|
||||
"&:hover, &:focus": {
|
||||
background: "transparent",
|
||||
},
|
||||
},
|
||||
}));
|
||||
30
frontend/react-material-admin/src/components/PageTitle/PageTitle.js
vendored
Normal file
30
frontend/react-material-admin/src/components/PageTitle/PageTitle.js
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import React from "react";
|
||||
import { Button } from "@material-ui/core";
|
||||
|
||||
// styles
|
||||
import useStyles from "./styles";
|
||||
|
||||
// components
|
||||
import { Typography } from "../Wrappers";
|
||||
|
||||
export default function PageTitle(props) {
|
||||
var classes = useStyles();
|
||||
|
||||
return (
|
||||
<div className={classes.pageTitleContainer}>
|
||||
<Typography className={classes.typo} variant="h1" size="sm">
|
||||
{props.title}
|
||||
</Typography>
|
||||
{props.button && (
|
||||
<Button
|
||||
classes={{ root: classes.button }}
|
||||
variant="contained"
|
||||
size="large"
|
||||
color="secondary"
|
||||
>
|
||||
{props.button}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "PageTitle",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "PageTitle.js"
|
||||
}
|
||||
20
frontend/react-material-admin/src/components/PageTitle/styles.js
vendored
Normal file
20
frontend/react-material-admin/src/components/PageTitle/styles.js
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { makeStyles } from "@material-ui/styles";
|
||||
|
||||
export default makeStyles(theme => ({
|
||||
pageTitleContainer: {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: theme.spacing(4),
|
||||
marginTop: theme.spacing(5),
|
||||
},
|
||||
typo: {
|
||||
color: theme.palette.text.hint,
|
||||
},
|
||||
button: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
textTransform: "none",
|
||||
"&:active": {
|
||||
boxShadow: theme.customShadows.widgetWide,
|
||||
},
|
||||
},
|
||||
}));
|
||||
156
frontend/react-material-admin/src/components/Sidebar/Sidebar.js
vendored
Normal file
156
frontend/react-material-admin/src/components/Sidebar/Sidebar.js
vendored
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { Drawer, IconButton, List } from "@material-ui/core";
|
||||
import {
|
||||
Home as HomeIcon,
|
||||
NotificationsNone as NotificationsIcon,
|
||||
FormatSize as TypographyIcon,
|
||||
FilterNone as UIElementsIcon,
|
||||
BorderAll as TableIcon,
|
||||
QuestionAnswer as SupportIcon,
|
||||
LibraryBooks as LibraryIcon,
|
||||
HelpOutline as FAQIcon,
|
||||
ArrowBack as ArrowBackIcon,
|
||||
} from "@material-ui/icons";
|
||||
import { useTheme } from "@material-ui/styles";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import classNames from "classnames";
|
||||
|
||||
// styles
|
||||
import useStyles from "./styles";
|
||||
|
||||
// components
|
||||
import SidebarLink from "./components/SidebarLink/SidebarLink";
|
||||
import Dot from "./components/Dot";
|
||||
|
||||
// context
|
||||
import {
|
||||
useLayoutState,
|
||||
useLayoutDispatch,
|
||||
toggleSidebar,
|
||||
} from "../../context/LayoutContext";
|
||||
|
||||
const structure = [
|
||||
{ id: 0, label: "Dashboard", link: "/app/dashboard", icon: <HomeIcon /> },
|
||||
{
|
||||
id: 1,
|
||||
label: "Typography",
|
||||
link: "/app/typography",
|
||||
icon: <TypographyIcon />,
|
||||
},
|
||||
{ id: 2, label: "Tables", link: "/app/tables", icon: <TableIcon /> },
|
||||
{
|
||||
id: 3,
|
||||
label: "Notifications",
|
||||
link: "/app/notifications",
|
||||
icon: <NotificationsIcon />,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
label: "UI Elements",
|
||||
link: "/app/ui",
|
||||
icon: <UIElementsIcon />,
|
||||
children: [
|
||||
{ label: "Icons", link: "/app/ui/icons" },
|
||||
{ label: "Charts", link: "/app/ui/charts" },
|
||||
{ label: "Maps", link: "/app/ui/maps" },
|
||||
],
|
||||
},
|
||||
{ id: 5, type: "divider" },
|
||||
{ id: 6, type: "title", label: "HELP" },
|
||||
{ id: 7, label: "Library", link: "", icon: <LibraryIcon /> },
|
||||
{ id: 8, label: "Support", link: "", icon: <SupportIcon /> },
|
||||
{ id: 9, label: "FAQ", link: "", icon: <FAQIcon /> },
|
||||
{ id: 10, type: "divider" },
|
||||
{ id: 11, type: "title", label: "PROJECTS" },
|
||||
{
|
||||
id: 12,
|
||||
label: "My recent",
|
||||
link: "",
|
||||
icon: <Dot size="small" color="warning" />,
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
label: "Starred",
|
||||
link: "",
|
||||
icon: <Dot size="small" color="primary" />,
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
label: "Background",
|
||||
link: "",
|
||||
icon: <Dot size="small" color="secondary" />,
|
||||
},
|
||||
];
|
||||
|
||||
function Sidebar({ location }) {
|
||||
var classes = useStyles();
|
||||
var theme = useTheme();
|
||||
|
||||
// global
|
||||
var { isSidebarOpened } = useLayoutState();
|
||||
var layoutDispatch = useLayoutDispatch();
|
||||
|
||||
// local
|
||||
var [isPermanent, setPermanent] = useState(true);
|
||||
|
||||
useEffect(function() {
|
||||
window.addEventListener("resize", handleWindowWidthChange);
|
||||
handleWindowWidthChange();
|
||||
return function cleanup() {
|
||||
window.removeEventListener("resize", handleWindowWidthChange);
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
variant={isPermanent ? "permanent" : "temporary"}
|
||||
className={classNames(classes.drawer, {
|
||||
[classes.drawerOpen]: isSidebarOpened,
|
||||
[classes.drawerClose]: !isSidebarOpened,
|
||||
})}
|
||||
classes={{
|
||||
paper: classNames({
|
||||
[classes.drawerOpen]: isSidebarOpened,
|
||||
[classes.drawerClose]: !isSidebarOpened,
|
||||
}),
|
||||
}}
|
||||
open={isSidebarOpened}
|
||||
>
|
||||
<div className={classes.toolbar} />
|
||||
<div className={classes.mobileBackButton}>
|
||||
<IconButton onClick={() => toggleSidebar(layoutDispatch)}>
|
||||
<ArrowBackIcon
|
||||
classes={{
|
||||
root: classNames(classes.headerIcon, classes.headerIconCollapse),
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</div>
|
||||
<List className={classes.sidebarList}>
|
||||
{structure.map(link => (
|
||||
<SidebarLink
|
||||
key={link.id}
|
||||
location={location}
|
||||
isSidebarOpened={isSidebarOpened}
|
||||
{...link}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
</Drawer>
|
||||
);
|
||||
|
||||
// ##################################################################
|
||||
function handleWindowWidthChange() {
|
||||
var windowWidth = window.innerWidth;
|
||||
var breakpointWidth = theme.breakpoints.values.md;
|
||||
var isSmallScreen = windowWidth < breakpointWidth;
|
||||
|
||||
if (isSmallScreen && isPermanent) {
|
||||
setPermanent(false);
|
||||
} else if (!isSmallScreen && !isPermanent) {
|
||||
setPermanent(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(Sidebar);
|
||||
140
frontend/react-material-admin/src/components/Sidebar/SidebarView.js
vendored
Normal file
140
frontend/react-material-admin/src/components/Sidebar/SidebarView.js
vendored
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Drawer,
|
||||
IconButton,
|
||||
List,
|
||||
withStyles } from "@material-ui/core";
|
||||
import {
|
||||
Home as HomeIcon,
|
||||
NotificationsNone as NotificationsIcon,
|
||||
FormatSize as TypographyIcon,
|
||||
FilterNone as UIElementsIcon,
|
||||
BorderAll as TableIcon,
|
||||
QuestionAnswer as SupportIcon,
|
||||
LibraryBooks as LibraryIcon,
|
||||
HelpOutline as FAQIcon,
|
||||
ArrowBack as ArrowBackIcon,
|
||||
} from "@material-ui/icons";
|
||||
import classNames from 'classnames';
|
||||
|
||||
import SidebarLink from './components/SidebarLink/SidebarLinkContainer';
|
||||
import Dot from './components/Dot';
|
||||
|
||||
const structure = [
|
||||
{ id: 0, label: 'Dashboard', link: '/app/dashboard', icon: <HomeIcon /> },
|
||||
{ id: 1, label: 'Typography', link: '/app/typography', icon: <TypographyIcon /> },
|
||||
{ id: 2, label: 'Tables', link: '/app/tables', icon: <TableIcon /> },
|
||||
{ id: 3, label: 'Notifications', link: '/app/notifications', icon: <NotificationsIcon />},
|
||||
{
|
||||
id: 4,
|
||||
label: 'UI Elements',
|
||||
link: '/app/ui',
|
||||
icon: <UIElementsIcon />,
|
||||
children: [
|
||||
{ label: 'Icons', link: '/app/ui/icons' },
|
||||
{ label: 'Charts', link: '/app/ui/charts' },
|
||||
{ label: 'Maps', link: '/app/ui/maps' },
|
||||
],
|
||||
},
|
||||
{ id: 5, type: 'divider' },
|
||||
{ id: 6, type: 'title', label: 'HELP' },
|
||||
{ id: 7, label: 'Library', link: '', icon: <LibraryIcon /> },
|
||||
{ id: 8, label: 'Support', link: '', icon: <SupportIcon /> },
|
||||
{ id: 9, label: 'FAQ', link: '', icon: <FAQIcon />},
|
||||
{ id: 10, type: 'divider' },
|
||||
{ id: 11, type: 'title', label: 'PROJECTS' },
|
||||
{ id: 12, label: 'My recent', link: '', icon: <Dot size="small" color="secondary" /> },
|
||||
{ id: 13, label: 'Starred', link: '', icon: <Dot size="small" color="primary" /> },
|
||||
{ id: 14, label: 'Background', link: '', icon: <Dot size="small" color="secondary" /> },
|
||||
];
|
||||
|
||||
const SidebarView = ({ classes, theme, toggleSidebar, isSidebarOpened, isPermanent, location }) => {
|
||||
return (
|
||||
<Drawer
|
||||
variant={isPermanent ? 'permanent' : 'temporary'}
|
||||
className={classNames(classes.drawer, {
|
||||
[classes.drawerOpen]: isSidebarOpened,
|
||||
[classes.drawerClose]: !isSidebarOpened,
|
||||
})}
|
||||
classes={{
|
||||
paper: classNames(classes.drawer, {
|
||||
[classes.drawerOpen]: isSidebarOpened,
|
||||
[classes.drawerClose]: !isSidebarOpened,
|
||||
}),
|
||||
}}
|
||||
open={isSidebarOpened}
|
||||
>
|
||||
<div className={classes.mobileBackButton}>
|
||||
<IconButton
|
||||
onClick={toggleSidebar}
|
||||
>
|
||||
<ArrowBackIcon classes={{ root: classNames(classes.headerIcon, classes.headerIconCollapse) }} />
|
||||
</IconButton>
|
||||
</div>
|
||||
<List className={classes.sidebarList}>
|
||||
{structure.map(link => <SidebarLink key={link.id} location={location} isSidebarOpened={isSidebarOpened} {...link} />)}
|
||||
</List>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
const drawerWidth = 240;
|
||||
|
||||
const styles = theme => ({
|
||||
menuButton: {
|
||||
marginLeft: 12,
|
||||
marginRight: 36,
|
||||
},
|
||||
hide: {
|
||||
display: 'none',
|
||||
},
|
||||
drawer: {
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
whiteSpace: 'nowrap',
|
||||
top: theme.spacing.unit * 8,
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
top: 0,
|
||||
}
|
||||
},
|
||||
drawerOpen: {
|
||||
width: drawerWidth,
|
||||
transition: theme.transitions.create('width', {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
},
|
||||
drawerClose: {
|
||||
transition: theme.transitions.create('width', {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
overflowX: 'hidden',
|
||||
width: theme.spacing.unit * 7 + 40,
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
width: drawerWidth,
|
||||
}
|
||||
},
|
||||
toolbar: {
|
||||
...theme.mixins.toolbar,
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
display: 'none',
|
||||
}
|
||||
},
|
||||
content: {
|
||||
flexGrow: 1,
|
||||
padding: theme.spacing.unit * 3,
|
||||
},
|
||||
mobileBackButton: {
|
||||
marginTop: theme.spacing.unit * .5,
|
||||
marginLeft: theme.spacing.unit * 3,
|
||||
[theme.breakpoints.only("sm")]: {
|
||||
marginTop: theme.spacing.unit * .625,
|
||||
},
|
||||
[theme.breakpoints.up("md")]: {
|
||||
display: 'none',
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default withStyles(styles, { withTheme: true })(SidebarView);
|
||||
40
frontend/react-material-admin/src/components/Sidebar/components/Dot.js
vendored
Normal file
40
frontend/react-material-admin/src/components/Sidebar/components/Dot.js
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import React from "react";
|
||||
import { makeStyles, useTheme } from "@material-ui/styles";
|
||||
import classnames from "classnames";
|
||||
|
||||
// styles
|
||||
var useStyles = makeStyles(theme => ({
|
||||
dotBase: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
backgroundColor: theme.palette.text.hint,
|
||||
borderRadius: "50%",
|
||||
transition: theme.transitions.create("background-color"),
|
||||
},
|
||||
dotSmall: {
|
||||
width: 5,
|
||||
height: 5
|
||||
},
|
||||
dotLarge: {
|
||||
width: 11,
|
||||
height: 11,
|
||||
},
|
||||
}));
|
||||
|
||||
export default function Dot({ size, color }) {
|
||||
var classes = useStyles();
|
||||
var theme = useTheme();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames(classes.dotBase, {
|
||||
[classes.dotLarge]: size === "large",
|
||||
[classes.dotSmall]: size === "small",
|
||||
})}
|
||||
style={{
|
||||
backgroundColor:
|
||||
color && theme.palette[color] && theme.palette[color].main,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
145
frontend/react-material-admin/src/components/Sidebar/components/SidebarLink/SidebarLink.js
vendored
Normal file
145
frontend/react-material-admin/src/components/Sidebar/components/SidebarLink/SidebarLink.js
vendored
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
import React, { useState } from "react";
|
||||
import {
|
||||
Collapse,
|
||||
Divider,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import { Inbox as InboxIcon } from "@material-ui/icons";
|
||||
import { Link } from "react-router-dom";
|
||||
import classnames from "classnames";
|
||||
|
||||
// styles
|
||||
import useStyles from "./styles";
|
||||
|
||||
// components
|
||||
import Dot from "../Dot";
|
||||
|
||||
export default function SidebarLink({
|
||||
link,
|
||||
icon,
|
||||
label,
|
||||
children,
|
||||
location,
|
||||
isSidebarOpened,
|
||||
nested,
|
||||
type,
|
||||
}) {
|
||||
var classes = useStyles();
|
||||
|
||||
// local
|
||||
var [isOpen, setIsOpen] = useState(false);
|
||||
var isLinkActive =
|
||||
link &&
|
||||
(location.pathname === link || location.pathname.indexOf(link) !== -1);
|
||||
|
||||
if (type === "title")
|
||||
return (
|
||||
<Typography
|
||||
className={classnames(classes.linkText, classes.sectionTitle, {
|
||||
[classes.linkTextHidden]: !isSidebarOpened,
|
||||
})}
|
||||
>
|
||||
{label}
|
||||
</Typography>
|
||||
);
|
||||
|
||||
if (type === "divider") return <Divider className={classes.divider} />;
|
||||
|
||||
if (!children)
|
||||
return (
|
||||
<ListItem
|
||||
button
|
||||
component={link && Link}
|
||||
to={link}
|
||||
className={classes.link}
|
||||
classes={{
|
||||
root: classnames(classes.linkRoot, {
|
||||
[classes.linkActive]: isLinkActive && !nested,
|
||||
[classes.linkNested]: nested,
|
||||
}),
|
||||
}}
|
||||
disableRipple
|
||||
>
|
||||
<ListItemIcon
|
||||
className={classnames(classes.linkIcon, {
|
||||
[classes.linkIconActive]: isLinkActive,
|
||||
})}
|
||||
>
|
||||
{nested ? <Dot color={isLinkActive && "primary"} /> : icon}
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
classes={{
|
||||
primary: classnames(classes.linkText, {
|
||||
[classes.linkTextActive]: isLinkActive,
|
||||
[classes.linkTextHidden]: !isSidebarOpened,
|
||||
}),
|
||||
}}
|
||||
primary={label}
|
||||
/>
|
||||
</ListItem>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListItem
|
||||
button
|
||||
component={link && Link}
|
||||
onClick={toggleCollapse}
|
||||
className={classes.link}
|
||||
to={link}
|
||||
disableRipple
|
||||
>
|
||||
<ListItemIcon
|
||||
className={classnames(classes.linkIcon, {
|
||||
[classes.linkIconActive]: isLinkActive,
|
||||
})}
|
||||
>
|
||||
{icon ? icon : <InboxIcon />}
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
classes={{
|
||||
primary: classnames(classes.linkText, {
|
||||
[classes.linkTextActive]: isLinkActive,
|
||||
[classes.linkTextHidden]: !isSidebarOpened,
|
||||
}),
|
||||
}}
|
||||
primary={label}
|
||||
/>
|
||||
</ListItem>
|
||||
{children && (
|
||||
<Collapse
|
||||
in={isOpen && isSidebarOpened}
|
||||
timeout="auto"
|
||||
unmountOnExit
|
||||
className={classes.nestedList}
|
||||
>
|
||||
<List component="div" disablePadding>
|
||||
{children.map(childrenLink => (
|
||||
<SidebarLink
|
||||
key={childrenLink && childrenLink.link}
|
||||
location={location}
|
||||
isSidebarOpened={isSidebarOpened}
|
||||
classes={classes}
|
||||
nested
|
||||
{...childrenLink}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
</Collapse>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
// ###########################################################
|
||||
|
||||
function toggleCollapse(e) {
|
||||
if (isSidebarOpened) {
|
||||
e.preventDefault();
|
||||
setIsOpen(!isOpen);
|
||||
}
|
||||
}
|
||||
}
|
||||
56
frontend/react-material-admin/src/components/Sidebar/components/SidebarLink/styles.js
vendored
Normal file
56
frontend/react-material-admin/src/components/Sidebar/components/SidebarLink/styles.js
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { makeStyles } from "@material-ui/styles";
|
||||
|
||||
export default makeStyles(theme => ({
|
||||
link: {
|
||||
textDecoration: "none",
|
||||
"&:hover, &:focus": {
|
||||
backgroundColor: theme.palette.background.light,
|
||||
},
|
||||
},
|
||||
linkActive: {
|
||||
backgroundColor: theme.palette.background.light,
|
||||
},
|
||||
linkNested: {
|
||||
paddingLeft: 0,
|
||||
"&:hover, &:focus": {
|
||||
backgroundColor: "#FFFFFF",
|
||||
},
|
||||
},
|
||||
linkIcon: {
|
||||
marginRight: theme.spacing(1),
|
||||
color: theme.palette.text.secondary + "99",
|
||||
transition: theme.transitions.create("color"),
|
||||
width: 24,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
},
|
||||
linkIconActive: {
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
linkText: {
|
||||
padding: 0,
|
||||
color: theme.palette.text.secondary + "CC",
|
||||
transition: theme.transitions.create(["opacity", "color"]),
|
||||
fontSize: 16,
|
||||
},
|
||||
linkTextActive: {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
linkTextHidden: {
|
||||
opacity: 0,
|
||||
},
|
||||
nestedList: {
|
||||
paddingLeft: theme.spacing(2) + 30,
|
||||
},
|
||||
sectionTitle: {
|
||||
marginLeft: theme.spacing(4.5),
|
||||
marginTop: theme.spacing(2),
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
divider: {
|
||||
marginTop: theme.spacing(2),
|
||||
marginBottom: theme.spacing(4),
|
||||
height: 1,
|
||||
backgroundColor: "#D8D8D880",
|
||||
},
|
||||
}));
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "Sidebar",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "Sidebar.js"
|
||||
}
|
||||
59
frontend/react-material-admin/src/components/Sidebar/styles.js
vendored
Normal file
59
frontend/react-material-admin/src/components/Sidebar/styles.js
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import { makeStyles } from "@material-ui/styles";
|
||||
|
||||
const drawerWidth = 240;
|
||||
|
||||
export default makeStyles(theme => ({
|
||||
menuButton: {
|
||||
marginLeft: 12,
|
||||
marginRight: 36,
|
||||
},
|
||||
hide: {
|
||||
display: "none",
|
||||
},
|
||||
drawer: {
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
whiteSpace: "nowrap",
|
||||
},
|
||||
drawerOpen: {
|
||||
width: drawerWidth,
|
||||
transition: theme.transitions.create("width", {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
},
|
||||
drawerClose: {
|
||||
transition: theme.transitions.create("width", {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
overflowX: "hidden",
|
||||
width: theme.spacing(7) + 40,
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
width: drawerWidth,
|
||||
},
|
||||
},
|
||||
toolbar: {
|
||||
...theme.mixins.toolbar,
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
display: "none",
|
||||
},
|
||||
},
|
||||
content: {
|
||||
flexGrow: 1,
|
||||
padding: theme.spacing(3),
|
||||
},
|
||||
/* sidebarList: {
|
||||
marginTop: theme.spacing(6),
|
||||
}, */
|
||||
mobileBackButton: {
|
||||
marginTop: theme.spacing(0.5),
|
||||
marginLeft: theme.spacing(3),
|
||||
[theme.breakpoints.only("sm")]: {
|
||||
marginTop: theme.spacing(0.625),
|
||||
},
|
||||
[theme.breakpoints.up("md")]: {
|
||||
display: "none",
|
||||
},
|
||||
},
|
||||
}));
|
||||
27
frontend/react-material-admin/src/components/UserAvatar/UserAvatar.js
vendored
Normal file
27
frontend/react-material-admin/src/components/UserAvatar/UserAvatar.js
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import React from "react";
|
||||
import { useTheme } from "@material-ui/styles";
|
||||
|
||||
// styles
|
||||
import useStyles from "./styles";
|
||||
|
||||
// components
|
||||
import { Typography } from "../Wrappers";
|
||||
|
||||
export default function UserAvatar({ color = "primary", ...props }) {
|
||||
var classes = useStyles();
|
||||
var theme = useTheme();
|
||||
|
||||
var letters = props.name
|
||||
.split(" ")
|
||||
.map(word => word[0])
|
||||
.join("");
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes.avatar}
|
||||
style={{ backgroundColor: theme.palette[color].main }}
|
||||
>
|
||||
<Typography className={classes.text}>{letters}</Typography>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "UserAvatar",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "UserAvatar.js"
|
||||
}
|
||||
15
frontend/react-material-admin/src/components/UserAvatar/styles.js
vendored
Normal file
15
frontend/react-material-admin/src/components/UserAvatar/styles.js
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { makeStyles } from "@material-ui/styles";
|
||||
|
||||
export default makeStyles(() => ({
|
||||
avatar: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
borderRadius: "50%",
|
||||
},
|
||||
text: {
|
||||
color: "white",
|
||||
},
|
||||
}));
|
||||
87
frontend/react-material-admin/src/components/Widget/Widget.js
vendored
Normal file
87
frontend/react-material-admin/src/components/Widget/Widget.js
vendored
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import React, { useState } from "react";
|
||||
import {
|
||||
Paper,
|
||||
IconButton,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import { MoreVert as MoreIcon } from "@material-ui/icons";
|
||||
import classnames from "classnames";
|
||||
|
||||
// styles
|
||||
import useStyles from "./styles";
|
||||
|
||||
export default function Widget({
|
||||
children,
|
||||
title,
|
||||
noBodyPadding,
|
||||
bodyClass,
|
||||
disableWidgetMenu,
|
||||
header,
|
||||
...props
|
||||
}) {
|
||||
var classes = useStyles();
|
||||
|
||||
// local
|
||||
var [moreButtonRef, setMoreButtonRef] = useState(null);
|
||||
var [isMoreMenuOpen, setMoreMenuOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className={classes.widgetWrapper}>
|
||||
<Paper className={classes.paper} classes={{ root: classes.widgetRoot }}>
|
||||
<div className={classes.widgetHeader}>
|
||||
{header ? (
|
||||
header
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<Typography variant="h5" color="textSecondary">
|
||||
{title}
|
||||
</Typography>
|
||||
{!disableWidgetMenu && (
|
||||
<IconButton
|
||||
color="primary"
|
||||
classes={{ root: classes.moreButton }}
|
||||
aria-owns="widget-menu"
|
||||
aria-haspopup="true"
|
||||
onClick={() => setMoreMenuOpen(true)}
|
||||
buttonRef={setMoreButtonRef}
|
||||
>
|
||||
<MoreIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={classnames(classes.widgetBody, {
|
||||
[classes.noPadding]: noBodyPadding,
|
||||
[bodyClass]: bodyClass,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</Paper>
|
||||
<Menu
|
||||
id="widget-menu"
|
||||
open={isMoreMenuOpen}
|
||||
anchorEl={moreButtonRef}
|
||||
onClose={() => setMoreMenuOpen(false)}
|
||||
disableAutoFocusItem
|
||||
>
|
||||
<MenuItem>
|
||||
<Typography>Edit</Typography>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<Typography>Copy</Typography>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<Typography>Delete</Typography>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<Typography>Print</Typography>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
122
frontend/react-material-admin/src/components/Widget/WidgetView.js
vendored
Normal file
122
frontend/react-material-admin/src/components/Widget/WidgetView.js
vendored
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import React from "react";
|
||||
import classnames from "classnames";
|
||||
import {
|
||||
Paper,
|
||||
IconButton,
|
||||
Menu,
|
||||
MenuItem,
|
||||
withStyles
|
||||
} from "@material-ui/core";
|
||||
import { MoreVert as MoreIcon } from "@material-ui/icons";
|
||||
import Typography from "@material-ui/core/es/Typography/Typography";
|
||||
|
||||
const Widget = ({
|
||||
classes,
|
||||
children,
|
||||
title,
|
||||
noBodyPadding,
|
||||
bodyClass,
|
||||
className,
|
||||
disableWidgetMenu,
|
||||
...props
|
||||
}) => (
|
||||
<div className={classes.widgetWrapper}>
|
||||
<Paper className={classes.paper} classes={{ root: classes.widgetRoot }}>
|
||||
<div className={classes.widgetHeader}>
|
||||
{props.header ? (
|
||||
props.header
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<Typography variant="h5" color="textSecondary">
|
||||
{title}
|
||||
</Typography>
|
||||
{!disableWidgetMenu && (
|
||||
<IconButton
|
||||
color="primary"
|
||||
classes={{ root: classes.moreButton }}
|
||||
aria-owns="widget-menu"
|
||||
aria-haspopup="true"
|
||||
onClick={() => props.setMoreMenuOpen(true)}
|
||||
buttonRef={props.setMoreButtonRef}
|
||||
>
|
||||
<MoreIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={classnames(classes.widgetBody, {
|
||||
[classes.noPadding]: noBodyPadding,
|
||||
[bodyClass]: bodyClass
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</Paper>
|
||||
<Menu
|
||||
id="widget-menu"
|
||||
open={props.isMoreMenuOpen}
|
||||
anchorEl={props.moreButtonRef}
|
||||
onClose={() => props.setMoreMenuOpen(false)}
|
||||
disableAutoFocusItem
|
||||
>
|
||||
<MenuItem>
|
||||
<Typography>Edit</Typography>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<Typography>Copy</Typography>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<Typography>Delete</Typography>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<Typography>Print</Typography>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
|
||||
const styles = theme => ({
|
||||
widgetWrapper: {
|
||||
display: "flex",
|
||||
minHeight: "100%"
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing.unit * 3,
|
||||
paddingBottom: theme.spacing.unit,
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center"
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing.unit * 3,
|
||||
paddingRight: theme.spacing.unit * 3,
|
||||
paddingLeft: theme.spacing.unit * 3
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0
|
||||
},
|
||||
paper: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
overflow: "hidden"
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing.unit,
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: "rgba(255, 255, 255, 0.35)"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default withStyles(styles, { withTheme: true })(Widget);
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "Widget",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "Widget.js"
|
||||
}
|
||||
43
frontend/react-material-admin/src/components/Widget/styles.js
vendored
Normal file
43
frontend/react-material-admin/src/components/Widget/styles.js
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { makeStyles } from "@material-ui/styles";
|
||||
|
||||
export default makeStyles(theme => ({
|
||||
widgetWrapper: {
|
||||
display: "flex",
|
||||
minHeight: "100%",
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(1),
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
paper: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
overflow: "hidden",
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing(1),
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: "rgba(255, 255, 255, 0.35)",
|
||||
},
|
||||
},
|
||||
}));
|
||||
181
frontend/react-material-admin/src/components/Wrappers/Wrappers.js
vendored
Normal file
181
frontend/react-material-admin/src/components/Wrappers/Wrappers.js
vendored
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
import React from "react";
|
||||
import {
|
||||
withStyles,
|
||||
Badge as BadgeBase,
|
||||
Typography as TypographyBase,
|
||||
Button as ButtonBase,
|
||||
} from "@material-ui/core";
|
||||
import { useTheme, makeStyles } from "@material-ui/styles";
|
||||
import classnames from "classnames";
|
||||
|
||||
// styles
|
||||
var useStyles = makeStyles(theme => ({
|
||||
badge: {
|
||||
fontWeight: 600,
|
||||
height: 16,
|
||||
minWidth: 16,
|
||||
},
|
||||
}));
|
||||
|
||||
function Badge({ children, colorBrightness, color, ...props }) {
|
||||
var classes = useStyles();
|
||||
var theme = useTheme();
|
||||
var Styled = createStyled({
|
||||
badge: {
|
||||
backgroundColor: getColor(color, theme, colorBrightness),
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Styled>
|
||||
{styledProps => (
|
||||
<BadgeBase
|
||||
classes={{
|
||||
badge: classnames(classes.badge, styledProps.classes.badge),
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</BadgeBase>
|
||||
)}
|
||||
</Styled>
|
||||
);
|
||||
}
|
||||
|
||||
function Typography({
|
||||
children,
|
||||
weight,
|
||||
size,
|
||||
colorBrightness,
|
||||
color,
|
||||
...props
|
||||
}) {
|
||||
var theme = useTheme();
|
||||
|
||||
return (
|
||||
<TypographyBase
|
||||
style={{
|
||||
color: getColor(color, theme, colorBrightness),
|
||||
fontWeight: getFontWeight(weight),
|
||||
fontSize: getFontSize(size, props.variant, theme),
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</TypographyBase>
|
||||
);
|
||||
}
|
||||
|
||||
function Button({ children, color, className, ...props }) {
|
||||
var theme = useTheme();
|
||||
|
||||
var Styled = createStyled({
|
||||
root: {
|
||||
color: getColor(color, theme),
|
||||
},
|
||||
contained: {
|
||||
backgroundColor: getColor(color, theme),
|
||||
boxShadow: theme.customShadows.widget,
|
||||
color: `${color ? "white" : theme.palette.text.primary} !important`,
|
||||
"&:hover": {
|
||||
backgroundColor: getColor(color, theme, "light"),
|
||||
boxShadow: theme.customShadows.widgetWide,
|
||||
},
|
||||
"&:active": {
|
||||
boxShadow: theme.customShadows.widgetWide,
|
||||
},
|
||||
},
|
||||
outlined: {
|
||||
color: getColor(color, theme),
|
||||
borderColor: getColor(color, theme),
|
||||
},
|
||||
select: {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: "#fff",
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Styled>
|
||||
{({ classes }) => (
|
||||
<ButtonBase
|
||||
classes={{
|
||||
contained: classes.contained,
|
||||
root: classes.root,
|
||||
outlined: classes.outlined,
|
||||
}}
|
||||
{...props}
|
||||
className={classnames(
|
||||
{
|
||||
[classes.select]: props.select,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</ButtonBase>
|
||||
)}
|
||||
</Styled>
|
||||
);
|
||||
}
|
||||
|
||||
export { Badge, Typography, Button };
|
||||
|
||||
// ########################################################################
|
||||
|
||||
function getColor(color, theme, brigtness = "main") {
|
||||
if (color && theme.palette[color] && theme.palette[color][brigtness]) {
|
||||
return theme.palette[color][brigtness];
|
||||
}
|
||||
}
|
||||
|
||||
function getFontWeight(style) {
|
||||
switch (style) {
|
||||
case "light":
|
||||
return 300;
|
||||
case "medium":
|
||||
return 500;
|
||||
case "bold":
|
||||
return 600;
|
||||
default:
|
||||
return 400;
|
||||
}
|
||||
}
|
||||
|
||||
function getFontSize(size, variant = "", theme) {
|
||||
var multiplier;
|
||||
|
||||
switch (size) {
|
||||
case "sm":
|
||||
multiplier = 0.8;
|
||||
break;
|
||||
case "md":
|
||||
multiplier = 1.5;
|
||||
break;
|
||||
case "xl":
|
||||
multiplier = 2;
|
||||
break;
|
||||
case "xxl":
|
||||
multiplier = 3;
|
||||
break;
|
||||
default:
|
||||
multiplier = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
var defaultSize =
|
||||
variant && theme.typography[variant]
|
||||
? theme.typography[variant].fontSize
|
||||
: theme.typography.fontSize + "px";
|
||||
|
||||
return `calc(${defaultSize} * ${multiplier})`;
|
||||
}
|
||||
|
||||
function createStyled(styles, options) {
|
||||
var Styled = function(props) {
|
||||
const { children, ...other } = props;
|
||||
return children(other);
|
||||
};
|
||||
|
||||
return withStyles(styles, options)(Styled);
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "Wrappers",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "Wrappers.js"
|
||||
}
|
||||
|
||||
52
frontend/react-material-admin/src/context/LayoutContext.js
vendored
Normal file
52
frontend/react-material-admin/src/context/LayoutContext.js
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import React from "react";
|
||||
|
||||
var LayoutStateContext = React.createContext();
|
||||
var LayoutDispatchContext = React.createContext();
|
||||
|
||||
function layoutReducer(state, action) {
|
||||
switch (action.type) {
|
||||
case "TOGGLE_SIDEBAR":
|
||||
return { ...state, isSidebarOpened: !state.isSidebarOpened };
|
||||
default: {
|
||||
throw new Error(`Unhandled action type: ${action.type}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function LayoutProvider({ children }) {
|
||||
var [state, dispatch] = React.useReducer(layoutReducer, {
|
||||
isSidebarOpened: true,
|
||||
});
|
||||
return (
|
||||
<LayoutStateContext.Provider value={state}>
|
||||
<LayoutDispatchContext.Provider value={dispatch}>
|
||||
{children}
|
||||
</LayoutDispatchContext.Provider>
|
||||
</LayoutStateContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function useLayoutState() {
|
||||
var context = React.useContext(LayoutStateContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useLayoutState must be used within a LayoutProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
function useLayoutDispatch() {
|
||||
var context = React.useContext(LayoutDispatchContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useLayoutDispatch must be used within a LayoutProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
export { LayoutProvider, useLayoutState, useLayoutDispatch, toggleSidebar };
|
||||
|
||||
// ###########################################################
|
||||
function toggleSidebar(dispatch) {
|
||||
dispatch({
|
||||
type: "TOGGLE_SIDEBAR",
|
||||
});
|
||||
}
|
||||
76
frontend/react-material-admin/src/context/UserContext.js
vendored
Normal file
76
frontend/react-material-admin/src/context/UserContext.js
vendored
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import React from "react";
|
||||
|
||||
var UserStateContext = React.createContext();
|
||||
var UserDispatchContext = React.createContext();
|
||||
|
||||
function userReducer(state, action) {
|
||||
switch (action.type) {
|
||||
case "LOGIN_SUCCESS":
|
||||
return { ...state, isAuthenticated: true };
|
||||
case "SIGN_OUT_SUCCESS":
|
||||
return { ...state, isAuthenticated: false };
|
||||
default: {
|
||||
throw new Error(`Unhandled action type: ${action.type}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function UserProvider({ children }) {
|
||||
var [state, dispatch] = React.useReducer(userReducer, {
|
||||
isAuthenticated: !!localStorage.getItem("id_token"),
|
||||
});
|
||||
|
||||
return (
|
||||
<UserStateContext.Provider value={state}>
|
||||
<UserDispatchContext.Provider value={dispatch}>
|
||||
{children}
|
||||
</UserDispatchContext.Provider>
|
||||
</UserStateContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function useUserState() {
|
||||
var context = React.useContext(UserStateContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useUserState must be used within a UserProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
function useUserDispatch() {
|
||||
var context = React.useContext(UserDispatchContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useUserDispatch must be used within a UserProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
export { UserProvider, useUserState, useUserDispatch, loginUser, signOut };
|
||||
|
||||
// ###########################################################
|
||||
|
||||
function loginUser(dispatch, login, password, history, setIsLoading, setError) {
|
||||
setError(false);
|
||||
setIsLoading(true);
|
||||
|
||||
if (!!login && !!password) {
|
||||
setTimeout(() => {
|
||||
localStorage.setItem('id_token', 1)
|
||||
setError(null)
|
||||
setIsLoading(false)
|
||||
dispatch({ type: 'LOGIN_SUCCESS' })
|
||||
|
||||
history.push('/app/dashboard')
|
||||
}, 2000);
|
||||
} else {
|
||||
dispatch({ type: "LOGIN_FAILURE" });
|
||||
setError(true);
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
function signOut(dispatch, history) {
|
||||
localStorage.removeItem("id_token");
|
||||
dispatch({ type: "SIGN_OUT_SUCCESS" });
|
||||
history.push("/login");
|
||||
}
|
||||
47
frontend/react-material-admin/src/images/google.svg
Normal file
47
frontend/react-material-admin/src/images/google.svg
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<path style="fill:#FBBB00;" d="M113.47,309.408L95.648,375.94l-65.139,1.378C11.042,341.211,0,299.9,0,256
|
||||
c0-42.451,10.324-82.483,28.624-117.732h0.014l57.992,10.632l25.404,57.644c-5.317,15.501-8.215,32.141-8.215,49.456
|
||||
C103.821,274.792,107.225,292.797,113.47,309.408z"/>
|
||||
<path style="fill:#518EF8;" d="M507.527,208.176C510.467,223.662,512,239.655,512,256c0,18.328-1.927,36.206-5.598,53.451
|
||||
c-12.462,58.683-45.025,109.925-90.134,146.187l-0.014-0.014l-73.044-3.727l-10.338-64.535
|
||||
c29.932-17.554,53.324-45.025,65.646-77.911h-136.89V208.176h138.887L507.527,208.176L507.527,208.176z"/>
|
||||
<path style="fill:#28B446;" d="M416.253,455.624l0.014,0.014C372.396,490.901,316.666,512,256,512
|
||||
c-97.491,0-182.252-54.491-225.491-134.681l82.961-67.91c21.619,57.698,77.278,98.771,142.53,98.771
|
||||
c28.047,0,54.323-7.582,76.87-20.818L416.253,455.624z"/>
|
||||
<path style="fill:#F14336;" d="M419.404,58.936l-82.933,67.896c-23.335-14.586-50.919-23.012-80.471-23.012
|
||||
c-66.729,0-123.429,42.957-143.965,102.724l-83.397-68.276h-0.014C71.23,56.123,157.06,0,256,0
|
||||
C318.115,0,375.068,22.126,419.404,58.936z"/>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
27
frontend/react-material-admin/src/index.js
vendored
Executable file
27
frontend/react-material-admin/src/index.js
vendored
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { ThemeProvider } from "@material-ui/styles";
|
||||
import { CssBaseline } from "@material-ui/core";
|
||||
|
||||
import Themes from "./themes";
|
||||
import App from "./components/App";
|
||||
import * as serviceWorker from "./serviceWorker";
|
||||
import { LayoutProvider } from "./context/LayoutContext";
|
||||
import { UserProvider } from "./context/UserContext";
|
||||
|
||||
ReactDOM.render(
|
||||
<LayoutProvider>
|
||||
<UserProvider>
|
||||
<ThemeProvider theme={Themes.default}>
|
||||
<CssBaseline />
|
||||
<App />
|
||||
</ThemeProvider>
|
||||
</UserProvider>
|
||||
</LayoutProvider>,
|
||||
document.getElementById("root"),
|
||||
);
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
// unregister() to register() below. Note this comes with some pitfalls.
|
||||
// Learn more about service workers: http://bit.ly/CRA-PWA
|
||||
serviceWorker.unregister();
|
||||
228
frontend/react-material-admin/src/pages/charts/Charts.js
vendored
Normal file
228
frontend/react-material-admin/src/pages/charts/Charts.js
vendored
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
import React, { useState } from "react";
|
||||
import { Grid } from "@material-ui/core";
|
||||
import { useTheme } from "@material-ui/styles";
|
||||
import {
|
||||
CartesianGrid,
|
||||
Legend,
|
||||
Line,
|
||||
LineChart,
|
||||
Pie,
|
||||
PieChart,
|
||||
ResponsiveContainer,
|
||||
Sector,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
|
||||
// components
|
||||
import Widget from "../../components/Widget/Widget";
|
||||
import ApexLineChart from "./components/ApexLineChart";
|
||||
import ApexHeatmap from "./components/ApexHeatmap";
|
||||
import PageTitle from "../../components/PageTitle/PageTitle";
|
||||
|
||||
const lineChartData = [
|
||||
{
|
||||
name: "Page A",
|
||||
uv: 4000,
|
||||
pv: 2400,
|
||||
amt: 2400,
|
||||
},
|
||||
{
|
||||
name: "Page B",
|
||||
uv: 3000,
|
||||
pv: 1398,
|
||||
amt: 2210,
|
||||
},
|
||||
{
|
||||
name: "Page C",
|
||||
uv: 2000,
|
||||
pv: 9800,
|
||||
amt: 2290,
|
||||
},
|
||||
{
|
||||
name: "Page D",
|
||||
uv: 2780,
|
||||
pv: 3908,
|
||||
amt: 2000,
|
||||
},
|
||||
{
|
||||
name: "Page E",
|
||||
uv: 1890,
|
||||
pv: 4800,
|
||||
amt: 2181,
|
||||
},
|
||||
{
|
||||
name: "Page F",
|
||||
uv: 2390,
|
||||
pv: 3800,
|
||||
amt: 2500,
|
||||
},
|
||||
{
|
||||
name: "Page G",
|
||||
uv: 3490,
|
||||
pv: 4300,
|
||||
amt: 2100,
|
||||
},
|
||||
];
|
||||
|
||||
const pieChartData = [
|
||||
{ name: "Group A", value: 400 },
|
||||
{ name: "Group B", value: 300 },
|
||||
{ name: "Group C", value: 300 },
|
||||
{ name: "Group D", value: 200 },
|
||||
];
|
||||
|
||||
export default function Charts(props) {
|
||||
var theme = useTheme();
|
||||
|
||||
// local
|
||||
var [activeIndex, setActiveIndexId] = useState(0);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title="Charts Page - Data Display" button="Latest Reports" />
|
||||
<Grid container spacing={4}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Widget title="Apex Line Chart" upperTitle noBodyPadding>
|
||||
<ApexLineChart />
|
||||
</Widget>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Widget title="Apex Heatmap" upperTitle noBodyPadding>
|
||||
<ApexHeatmap />
|
||||
</Widget>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={8}>
|
||||
<Widget title="Simple Line Chart" noBodyPadding upperTitle>
|
||||
<ResponsiveContainer width="100%" height={350}>
|
||||
<LineChart
|
||||
width={500}
|
||||
height={300}
|
||||
data={lineChartData}
|
||||
margin={{
|
||||
top: 5,
|
||||
right: 30,
|
||||
left: 20,
|
||||
bottom: 5,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="pv"
|
||||
stroke={theme.palette.primary.main}
|
||||
activeDot={{ r: 8 }}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="uv"
|
||||
stroke={theme.palette.secondary.main}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</Widget>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={4}>
|
||||
<Widget title="Pie Chart with Tooltips" noBodyPadding upperTitle>
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<PieChart width={200} height={300}>
|
||||
<Pie
|
||||
activeIndex={activeIndex}
|
||||
activeShape={renderActiveShape}
|
||||
data={pieChartData}
|
||||
cx={200}
|
||||
cy={150}
|
||||
innerRadius={60}
|
||||
outerRadius={80}
|
||||
fill={theme.palette.primary.main}
|
||||
dataKey="value"
|
||||
onMouseEnter={(e, id) => setActiveIndexId(id)}
|
||||
/>
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</Widget>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// ################################################################
|
||||
|
||||
function renderActiveShape(props) {
|
||||
var RADIAN = Math.PI / 180;
|
||||
var {
|
||||
cx,
|
||||
cy,
|
||||
midAngle,
|
||||
innerRadius,
|
||||
outerRadius,
|
||||
startAngle,
|
||||
endAngle,
|
||||
fill,
|
||||
payload,
|
||||
percent,
|
||||
value,
|
||||
} = props;
|
||||
var sin = Math.sin(-RADIAN * midAngle);
|
||||
var cos = Math.cos(-RADIAN * midAngle);
|
||||
var sx = cx + (outerRadius + 10) * cos;
|
||||
var sy = cy + (outerRadius + 10) * sin;
|
||||
var mx = cx + (outerRadius + 30) * cos;
|
||||
var my = cy + (outerRadius + 30) * sin;
|
||||
var ex = mx + (cos >= 0 ? 1 : -1) * 22;
|
||||
var ey = my;
|
||||
var textAnchor = cos >= 0 ? "start" : "end";
|
||||
|
||||
return (
|
||||
<g>
|
||||
<text x={cx} y={cy} dy={8} textAnchor="middle" fill={fill}>
|
||||
{payload.name}
|
||||
</text>
|
||||
<Sector
|
||||
cx={cx}
|
||||
cy={cy}
|
||||
innerRadius={innerRadius}
|
||||
outerRadius={outerRadius}
|
||||
startAngle={startAngle}
|
||||
endAngle={endAngle}
|
||||
fill={fill}
|
||||
/>
|
||||
<Sector
|
||||
cx={cx}
|
||||
cy={cy}
|
||||
startAngle={startAngle}
|
||||
endAngle={endAngle}
|
||||
innerRadius={outerRadius + 6}
|
||||
outerRadius={outerRadius + 10}
|
||||
fill={fill}
|
||||
/>
|
||||
<path
|
||||
d={`M${sx},${sy}L${mx},${my}L${ex},${ey}`}
|
||||
stroke={fill}
|
||||
fill="none"
|
||||
/>
|
||||
<circle cx={ex} cy={ey} r={2} fill={fill} stroke="none" />
|
||||
<text
|
||||
x={ex + (cos >= 0 ? 1 : -1) * 12}
|
||||
y={ey}
|
||||
textAnchor={textAnchor}
|
||||
fill="#333"
|
||||
>{`PV ${value}`}</text>
|
||||
<text
|
||||
x={ex + (cos >= 0 ? 1 : -1) * 12}
|
||||
y={ey}
|
||||
dy={18}
|
||||
textAnchor={textAnchor}
|
||||
fill="#999"
|
||||
>
|
||||
{`(Rate ${(percent * 100).toFixed(2)}%)`}
|
||||
</text>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
115
frontend/react-material-admin/src/pages/charts/components/ApexHeatmap.js
vendored
Normal file
115
frontend/react-material-admin/src/pages/charts/components/ApexHeatmap.js
vendored
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import React from "react";
|
||||
import { useTheme } from "@material-ui/styles";
|
||||
import ApexCharts from "react-apexcharts";
|
||||
|
||||
const series = [
|
||||
{
|
||||
name: "Metric1",
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Metric2",
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Metric3",
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Metric4",
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Metric5",
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Metric6",
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Metric7",
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Metric8",
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Metric9",
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90,
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
export default function ApexLineChart() {
|
||||
var theme = useTheme();
|
||||
|
||||
return (
|
||||
<ApexCharts
|
||||
options={themeOptions(theme)}
|
||||
series={series}
|
||||
type="heatmap"
|
||||
height={350}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// ##################################################################
|
||||
function generateData(count, yrange) {
|
||||
var i = 0;
|
||||
var series = [];
|
||||
while (i < count) {
|
||||
var x = "w" + (i + 1).toString();
|
||||
var y =
|
||||
Math.floor(Math.random() * (yrange.max - yrange.min + 1)) + yrange.min;
|
||||
|
||||
series.push({
|
||||
x: x,
|
||||
y: y,
|
||||
});
|
||||
i++;
|
||||
}
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
function themeOptions(theme) {
|
||||
return {
|
||||
chart: {
|
||||
toolbar: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
},
|
||||
colors: [theme.palette.primary.main],
|
||||
};
|
||||
}
|
||||
68
frontend/react-material-admin/src/pages/charts/components/ApexLineChart.js
vendored
Normal file
68
frontend/react-material-admin/src/pages/charts/components/ApexLineChart.js
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import React from "react";
|
||||
import ApexCharts from "react-apexcharts";
|
||||
import { useTheme } from "@material-ui/styles";
|
||||
|
||||
const series = [
|
||||
{
|
||||
name: "series1",
|
||||
data: [31, 40, 28, 51, 42, 109, 100],
|
||||
},
|
||||
{
|
||||
name: "series2",
|
||||
data: [11, 32, 45, 32, 34, 52, 41],
|
||||
},
|
||||
];
|
||||
|
||||
export default function ApexLineChart() {
|
||||
var theme = useTheme();
|
||||
|
||||
return (
|
||||
<ApexCharts
|
||||
options={themeOptions(theme)}
|
||||
series={series}
|
||||
type="area"
|
||||
height={350}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// ############################################################
|
||||
function themeOptions(theme) {
|
||||
return {
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
},
|
||||
stroke: {
|
||||
curve: "smooth",
|
||||
},
|
||||
xaxis: {
|
||||
type: "datetime",
|
||||
categories: [
|
||||
"2018-09-19T00:00:00",
|
||||
"2018-09-19T01:30:00",
|
||||
"2018-09-19T02:30:00",
|
||||
"2018-09-19T03:30:00",
|
||||
"2018-09-19T04:30:00",
|
||||
"2018-09-19T05:30:00",
|
||||
"2018-09-19T06:30:00",
|
||||
],
|
||||
},
|
||||
tooltip: {
|
||||
x: {
|
||||
format: "dd/MM/yy HH:mm",
|
||||
},
|
||||
},
|
||||
fill: {
|
||||
colors: [theme.palette.primary.light, theme.palette.success.light],
|
||||
},
|
||||
colors: [theme.palette.primary.main, theme.palette.success.main],
|
||||
chart: {
|
||||
toolbar: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
show: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "Charts",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "Charts.js"
|
||||
}
|
||||
481
frontend/react-material-admin/src/pages/dashboard/Dashboard.js
vendored
Normal file
481
frontend/react-material-admin/src/pages/dashboard/Dashboard.js
vendored
Normal file
|
|
@ -0,0 +1,481 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Grid,
|
||||
LinearProgress,
|
||||
Select,
|
||||
OutlinedInput,
|
||||
MenuItem,
|
||||
} from "@material-ui/core";
|
||||
import { useTheme } from "@material-ui/styles";
|
||||
import {
|
||||
ResponsiveContainer,
|
||||
ComposedChart,
|
||||
AreaChart,
|
||||
LineChart,
|
||||
Line,
|
||||
Area,
|
||||
PieChart,
|
||||
Pie,
|
||||
Cell,
|
||||
YAxis,
|
||||
XAxis,
|
||||
} from "recharts";
|
||||
|
||||
// styles
|
||||
import useStyles from "./styles";
|
||||
|
||||
// components
|
||||
import mock from "./mock";
|
||||
import Widget from "../../components/Widget";
|
||||
import PageTitle from "../../components/PageTitle";
|
||||
import { Typography } from "../../components/Wrappers";
|
||||
import Dot from "../../components/Sidebar/components/Dot";
|
||||
import Table from "./components/Table/Table";
|
||||
import BigStat from "./components/BigStat/BigStat";
|
||||
|
||||
import { HelloRequest,
|
||||
HelloReply } from "../../proto/squeak_admin_pb"
|
||||
import { SqueakAdminClient } from "../../proto/squeak_admin_grpc_web_pb"
|
||||
|
||||
var client = new SqueakAdminClient('http://' + window.location.hostname + ':8080')
|
||||
|
||||
const mainChartData = getMainChartData();
|
||||
const PieChartData = [
|
||||
{ name: "Group A", value: 400, color: "primary" },
|
||||
{ name: "Group B", value: 300, color: "secondary" },
|
||||
{ name: "Group C", value: 300, color: "warning" },
|
||||
{ name: "Group D", value: 200, color: "success" },
|
||||
];
|
||||
|
||||
export default function Dashboard(props) {
|
||||
console.log('starting Dashboard....');
|
||||
var classes = useStyles();
|
||||
var theme = useTheme();
|
||||
|
||||
// local
|
||||
var [mainChartState, setMainChartState] = useState("monthly");
|
||||
const [msg, setMsg] = useState("waiting for message...");
|
||||
|
||||
const getMsg = () => {
|
||||
console.log("called getMsg");
|
||||
|
||||
var helloRequest = new HelloRequest()
|
||||
helloRequest.setName('World');
|
||||
|
||||
client.sayHello(helloRequest, {}, (err, response) => {
|
||||
console.log(response.getMessage());
|
||||
setMsg(response.getMessage())
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(()=>{
|
||||
getMsg()
|
||||
},[]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title="Dashboard" button="Latest Reports" />
|
||||
|
||||
<div>
|
||||
Message : {msg}
|
||||
</div>
|
||||
|
||||
<Grid container spacing={4}>
|
||||
<Grid item lg={3} md={4} sm={6} xs={12}>
|
||||
<Widget
|
||||
title="Visits Today"
|
||||
upperTitle
|
||||
bodyClass={classes.fullHeightBody}
|
||||
className={classes.card}
|
||||
>
|
||||
<div className={classes.visitsNumberContainer}>
|
||||
<Typography size="xl" weight="medium">
|
||||
12, 678
|
||||
</Typography>
|
||||
<LineChart
|
||||
width={55}
|
||||
height={30}
|
||||
data={[
|
||||
{ value: 10 },
|
||||
{ value: 15 },
|
||||
{ value: 10 },
|
||||
{ value: 17 },
|
||||
{ value: 18 },
|
||||
]}
|
||||
margin={{ left: theme.spacing(2) }}
|
||||
>
|
||||
<Line
|
||||
type="natural"
|
||||
dataKey="value"
|
||||
stroke={theme.palette.success.main}
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
/>
|
||||
</LineChart>
|
||||
</div>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justify="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<Grid item>
|
||||
<Typography color="text" colorBrightness="secondary">
|
||||
Registrations
|
||||
</Typography>
|
||||
<Typography size="md">860</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography color="text" colorBrightness="secondary">
|
||||
Sign Out
|
||||
</Typography>
|
||||
<Typography size="md">32</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography color="text" colorBrightness="secondary">
|
||||
Rate
|
||||
</Typography>
|
||||
<Typography size="md">3.25%</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Widget>
|
||||
</Grid>
|
||||
<Grid item lg={3} md={8} sm={6} xs={12}>
|
||||
<Widget
|
||||
title="App Performance"
|
||||
upperTitle
|
||||
className={classes.card}
|
||||
bodyClass={classes.fullHeightBody}
|
||||
>
|
||||
<div className={classes.performanceLegendWrapper}>
|
||||
<div className={classes.legendElement}>
|
||||
<Dot color="warning" />
|
||||
<Typography
|
||||
color="text"
|
||||
colorBrightness="secondary"
|
||||
className={classes.legendElementText}
|
||||
>
|
||||
Integration
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={classes.legendElement}>
|
||||
<Dot color="primary" />
|
||||
<Typography
|
||||
color="text"
|
||||
colorBrightness="secondary"
|
||||
className={classes.legendElementText}
|
||||
>
|
||||
SDK
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.progressSection}>
|
||||
<Typography
|
||||
size="md"
|
||||
color="text"
|
||||
colorBrightness="secondary"
|
||||
className={classes.progressSectionTitle}
|
||||
>
|
||||
Integration
|
||||
</Typography>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={30}
|
||||
classes={{ barColorPrimary: classes.progressBar }}
|
||||
className={classes.progress}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Typography
|
||||
size="md"
|
||||
color="text"
|
||||
colorBrightness="secondary"
|
||||
className={classes.progressSectionTitle}
|
||||
>
|
||||
SDK
|
||||
</Typography>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={55}
|
||||
classes={{ barColorPrimary: classes.progressBar }}
|
||||
className={classes.progress}
|
||||
/>
|
||||
</div>
|
||||
</Widget>
|
||||
</Grid>
|
||||
<Grid item lg={3} md={8} sm={6} xs={12}>
|
||||
<Widget
|
||||
title="Server Overview"
|
||||
upperTitle
|
||||
className={classes.card}
|
||||
bodyClass={classes.fullHeightBody}
|
||||
>
|
||||
<div className={classes.serverOverviewElement}>
|
||||
<Typography
|
||||
color="text"
|
||||
colorBrightness="secondary"
|
||||
className={classes.serverOverviewElementText}
|
||||
>
|
||||
60% / 37°С / 3.3 Ghz
|
||||
</Typography>
|
||||
<div className={classes.serverOverviewElementChartWrapper}>
|
||||
<ResponsiveContainer height={50} width="99%">
|
||||
<AreaChart data={getRandomData(10)}>
|
||||
<Area
|
||||
type="natural"
|
||||
dataKey="value"
|
||||
stroke={theme.palette.secondary.main}
|
||||
fill={theme.palette.secondary.light}
|
||||
strokeWidth={2}
|
||||
fillOpacity="0.25"
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.serverOverviewElement}>
|
||||
<Typography
|
||||
color="text"
|
||||
colorBrightness="secondary"
|
||||
className={classes.serverOverviewElementText}
|
||||
>
|
||||
54% / 31°С / 3.3 Ghz
|
||||
</Typography>
|
||||
<div className={classes.serverOverviewElementChartWrapper}>
|
||||
<ResponsiveContainer height={50} width="99%">
|
||||
<AreaChart data={getRandomData(10)}>
|
||||
<Area
|
||||
type="natural"
|
||||
dataKey="value"
|
||||
stroke={theme.palette.primary.main}
|
||||
fill={theme.palette.primary.light}
|
||||
strokeWidth={2}
|
||||
fillOpacity="0.25"
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.serverOverviewElement}>
|
||||
<Typography
|
||||
color="text"
|
||||
colorBrightness="secondary"
|
||||
className={classes.serverOverviewElementText}
|
||||
>
|
||||
57% / 21°С / 3.3 Ghz
|
||||
</Typography>
|
||||
<div className={classes.serverOverviewElementChartWrapper}>
|
||||
<ResponsiveContainer height={50} width="99%">
|
||||
<AreaChart data={getRandomData(10)}>
|
||||
<Area
|
||||
type="natural"
|
||||
dataKey="value"
|
||||
stroke={theme.palette.warning.main}
|
||||
fill={theme.palette.warning.light}
|
||||
strokeWidth={2}
|
||||
fillOpacity="0.25"
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
</Widget>
|
||||
</Grid>
|
||||
<Grid item lg={3} md={4} sm={6} xs={12}>
|
||||
<Widget title="Revenue Breakdown" upperTitle className={classes.card}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={6}>
|
||||
<ResponsiveContainer width="100%" height={144}>
|
||||
<PieChart margin={{ left: theme.spacing(2) }}>
|
||||
<Pie
|
||||
data={PieChartData}
|
||||
innerRadius={45}
|
||||
outerRadius={60}
|
||||
dataKey="value"
|
||||
>
|
||||
{PieChartData.map((entry, index) => (
|
||||
<Cell
|
||||
key={`cell-${index}`}
|
||||
fill={theme.palette[entry.color].main}
|
||||
/>
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<div className={classes.pieChartLegendWrapper}>
|
||||
{PieChartData.map(({ name, value, color }, index) => (
|
||||
<div key={color} className={classes.legendItemContainer}>
|
||||
<Dot color={color} />
|
||||
<Typography style={{ whiteSpace: "nowrap" }}>
|
||||
{name}
|
||||
</Typography>
|
||||
<Typography color="text" colorBrightness="secondary">
|
||||
{value}
|
||||
</Typography>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Widget>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Widget
|
||||
bodyClass={classes.mainChartBody}
|
||||
header={
|
||||
<div className={classes.mainChartHeader}>
|
||||
<Typography
|
||||
variant="h5"
|
||||
color="text"
|
||||
colorBrightness="secondary"
|
||||
>
|
||||
Daily Line Chart
|
||||
</Typography>
|
||||
<div className={classes.mainChartHeaderLabels}>
|
||||
<div className={classes.mainChartHeaderLabel}>
|
||||
<Dot color="warning" />
|
||||
<Typography className={classes.mainChartLegentElement}>
|
||||
Tablet
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={classes.mainChartHeaderLabel}>
|
||||
<Dot color="primary" />
|
||||
<Typography className={classes.mainChartLegentElement}>
|
||||
Mobile
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={classes.mainChartHeaderLabel}>
|
||||
<Dot color="primary" />
|
||||
<Typography className={classes.mainChartLegentElement}>
|
||||
Desktop
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<Select
|
||||
value={mainChartState}
|
||||
onChange={e => setMainChartState(e.target.value)}
|
||||
input={
|
||||
<OutlinedInput
|
||||
labelWidth={0}
|
||||
classes={{
|
||||
notchedOutline: classes.mainChartSelectRoot,
|
||||
input: classes.mainChartSelect,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
autoWidth
|
||||
>
|
||||
<MenuItem value="daily">Daily</MenuItem>
|
||||
<MenuItem value="weekly">Weekly</MenuItem>
|
||||
<MenuItem value="monthly">Monthly</MenuItem>
|
||||
</Select>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<ResponsiveContainer width="100%" minWidth={500} height={350}>
|
||||
<ComposedChart
|
||||
margin={{ top: 0, right: -15, left: -15, bottom: 0 }}
|
||||
data={mainChartData}
|
||||
>
|
||||
<YAxis
|
||||
ticks={[0, 2500, 5000, 7500]}
|
||||
tick={{ fill: theme.palette.text.hint + "80", fontSize: 14 }}
|
||||
stroke={theme.palette.text.hint + "80"}
|
||||
tickLine={false}
|
||||
/>
|
||||
<XAxis
|
||||
tickFormatter={i => i + 1}
|
||||
tick={{ fill: theme.palette.text.hint + "80", fontSize: 14 }}
|
||||
stroke={theme.palette.text.hint + "80"}
|
||||
tickLine={false}
|
||||
/>
|
||||
<Area
|
||||
type="natural"
|
||||
dataKey="desktop"
|
||||
fill={theme.palette.background.light}
|
||||
strokeWidth={0}
|
||||
activeDot={false}
|
||||
/>
|
||||
<Line
|
||||
type="natural"
|
||||
dataKey="mobile"
|
||||
stroke={theme.palette.primary.main}
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
activeDot={false}
|
||||
/>
|
||||
<Line
|
||||
type="linear"
|
||||
dataKey="tablet"
|
||||
stroke={theme.palette.warning.main}
|
||||
strokeWidth={2}
|
||||
dot={{
|
||||
stroke: theme.palette.warning.dark,
|
||||
strokeWidth: 2,
|
||||
fill: theme.palette.warning.main,
|
||||
}}
|
||||
/>
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
</Widget>
|
||||
</Grid>
|
||||
{mock.bigStat.map(stat => (
|
||||
<Grid item md={4} sm={6} xs={12} key={stat.product}>
|
||||
<BigStat {...stat} />
|
||||
</Grid>
|
||||
))}
|
||||
<Grid item xs={12}>
|
||||
<Widget
|
||||
title="Support Requests"
|
||||
upperTitle
|
||||
noBodyPadding
|
||||
bodyClass={classes.tableWidget}
|
||||
>
|
||||
<Table data={mock.table} />
|
||||
</Widget>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// #######################################################################
|
||||
function getRandomData(length, min, max, multiplier = 10, maxDiff = 10) {
|
||||
var array = new Array(length).fill();
|
||||
let lastValue;
|
||||
|
||||
return array.map((item, index) => {
|
||||
let randomValue = Math.floor(Math.random() * multiplier + 1);
|
||||
|
||||
while (
|
||||
randomValue <= min ||
|
||||
randomValue >= max ||
|
||||
(lastValue && randomValue - lastValue > maxDiff)
|
||||
) {
|
||||
randomValue = Math.floor(Math.random() * multiplier + 1);
|
||||
}
|
||||
|
||||
lastValue = randomValue;
|
||||
|
||||
return { value: randomValue };
|
||||
});
|
||||
}
|
||||
|
||||
function getMainChartData() {
|
||||
var resultArray = [];
|
||||
var tablet = getRandomData(31, 3500, 6500, 7500, 1000);
|
||||
var desktop = getRandomData(31, 1500, 7500, 7500, 1500);
|
||||
var mobile = getRandomData(31, 1500, 7500, 7500, 1500);
|
||||
|
||||
for (let i = 0; i < tablet.length; i++) {
|
||||
resultArray.push({
|
||||
tablet: tablet[i].value,
|
||||
desktop: desktop[i].value,
|
||||
mobile: mobile[i].value,
|
||||
});
|
||||
}
|
||||
|
||||
return resultArray;
|
||||
}
|
||||
120
frontend/react-material-admin/src/pages/dashboard/components/BigStat/BigStat.js
vendored
Normal file
120
frontend/react-material-admin/src/pages/dashboard/components/BigStat/BigStat.js
vendored
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
import React, { useState } from "react";
|
||||
import { Grid, Select, MenuItem, Input } from "@material-ui/core";
|
||||
import { ArrowForward as ArrowForwardIcon } from "@material-ui/icons";
|
||||
import { useTheme } from "@material-ui/styles";
|
||||
import { BarChart, Bar } from "recharts";
|
||||
import classnames from "classnames";
|
||||
|
||||
// styles
|
||||
import useStyles from "./styles";
|
||||
|
||||
// components
|
||||
import Widget from "../../../../components/Widget";
|
||||
import { Typography } from "../../../../components/Wrappers";
|
||||
|
||||
export default function BigStat(props) {
|
||||
var { product, total, color, registrations, bounce } = props;
|
||||
var classes = useStyles();
|
||||
var theme = useTheme();
|
||||
|
||||
// local
|
||||
var [value, setValue] = useState("daily");
|
||||
|
||||
return (
|
||||
<Widget
|
||||
header={
|
||||
<div className={classes.title}>
|
||||
<Typography variant="h5">{product}</Typography>
|
||||
|
||||
<Select
|
||||
value={value}
|
||||
onChange={e => setValue(e.target.value)}
|
||||
input={
|
||||
<Input
|
||||
disableUnderline
|
||||
classes={{ input: classes.selectInput }}
|
||||
/>
|
||||
}
|
||||
className={classes.select}
|
||||
>
|
||||
<MenuItem value="daily">Daily</MenuItem>
|
||||
<MenuItem value="weekly">Weekly</MenuItem>
|
||||
<MenuItem value="monthly">Monthly</MenuItem>
|
||||
</Select>
|
||||
</div>
|
||||
}
|
||||
upperTitle
|
||||
>
|
||||
<div className={classes.totalValueContainer}>
|
||||
<div className={classes.totalValue}>
|
||||
<Typography size="xxl" color="text" colorBrightness="secondary">
|
||||
{total[value]}
|
||||
</Typography>
|
||||
<Typography color={total.percent.profit ? "success" : "secondary"}>
|
||||
{total.percent.profit ? "+" : "-"}
|
||||
{total.percent.value}%
|
||||
</Typography>
|
||||
</div>
|
||||
<BarChart width={150} height={70} data={getRandomData()}>
|
||||
<Bar
|
||||
dataKey="value"
|
||||
fill={theme.palette[color].main}
|
||||
radius={10}
|
||||
barSize={10}
|
||||
/>
|
||||
</BarChart>
|
||||
</div>
|
||||
<div className={classes.bottomStatsContainer}>
|
||||
<div className={classnames(classes.statCell, classes.borderRight)}>
|
||||
<Grid container alignItems="center">
|
||||
<Typography variant="h6">{registrations[value].value}</Typography>
|
||||
<ArrowForwardIcon
|
||||
className={classnames(classes.profitArrow, {
|
||||
[!registrations[value].profit]: classes.profitArrowDanger,
|
||||
})}
|
||||
/>
|
||||
</Grid>
|
||||
<Typography size="sm" color="text" colorBrightness="secondary">
|
||||
Registrations
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={classes.statCell}>
|
||||
<Grid container alignItems="center">
|
||||
<Typography variant="h6">{bounce[value].value}%</Typography>
|
||||
<ArrowForwardIcon
|
||||
className={classnames(classes.profitArrow, {
|
||||
[!registrations[value].profit]: classes.profitArrowDanger,
|
||||
})}
|
||||
/>
|
||||
</Grid>
|
||||
<Typography size="sm" color="text" colorBrightness="secondary">
|
||||
Bounce Rate
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={classnames(classes.statCell, classes.borderRight)}>
|
||||
<Grid container alignItems="center">
|
||||
<Typography variant="h6">
|
||||
{registrations[value].value * 10}
|
||||
</Typography>
|
||||
<ArrowForwardIcon
|
||||
className={classnames(classes.profitArrow, {
|
||||
[classes.profitArrowDanger]: !registrations[value].profit,
|
||||
})}
|
||||
/>
|
||||
</Grid>
|
||||
<Typography size="sm" color="text" colorBrightness="secondary">
|
||||
Views
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</Widget>
|
||||
);
|
||||
}
|
||||
|
||||
// #######################################################################
|
||||
|
||||
function getRandomData() {
|
||||
return Array(7)
|
||||
.fill()
|
||||
.map(() => ({ value: Math.floor(Math.random() * 10) + 1 }));
|
||||
}
|
||||
45
frontend/react-material-admin/src/pages/dashboard/components/BigStat/styles.js
vendored
Normal file
45
frontend/react-material-admin/src/pages/dashboard/components/BigStat/styles.js
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import { makeStyles } from "@material-ui/styles";
|
||||
|
||||
export default makeStyles(theme => ({
|
||||
title: {
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
marginBottom: theme.spacing(1),
|
||||
},
|
||||
bottomStatsContainer: {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
margin: theme.spacing(1) * -2,
|
||||
marginTop: theme.spacing(1),
|
||||
},
|
||||
statCell: {
|
||||
padding: theme.spacing(2),
|
||||
},
|
||||
totalValueContainer: {
|
||||
display: "flex",
|
||||
alignItems: "flex-end",
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
totalValue: {
|
||||
display: "flex",
|
||||
alignItems: "baseline",
|
||||
},
|
||||
profitArrow: {
|
||||
transform: "rotate(-45deg)",
|
||||
fill: theme.palette.success.main,
|
||||
},
|
||||
profitArrowDanger: {
|
||||
transform: "rotate(45deg)",
|
||||
fill: theme.palette.secondary.main,
|
||||
},
|
||||
selectInput: {
|
||||
padding: 10,
|
||||
paddingRight: 25,
|
||||
"&:focus": {
|
||||
backgroundColor: "white",
|
||||
},
|
||||
},
|
||||
}));
|
||||
56
frontend/react-material-admin/src/pages/dashboard/components/Table/Table.js
vendored
Normal file
56
frontend/react-material-admin/src/pages/dashboard/components/Table/Table.js
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import React from "react";
|
||||
import {
|
||||
Table,
|
||||
TableRow,
|
||||
TableHead,
|
||||
TableBody,
|
||||
TableCell,
|
||||
} from "@material-ui/core";
|
||||
|
||||
// components
|
||||
import { Button } from "../../../../components/Wrappers";
|
||||
|
||||
const states = {
|
||||
sent: "success",
|
||||
pending: "warning",
|
||||
declined: "secondary",
|
||||
};
|
||||
|
||||
export default function TableComponent({ data }) {
|
||||
var keys = Object.keys(data[0]).map(i => i.toUpperCase());
|
||||
keys.shift(); // delete "id" key
|
||||
|
||||
return (
|
||||
<Table className="mb-0">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{keys.map(key => (
|
||||
<TableCell key={key}>{key}</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{data.map(({ id, name, email, product, price, date, city, status }) => (
|
||||
<TableRow key={id}>
|
||||
<TableCell className="pl-3 fw-normal">{name}</TableCell>
|
||||
<TableCell>{email}</TableCell>
|
||||
<TableCell>{product}</TableCell>
|
||||
<TableCell>{price}</TableCell>
|
||||
<TableCell>{date}</TableCell>
|
||||
<TableCell>{city}</TableCell>
|
||||
<TableCell>
|
||||
<Button
|
||||
color={states[status.toLowerCase()]}
|
||||
size="small"
|
||||
className="px-2"
|
||||
variant="contained"
|
||||
>
|
||||
{status}
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
182
frontend/react-material-admin/src/pages/dashboard/mock.js
vendored
Normal file
182
frontend/react-material-admin/src/pages/dashboard/mock.js
vendored
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
export default {
|
||||
tasks: [
|
||||
{
|
||||
id: 0,
|
||||
type: "Meeting",
|
||||
title: "Meeting with Andrew Piker",
|
||||
time: "9:00"
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
type: "Call",
|
||||
title: "Call with HT Company",
|
||||
time: "12:00"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: "Meeting",
|
||||
title: "Meeting with Zoe Alison",
|
||||
time: "14:00"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: "Interview",
|
||||
title: "Interview with HR",
|
||||
time: "15:00"
|
||||
}
|
||||
],
|
||||
bigStat: [
|
||||
{
|
||||
product: "Light Blue",
|
||||
total: {
|
||||
monthly: 4232,
|
||||
weekly: 1465,
|
||||
daily: 199,
|
||||
percent: { value: 3.7, profit: false }
|
||||
},
|
||||
color: "primary",
|
||||
registrations: {
|
||||
monthly: { value: 830, profit: false },
|
||||
weekly: { value: 215, profit: true },
|
||||
daily: { value: 33, profit: true }
|
||||
},
|
||||
bounce: {
|
||||
monthly: { value: 4.5, profit: false },
|
||||
weekly: { value: 3, profit: true },
|
||||
daily: { value: 3.25, profit: true }
|
||||
}
|
||||
},
|
||||
{
|
||||
product: "Sing App",
|
||||
total: {
|
||||
monthly: 754,
|
||||
weekly: 180,
|
||||
daily: 27,
|
||||
percent: { value: 2.5, profit: true }
|
||||
},
|
||||
color: "warning",
|
||||
registrations: {
|
||||
monthly: { value: 32, profit: true },
|
||||
weekly: { value: 8, profit: true },
|
||||
daily: { value: 2, profit: false }
|
||||
},
|
||||
bounce: {
|
||||
monthly: { value: 2.5, profit: true },
|
||||
weekly: { value: 4, profit: false },
|
||||
daily: { value: 4.5, profit: false }
|
||||
}
|
||||
},
|
||||
{
|
||||
product: "RNS",
|
||||
total: {
|
||||
monthly: 1025,
|
||||
weekly: 301,
|
||||
daily: 44,
|
||||
percent: { value: 3.1, profit: true }
|
||||
},
|
||||
color: "secondary",
|
||||
registrations: {
|
||||
monthly: { value: 230, profit: true },
|
||||
weekly: { value: 58, profit: false },
|
||||
daily: { value: 15, profit: false }
|
||||
},
|
||||
bounce: {
|
||||
monthly: { value: 21.5, profit: false },
|
||||
weekly: { value: 19.35, profit: false },
|
||||
daily: { value: 10.1, profit: true }
|
||||
}
|
||||
}
|
||||
],
|
||||
notifications: [
|
||||
{
|
||||
id: 0,
|
||||
icon: "thumbs-up",
|
||||
color: "primary",
|
||||
content:
|
||||
'Ken <span className="fw-semi-bold">accepts</span> your invitation'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
icon: "file",
|
||||
color: "success",
|
||||
content: "Report from LT Company"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
icon: "envelope",
|
||||
color: "danger",
|
||||
content: '4 <span className="fw-semi-bold">Private</span> Mails'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
icon: "comment",
|
||||
color: "success",
|
||||
content: '3 <span className="fw-semi-bold">Comments</span> to your Post'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
icon: "cog",
|
||||
color: "light",
|
||||
content: 'New <span className="fw-semi-bold">Version</span> of RNS app'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
icon: "bell",
|
||||
color: "info",
|
||||
content:
|
||||
'15 <span className="fw-semi-bold">Notifications</span> from Social Apps'
|
||||
}
|
||||
],
|
||||
table: [
|
||||
{
|
||||
id: 0,
|
||||
name: "Mark Otto",
|
||||
email: "ottoto@wxample.com",
|
||||
product: "ON the Road",
|
||||
price: "$25 224.2",
|
||||
date: "11 May 2017",
|
||||
city: "Otsego",
|
||||
status: "Sent"
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: "Jacob Thornton",
|
||||
email: "thornton@wxample.com",
|
||||
product: "HP Core i7",
|
||||
price: "$1 254.2",
|
||||
date: "4 Jun 2017",
|
||||
city: "Fivepointville",
|
||||
status: "Sent"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Larry the Bird",
|
||||
email: "bird@wxample.com",
|
||||
product: "Air Pro",
|
||||
price: "$1 570.0",
|
||||
date: "27 Aug 2017",
|
||||
city: "Leadville North",
|
||||
status: "Pending"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Joseph May",
|
||||
email: "josephmay@wxample.com",
|
||||
product: "Version Control",
|
||||
price: "$5 224.5",
|
||||
date: "19 Feb 2018",
|
||||
city: "Seaforth",
|
||||
status: "Declined"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "Peter Horadnia",
|
||||
email: "horadnia@wxample.com",
|
||||
product: "Let's Dance",
|
||||
price: "$43 594.7",
|
||||
date: "1 Mar 2018",
|
||||
city: "Hanoverton",
|
||||
status: "Sent"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "Dashboard",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "Dashboard.js"
|
||||
}
|
||||
115
frontend/react-material-admin/src/pages/dashboard/styles.js
vendored
Normal file
115
frontend/react-material-admin/src/pages/dashboard/styles.js
vendored
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import { makeStyles } from "@material-ui/styles";
|
||||
|
||||
export default makeStyles(theme => ({
|
||||
card: {
|
||||
minHeight: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
},
|
||||
visitsNumberContainer: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexGrow: 1,
|
||||
paddingBottom: theme.spacing(1),
|
||||
},
|
||||
progressSection: {
|
||||
marginBottom: theme.spacing(1),
|
||||
},
|
||||
progressTitle: {
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
progress: {
|
||||
marginBottom: theme.spacing(1),
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
},
|
||||
pieChartLegendWrapper: {
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "flex-end",
|
||||
marginRight: theme.spacing(1),
|
||||
},
|
||||
legendItemContainer: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginBottom: theme.spacing(1),
|
||||
},
|
||||
fullHeightBody: {
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
tableWidget: {
|
||||
overflowX: "auto",
|
||||
},
|
||||
progressBar: {
|
||||
backgroundColor: theme.palette.warning.main,
|
||||
},
|
||||
performanceLegendWrapper: {
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
alignItems: "center",
|
||||
marginBottom: theme.spacing(1),
|
||||
},
|
||||
legendElement: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginRight: theme.spacing(2),
|
||||
},
|
||||
legendElementText: {
|
||||
marginLeft: theme.spacing(1),
|
||||
},
|
||||
serverOverviewElement: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
maxWidth: "100%",
|
||||
},
|
||||
serverOverviewElementText: {
|
||||
minWidth: 145,
|
||||
paddingRight: theme.spacing(2),
|
||||
},
|
||||
serverOverviewElementChartWrapper: {
|
||||
width: "100%",
|
||||
},
|
||||
mainChartBody: {
|
||||
overflowX: "auto",
|
||||
},
|
||||
mainChartHeader: {
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
[theme.breakpoints.only("xs")]: {
|
||||
flexWrap: "wrap",
|
||||
},
|
||||
},
|
||||
mainChartHeaderLabels: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
[theme.breakpoints.only("xs")]: {
|
||||
order: 3,
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
marginTop: theme.spacing(3),
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
},
|
||||
mainChartHeaderLabel: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginLeft: theme.spacing(3),
|
||||
},
|
||||
mainChartSelectRoot: {
|
||||
borderColor: theme.palette.text.hint + "80 !important",
|
||||
},
|
||||
mainChartSelect: {
|
||||
padding: 10,
|
||||
paddingRight: 25,
|
||||
},
|
||||
mainChartLegentElement: {
|
||||
fontSize: "18px !important",
|
||||
marginLeft: theme.spacing(1),
|
||||
},
|
||||
}));
|
||||
55
frontend/react-material-admin/src/pages/error/Error.js
vendored
Normal file
55
frontend/react-material-admin/src/pages/error/Error.js
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import React from "react";
|
||||
import { Grid, Paper, Typography, Button } from "@material-ui/core";
|
||||
import { Link } from "react-router-dom";
|
||||
import classnames from "classnames";
|
||||
|
||||
// styles
|
||||
import useStyles from "./styles";
|
||||
|
||||
// logo
|
||||
import logo from "./logo.svg";
|
||||
|
||||
export default function Error() {
|
||||
var classes = useStyles();
|
||||
|
||||
return (
|
||||
<Grid container className={classes.container}>
|
||||
<div className={classes.logotype}>
|
||||
<img className={classes.logotypeIcon} src={logo} alt="logo" />
|
||||
<Typography variant="h3" color="white" className={classes.logotypeText}>
|
||||
Material Admin
|
||||
</Typography>
|
||||
</div>
|
||||
<Paper classes={{ root: classes.paperRoot }}>
|
||||
<Typography
|
||||
variant="h1"
|
||||
color="primary"
|
||||
className={classnames(classes.textRow, classes.errorCode)}
|
||||
>
|
||||
404
|
||||
</Typography>
|
||||
<Typography variant="h5" color="primary" className={classes.textRow}>
|
||||
Oops. Looks like the page you're looking for no longer exists
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h6"
|
||||
color="text"
|
||||
colorBrightness="secondary"
|
||||
className={classnames(classes.textRow, classes.safetyText)}
|
||||
>
|
||||
But we're here to bring you back to safety
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
component={Link}
|
||||
to="/"
|
||||
size="large"
|
||||
className={classes.backButton}
|
||||
>
|
||||
Back to Home
|
||||
</Button>
|
||||
</Paper>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
13
frontend/react-material-admin/src/pages/error/logo.svg
Normal file
13
frontend/react-material-admin/src/pages/error/logo.svg
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="131px" height="106px" viewBox="0 0 131 106" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 53.2 (72643) - https://sketchapp.com -->
|
||||
<title>logo_white</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="logo_white" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
||||
<g id="Group-39" transform="translate(12.000000, 16.000000)" stroke-width="16.3476923">
|
||||
<path d="M4,72.4669231 L52.2318841,0" id="Line-10" stroke="#FFFFFF"></path>
|
||||
<path d="M1.10416667,74.69 L105.551432,74.69" id="Line-10" stroke="#FBC02D"></path>
|
||||
<path d="M52.2318841,0 L105.551432,74.69" id="Line-10" stroke="#FF5252"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 858 B |
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "Error",
|
||||
"version": "0.0.0",
|
||||
"main": "Error.js",
|
||||
"private": true
|
||||
}
|
||||
61
frontend/react-material-admin/src/pages/error/styles.js
vendored
Normal file
61
frontend/react-material-admin/src/pages/error/styles.js
vendored
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import { makeStyles } from "@material-ui/styles";
|
||||
|
||||
export default makeStyles(theme => ({
|
||||
container: {
|
||||
height: "100vh",
|
||||
width: "100vw",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
},
|
||||
logotype: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginBottom: theme.spacing(12),
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
display: "none",
|
||||
},
|
||||
},
|
||||
logotypeText: {
|
||||
fontWeight: 500,
|
||||
color: "white",
|
||||
marginLeft: theme.spacing(2),
|
||||
},
|
||||
logotypeIcon: {
|
||||
width: 70,
|
||||
marginRight: theme.spacing(2),
|
||||
},
|
||||
paperRoot: {
|
||||
boxShadow: theme.customShadows.widgetDark,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
paddingTop: theme.spacing(8),
|
||||
paddingBottom: theme.spacing(8),
|
||||
paddingLeft: theme.spacing(6),
|
||||
paddingRight: theme.spacing(6),
|
||||
maxWidth: 404,
|
||||
},
|
||||
textRow: {
|
||||
marginBottom: theme.spacing(10),
|
||||
textAlign: "center",
|
||||
},
|
||||
errorCode: {
|
||||
fontSize: 148,
|
||||
fontWeight: 600,
|
||||
},
|
||||
safetyText: {
|
||||
fontWeight: 300,
|
||||
color: theme.palette.text.hint,
|
||||
},
|
||||
backButton: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
textTransform: "none",
|
||||
fontSize: 22,
|
||||
},
|
||||
}));
|
||||
13564
frontend/react-material-admin/src/pages/icons/Icons.js
vendored
Normal file
13564
frontend/react-material-admin/src/pages/icons/Icons.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "Icons",
|
||||
"version": "0.0.0",
|
||||
"main": "Icons.js",
|
||||
"private": true
|
||||
}
|
||||
32
frontend/react-material-admin/src/pages/icons/styles.js
vendored
Normal file
32
frontend/react-material-admin/src/pages/icons/styles.js
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { makeStyles } from "@material-ui/styles";
|
||||
|
||||
export default makeStyles(theme => ({
|
||||
titleBold: {
|
||||
fontWeight: 600,
|
||||
},
|
||||
iconsBar: {
|
||||
marginBottom: theme.spacing(4),
|
||||
borderBottom: "1px solid",
|
||||
borderBottomColor: theme.palette.text.hint + "80",
|
||||
},
|
||||
tab: {
|
||||
color: theme.palette.primary.light + "CC",
|
||||
},
|
||||
materailIcon: {
|
||||
display: "flex",
|
||||
paddingLeft: `${theme.spacing(4)}px !important`,
|
||||
paddingRight: `${theme.spacing(4)}px !important`,
|
||||
color: theme.palette.text.secondary,
|
||||
fontSize: 24,
|
||||
overflowX: "hidden",
|
||||
},
|
||||
materialIconText: {
|
||||
marginLeft: theme.spacing(2),
|
||||
fontSize: 14,
|
||||
},
|
||||
iconsContainer: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
overflow: "hidden",
|
||||
paddingBottom: theme.spacing(2),
|
||||
},
|
||||
}));
|
||||
255
frontend/react-material-admin/src/pages/login/Login.js
vendored
Normal file
255
frontend/react-material-admin/src/pages/login/Login.js
vendored
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
import React, { useState } from "react";
|
||||
import {
|
||||
Grid,
|
||||
CircularProgress,
|
||||
Typography,
|
||||
Button,
|
||||
Tabs,
|
||||
Tab,
|
||||
TextField,
|
||||
Fade,
|
||||
} from "@material-ui/core";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import classnames from "classnames";
|
||||
|
||||
// styles
|
||||
import useStyles from "./styles";
|
||||
|
||||
// logo
|
||||
import logo from "./logo.svg";
|
||||
import google from "../../images/google.svg";
|
||||
|
||||
// context
|
||||
import { useUserDispatch, loginUser } from "../../context/UserContext";
|
||||
|
||||
function Login(props) {
|
||||
var classes = useStyles();
|
||||
|
||||
// global
|
||||
var userDispatch = useUserDispatch();
|
||||
|
||||
// local
|
||||
var [isLoading, setIsLoading] = useState(false);
|
||||
var [error, setError] = useState(null);
|
||||
var [activeTabId, setActiveTabId] = useState(0);
|
||||
var [nameValue, setNameValue] = useState("");
|
||||
var [loginValue, setLoginValue] = useState("");
|
||||
var [passwordValue, setPasswordValue] = useState("");
|
||||
|
||||
return (
|
||||
<Grid container className={classes.container}>
|
||||
<div className={classes.logotypeContainer}>
|
||||
<img src={logo} alt="logo" className={classes.logotypeImage} />
|
||||
<Typography className={classes.logotypeText}>Material Admin</Typography>
|
||||
</div>
|
||||
<div className={classes.formContainer}>
|
||||
<div className={classes.form}>
|
||||
<Tabs
|
||||
value={activeTabId}
|
||||
onChange={(e, id) => setActiveTabId(id)}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
centered
|
||||
>
|
||||
<Tab label="Login" classes={{ root: classes.tab }} />
|
||||
<Tab label="New User" classes={{ root: classes.tab }} />
|
||||
</Tabs>
|
||||
{activeTabId === 0 && (
|
||||
<React.Fragment>
|
||||
<Typography variant="h1" className={classes.greeting}>
|
||||
Good Morning, User
|
||||
</Typography>
|
||||
<Button size="large" className={classes.googleButton}>
|
||||
<img src={google} alt="google" className={classes.googleIcon} />
|
||||
Sign in with Google
|
||||
</Button>
|
||||
<div className={classes.formDividerContainer}>
|
||||
<div className={classes.formDivider} />
|
||||
<Typography className={classes.formDividerWord}>or</Typography>
|
||||
<div className={classes.formDivider} />
|
||||
</div>
|
||||
<Fade in={error}>
|
||||
<Typography color="secondary" className={classes.errorMessage}>
|
||||
Something is wrong with your login or password :(
|
||||
</Typography>
|
||||
</Fade>
|
||||
<TextField
|
||||
id="email"
|
||||
InputProps={{
|
||||
classes: {
|
||||
underline: classes.textFieldUnderline,
|
||||
input: classes.textField,
|
||||
},
|
||||
}}
|
||||
value={loginValue}
|
||||
onChange={e => setLoginValue(e.target.value)}
|
||||
margin="normal"
|
||||
placeholder="Email Adress"
|
||||
type="email"
|
||||
fullWidth
|
||||
/>
|
||||
<TextField
|
||||
id="password"
|
||||
InputProps={{
|
||||
classes: {
|
||||
underline: classes.textFieldUnderline,
|
||||
input: classes.textField,
|
||||
},
|
||||
}}
|
||||
value={passwordValue}
|
||||
onChange={e => setPasswordValue(e.target.value)}
|
||||
margin="normal"
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
fullWidth
|
||||
/>
|
||||
<div className={classes.formButtons}>
|
||||
{isLoading ? (
|
||||
<CircularProgress size={26} className={classes.loginLoader} />
|
||||
) : (
|
||||
<Button
|
||||
disabled={
|
||||
loginValue.length === 0 || passwordValue.length === 0
|
||||
}
|
||||
onClick={() =>
|
||||
loginUser(
|
||||
userDispatch,
|
||||
loginValue,
|
||||
passwordValue,
|
||||
props.history,
|
||||
setIsLoading,
|
||||
setError,
|
||||
)
|
||||
}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="large"
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
color="primary"
|
||||
size="large"
|
||||
className={classes.forgetButton}
|
||||
>
|
||||
Forget Password
|
||||
</Button>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{activeTabId === 1 && (
|
||||
<React.Fragment>
|
||||
<Typography variant="h1" className={classes.greeting}>
|
||||
Welcome!
|
||||
</Typography>
|
||||
<Typography variant="h2" className={classes.subGreeting}>
|
||||
Create your account
|
||||
</Typography>
|
||||
<Fade in={error}>
|
||||
<Typography color="secondary" className={classes.errorMessage}>
|
||||
Something is wrong with your login or password :(
|
||||
</Typography>
|
||||
</Fade>
|
||||
<TextField
|
||||
id="name"
|
||||
InputProps={{
|
||||
classes: {
|
||||
underline: classes.textFieldUnderline,
|
||||
input: classes.textField,
|
||||
},
|
||||
}}
|
||||
value={nameValue}
|
||||
onChange={e => setNameValue(e.target.value)}
|
||||
margin="normal"
|
||||
placeholder="Full Name"
|
||||
type="text"
|
||||
fullWidth
|
||||
/>
|
||||
<TextField
|
||||
id="email"
|
||||
InputProps={{
|
||||
classes: {
|
||||
underline: classes.textFieldUnderline,
|
||||
input: classes.textField,
|
||||
},
|
||||
}}
|
||||
value={loginValue}
|
||||
onChange={e => setLoginValue(e.target.value)}
|
||||
margin="normal"
|
||||
placeholder="Email Adress"
|
||||
type="email"
|
||||
fullWidth
|
||||
/>
|
||||
<TextField
|
||||
id="password"
|
||||
InputProps={{
|
||||
classes: {
|
||||
underline: classes.textFieldUnderline,
|
||||
input: classes.textField,
|
||||
},
|
||||
}}
|
||||
value={passwordValue}
|
||||
onChange={e => setPasswordValue(e.target.value)}
|
||||
margin="normal"
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
fullWidth
|
||||
/>
|
||||
<div className={classes.creatingButtonContainer}>
|
||||
{isLoading ? (
|
||||
<CircularProgress size={26} />
|
||||
) : (
|
||||
<Button
|
||||
onClick={() =>
|
||||
loginUser(
|
||||
userDispatch,
|
||||
loginValue,
|
||||
passwordValue,
|
||||
props.history,
|
||||
setIsLoading,
|
||||
setError,
|
||||
)
|
||||
}
|
||||
disabled={
|
||||
loginValue.length === 0 ||
|
||||
passwordValue.length === 0 ||
|
||||
nameValue.length === 0
|
||||
}
|
||||
size="large"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
fullWidth
|
||||
className={classes.createAccountButton}
|
||||
>
|
||||
Create your account
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className={classes.formDividerContainer}>
|
||||
<div className={classes.formDivider} />
|
||||
<Typography className={classes.formDividerWord}>or</Typography>
|
||||
<div className={classes.formDivider} />
|
||||
</div>
|
||||
<Button
|
||||
size="large"
|
||||
className={classnames(
|
||||
classes.googleButton,
|
||||
classes.googleButtonCreating,
|
||||
)}
|
||||
>
|
||||
<img src={google} alt="google" className={classes.googleIcon} />
|
||||
Sign in with Google
|
||||
</Button>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
<Typography color="primary" className={classes.copyright}>
|
||||
© 2014-2019 Flatlogic, LLC. All rights reserved.
|
||||
</Typography>
|
||||
</div>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
export default withRouter(Login);
|
||||
13
frontend/react-material-admin/src/pages/login/logo.svg
Normal file
13
frontend/react-material-admin/src/pages/login/logo.svg
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="131px" height="106px" viewBox="0 0 131 106" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 53.2 (72643) - https://sketchapp.com -->
|
||||
<title>logo_white</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="logo_white" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
||||
<g id="Group-39" transform="translate(12.000000, 16.000000)" stroke-width="16.3476923">
|
||||
<path d="M4,72.4669231 L52.2318841,0" id="Line-10" stroke="#FFFFFF"></path>
|
||||
<path d="M1.10416667,74.69 L105.551432,74.69" id="Line-10" stroke="#FBC02D"></path>
|
||||
<path d="M52.2318841,0 L105.551432,74.69" id="Line-10" stroke="#FF5252"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 858 B |
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "Login",
|
||||
"version": "0.0.0",
|
||||
"main": "Login.js",
|
||||
"private": true
|
||||
}
|
||||
148
frontend/react-material-admin/src/pages/login/styles.js
vendored
Normal file
148
frontend/react-material-admin/src/pages/login/styles.js
vendored
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
import { makeStyles } from "@material-ui/styles";
|
||||
|
||||
export default makeStyles(theme => ({
|
||||
container: {
|
||||
height: "100vh",
|
||||
width: "100vw",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
},
|
||||
logotypeContainer: {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
width: "60%",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
[theme.breakpoints.down("md")]: {
|
||||
width: "50%",
|
||||
},
|
||||
[theme.breakpoints.down("md")]: {
|
||||
display: "none",
|
||||
},
|
||||
},
|
||||
logotypeImage: {
|
||||
width: 165,
|
||||
marginBottom: theme.spacing(4),
|
||||
},
|
||||
logotypeText: {
|
||||
color: "white",
|
||||
fontWeight: 500,
|
||||
fontSize: 84,
|
||||
[theme.breakpoints.down("md")]: {
|
||||
fontSize: 48,
|
||||
},
|
||||
},
|
||||
formContainer: {
|
||||
width: "40%",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
[theme.breakpoints.down("md")]: {
|
||||
width: "50%",
|
||||
},
|
||||
},
|
||||
form: {
|
||||
width: 320,
|
||||
},
|
||||
tab: {
|
||||
fontWeight: 400,
|
||||
fontSize: 18,
|
||||
},
|
||||
greeting: {
|
||||
fontWeight: 500,
|
||||
textAlign: "center",
|
||||
marginTop: theme.spacing(4),
|
||||
},
|
||||
subGreeting: {
|
||||
fontWeight: 500,
|
||||
textAlign: "center",
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
googleButton: {
|
||||
marginTop: theme.spacing(6),
|
||||
boxShadow: theme.customShadows.widget,
|
||||
backgroundColor: "white",
|
||||
width: "100%",
|
||||
textTransform: "none",
|
||||
},
|
||||
googleButtonCreating: {
|
||||
marginTop: 0,
|
||||
},
|
||||
googleIcon: {
|
||||
width: 30,
|
||||
marginRight: theme.spacing(2),
|
||||
},
|
||||
creatingButtonContainer: {
|
||||
marginTop: theme.spacing(2.5),
|
||||
height: 46,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
createAccountButton: {
|
||||
height: 46,
|
||||
textTransform: "none",
|
||||
},
|
||||
formDividerContainer: {
|
||||
marginTop: theme.spacing(4),
|
||||
marginBottom: theme.spacing(4),
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
formDividerWord: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(2),
|
||||
},
|
||||
formDivider: {
|
||||
flexGrow: 1,
|
||||
height: 1,
|
||||
backgroundColor: theme.palette.text.hint + "40",
|
||||
},
|
||||
errorMessage: {
|
||||
textAlign: "center",
|
||||
},
|
||||
textFieldUnderline: {
|
||||
"&:before": {
|
||||
borderBottomColor: theme.palette.primary.light,
|
||||
},
|
||||
"&:after": {
|
||||
borderBottomColor: theme.palette.primary.main,
|
||||
},
|
||||
"&:hover:before": {
|
||||
borderBottomColor: `${theme.palette.primary.light} !important`,
|
||||
},
|
||||
},
|
||||
textField: {
|
||||
borderBottomColor: theme.palette.background.light,
|
||||
},
|
||||
formButtons: {
|
||||
width: "100%",
|
||||
marginTop: theme.spacing(4),
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
},
|
||||
forgetButton: {
|
||||
textTransform: "none",
|
||||
fontWeight: 400,
|
||||
},
|
||||
loginLoader: {
|
||||
marginLeft: theme.spacing(4),
|
||||
},
|
||||
copyright: {
|
||||
marginTop: theme.spacing(4),
|
||||
whiteSpace: "nowrap",
|
||||
[theme.breakpoints.up("md")]: {
|
||||
position: "absolute",
|
||||
bottom: theme.spacing(2),
|
||||
},
|
||||
},
|
||||
}));
|
||||
39
frontend/react-material-admin/src/pages/maps/Maps.js
vendored
Normal file
39
frontend/react-material-admin/src/pages/maps/Maps.js
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import React from "react";
|
||||
import {
|
||||
withGoogleMap,
|
||||
withScriptjs,
|
||||
GoogleMap,
|
||||
Marker,
|
||||
} from "react-google-maps";
|
||||
|
||||
// styles
|
||||
import useStyles from "./styles";
|
||||
|
||||
const BasicMap = withScriptjs(
|
||||
withGoogleMap(() => (
|
||||
<GoogleMap
|
||||
defaultZoom={12}
|
||||
defaultCenter={{
|
||||
lat: parseFloat(-37.813179),
|
||||
lng: parseFloat(144.950259),
|
||||
}}
|
||||
>
|
||||
<Marker position={{ lat: -37.813179, lng: 144.950259 }} />
|
||||
</GoogleMap>
|
||||
)),
|
||||
);
|
||||
|
||||
export default function Maps() {
|
||||
var classes = useStyles();
|
||||
|
||||
return (
|
||||
<div className={classes.mapContainer}>
|
||||
<BasicMap
|
||||
googleMapURL="https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places&key=AIzaSyB7OXmzfQYua_1LEhRdqsoYzyJOPh9hGLg"
|
||||
loadingElement={<div style={{ height: "inherit", width: "inherit" }} />}
|
||||
containerElement={<div style={{ height: "100%" }} />}
|
||||
mapElement={<div style={{ height: "100%" }} />}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "Maps",
|
||||
"version": "0.0.0",
|
||||
"main": "Maps.js",
|
||||
"private": true
|
||||
}
|
||||
8
frontend/react-material-admin/src/pages/maps/styles.js
vendored
Normal file
8
frontend/react-material-admin/src/pages/maps/styles.js
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { makeStyles } from "@material-ui/styles";
|
||||
|
||||
export default makeStyles(theme => ({
|
||||
mapContainer: {
|
||||
height: "100%",
|
||||
margin: -theme.spacing(1) * 3,
|
||||
},
|
||||
}));
|
||||
386
frontend/react-material-admin/src/pages/notifications/Notifications.js
vendored
Normal file
386
frontend/react-material-admin/src/pages/notifications/Notifications.js
vendored
Normal file
|
|
@ -0,0 +1,386 @@
|
|||
import React, { useState } from "react";
|
||||
import { Grid } from "@material-ui/core";
|
||||
import { Close as CloseIcon } from "@material-ui/icons";
|
||||
import { ToastContainer, toast } from "react-toastify";
|
||||
import SyntaxHighlighter from "react-syntax-highlighter";
|
||||
import { docco } from "react-syntax-highlighter/dist/esm/styles/hljs";
|
||||
import classnames from "classnames";
|
||||
|
||||
// styles
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import useStyles from "./styles";
|
||||
|
||||
// components
|
||||
import Widget from "../../components/Widget/Widget";
|
||||
import PageTitle from "../../components/PageTitle/PageTitle";
|
||||
import Notification from "../../components/Notification";
|
||||
import { Typography, Button } from "../../components/Wrappers/Wrappers";
|
||||
|
||||
const positions = [
|
||||
toast.POSITION.TOP_LEFT,
|
||||
toast.POSITION.TOP_CENTER,
|
||||
toast.POSITION.TOP_RIGHT,
|
||||
toast.POSITION.BOTTOM_LEFT,
|
||||
toast.POSITION.BOTTOM_CENTER,
|
||||
toast.POSITION.BOTTOM_RIGHT,
|
||||
];
|
||||
|
||||
export default function NotificationsPage(props) {
|
||||
var classes = useStyles();
|
||||
|
||||
// local
|
||||
var [notificationsPosition, setNotificationPosition] = useState(2);
|
||||
var [errorToastId, setErrorToastId] = useState(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title="Notifications" />
|
||||
<Grid container spacing={8}>
|
||||
<ToastContainer
|
||||
className={classes.toastsContainer}
|
||||
closeButton={
|
||||
<CloseButton className={classes.notificationCloseButton} />
|
||||
}
|
||||
closeOnClick={false}
|
||||
progressClassName={classes.notificationProgress}
|
||||
/>
|
||||
<Grid item xs={12} md={6} lg={4}>
|
||||
<Widget title="Layout Options" disableWidgetMenu>
|
||||
<Typography>
|
||||
There are few position options available for notifications. You
|
||||
can click any of them to change notifications position:
|
||||
</Typography>
|
||||
<div className={classes.layoutContainer}>
|
||||
<div className={classes.layoutButtonsRow}>
|
||||
<button
|
||||
onClick={() => changeNotificationPosition(0)}
|
||||
className={classnames(classes.layoutButton, {
|
||||
[classes.layoutButtonActive]: notificationsPosition === 0,
|
||||
})}
|
||||
/>
|
||||
<button
|
||||
onClick={() => changeNotificationPosition(1)}
|
||||
className={classnames(classes.layoutButton, {
|
||||
[classes.layoutButtonActive]: notificationsPosition === 1,
|
||||
})}
|
||||
/>
|
||||
<button
|
||||
onClick={() => changeNotificationPosition(2)}
|
||||
className={classnames(classes.layoutButton, {
|
||||
[classes.layoutButtonActive]: notificationsPosition === 2,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<Typography className={classes.layoutText} size="md">
|
||||
Click any position
|
||||
</Typography>
|
||||
<div className={classes.layoutButtonsRow}>
|
||||
<button
|
||||
onClick={() => changeNotificationPosition(3)}
|
||||
className={classnames(classes.layoutButton, {
|
||||
[classes.layoutButtonActive]: notificationsPosition === 3,
|
||||
})}
|
||||
/>
|
||||
<button
|
||||
onClick={() => changeNotificationPosition(4)}
|
||||
className={classnames(classes.layoutButton, {
|
||||
[classes.layoutButtonActive]: notificationsPosition === 4,
|
||||
})}
|
||||
/>
|
||||
<button
|
||||
onClick={() => changeNotificationPosition(5)}
|
||||
className={classnames(classes.layoutButton, {
|
||||
[classes.layoutButtonActive]: notificationsPosition === 5,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Widget>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6} lg={4}>
|
||||
<Widget title="Notifications Types" disableWidgetMenu>
|
||||
<Typography>
|
||||
Different types of notifications for lost of use cases. Custom
|
||||
classes are also supported.
|
||||
</Typography>
|
||||
<div className={classes.buttonsContainer}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => handleNotificationCall("info")}
|
||||
className={classnames(classes.notificationCallButton)}
|
||||
>
|
||||
Info Message
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={() => handleNotificationCall("error")}
|
||||
className={classnames(classes.notificationCallButton)}
|
||||
>
|
||||
Error + Retry Message
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="success"
|
||||
onClick={() => handleNotificationCall("success")}
|
||||
className={classnames(classes.notificationCallButton)}
|
||||
>
|
||||
Success Message
|
||||
</Button>
|
||||
</div>
|
||||
</Widget>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6} lg={4}>
|
||||
<Widget title="Usage" disableWidgetMenu>
|
||||
<Typography>
|
||||
Notifications are created with the help of{" "}
|
||||
<a href="https://github.com/fkhadra/react-toastify">
|
||||
react-toastify
|
||||
</a>
|
||||
</Typography>
|
||||
<div className={classes.codeContainer}>
|
||||
<SyntaxHighlighter
|
||||
className={classes.codeComponent}
|
||||
language="javascript"
|
||||
style={docco}
|
||||
>{`
|
||||
// import needed components, functions and styles
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
|
||||
const Page = () => {
|
||||
<div>
|
||||
<ToastContainer />
|
||||
<button onClick={() => toast('Toast Message')}>
|
||||
show notification
|
||||
</button>
|
||||
</div>
|
||||
};
|
||||
`}</SyntaxHighlighter>
|
||||
<Typography variant="caption">
|
||||
For more API information refer to the library documentation
|
||||
</Typography>
|
||||
</div>
|
||||
</Widget>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6} lg={4}>
|
||||
<Widget title="Notification Types Examples" disableWidgetMenu>
|
||||
<Notification
|
||||
className={classes.notificationItem}
|
||||
shadowless
|
||||
type="message"
|
||||
message="Thanks for Checking out Messenger"
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
/>
|
||||
<Notification
|
||||
className={classes.notificationItem}
|
||||
shadowless
|
||||
type="feedback"
|
||||
message="New user feedback received"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
/>
|
||||
<Notification
|
||||
className={classes.notificationItem}
|
||||
shadowless
|
||||
type="customer"
|
||||
message="New customer is registered"
|
||||
variant="contained"
|
||||
color="success"
|
||||
/>
|
||||
<Notification
|
||||
className={classes.notificationItem}
|
||||
shadowless
|
||||
type="shipped"
|
||||
message="The order was shipped"
|
||||
variant="contained"
|
||||
color="warning"
|
||||
/>
|
||||
<Notification
|
||||
className={classes.notificationItem}
|
||||
shadowless
|
||||
type="delivered"
|
||||
message="The order was delivered"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
/>
|
||||
<Notification
|
||||
className={classes.notificationItem}
|
||||
shadowless
|
||||
type="defence"
|
||||
message="5 Defence alerts"
|
||||
variant="contained"
|
||||
color="info"
|
||||
/>
|
||||
</Widget>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6} lg={4}>
|
||||
<Widget title="Notification Types Examples" disableWidgetMenu>
|
||||
<Notification
|
||||
className={classes.notificationItem}
|
||||
type="report"
|
||||
message="New report has been received"
|
||||
color="secondary"
|
||||
/>
|
||||
<Notification
|
||||
className={classes.notificationItem}
|
||||
type="feedback"
|
||||
message="New user feedback received"
|
||||
color="primary"
|
||||
/>
|
||||
<Notification
|
||||
className={classes.notificationItem}
|
||||
type="shipped"
|
||||
message="The item was shipped"
|
||||
color="success"
|
||||
/>
|
||||
<Notification
|
||||
className={classes.notificationItem}
|
||||
type="message"
|
||||
message="The new message from user @nahawaii"
|
||||
color="warning"
|
||||
/>
|
||||
<Notification
|
||||
className={classes.notificationItem}
|
||||
type="upload"
|
||||
message="Your file is ready to upload"
|
||||
color="primary"
|
||||
/>
|
||||
<Notification
|
||||
className={classes.notificationItem}
|
||||
type="disc"
|
||||
message="The disc is full"
|
||||
color="info"
|
||||
/>
|
||||
</Widget>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6} lg={4}>
|
||||
<Widget title="Notification Types Examples" disableWidgetMenu>
|
||||
<Notification
|
||||
className={classes.notificationItem}
|
||||
type="report"
|
||||
message="New report has been received"
|
||||
variant="rounded"
|
||||
color="secondary"
|
||||
/>
|
||||
<Notification
|
||||
className={classes.notificationItem}
|
||||
type="feedback"
|
||||
message="New user feedback received"
|
||||
variant="rounded"
|
||||
color="primary"
|
||||
/>
|
||||
<Notification
|
||||
className={classes.notificationItem}
|
||||
type="shipped"
|
||||
message="The item was shipped"
|
||||
variant="rounded"
|
||||
color="success"
|
||||
/>
|
||||
<Notification
|
||||
className={classes.notificationItem}
|
||||
type="message"
|
||||
message="The new message from user @nahawaii"
|
||||
variant="rounded"
|
||||
color="warning"
|
||||
/>
|
||||
<Notification
|
||||
className={classes.notificationItem}
|
||||
type="upload"
|
||||
message="Your file is ready to upload"
|
||||
variant="rounded"
|
||||
color="primary"
|
||||
/>
|
||||
<Notification
|
||||
className={classes.notificationItem}
|
||||
type="disc"
|
||||
message="The disc is full"
|
||||
variant="rounded"
|
||||
color="info"
|
||||
/>
|
||||
</Widget>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
|
||||
// #############################################################
|
||||
function sendNotification(componentProps, options) {
|
||||
return toast(
|
||||
<Notification
|
||||
{...componentProps}
|
||||
className={classes.notificationComponent}
|
||||
/>,
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
function retryErrorNotification() {
|
||||
var componentProps = {
|
||||
type: "message",
|
||||
message: "Message was sent successfully!",
|
||||
variant: "contained",
|
||||
color: "success",
|
||||
};
|
||||
toast.update(errorToastId, {
|
||||
render: <Notification {...componentProps} />,
|
||||
type: "success",
|
||||
});
|
||||
setErrorToastId(null);
|
||||
}
|
||||
|
||||
function handleNotificationCall(notificationType) {
|
||||
var componentProps;
|
||||
|
||||
if (errorToastId && notificationType === "error") return;
|
||||
|
||||
switch (notificationType) {
|
||||
case "info":
|
||||
componentProps = {
|
||||
type: "feedback",
|
||||
message: "New user feedback received",
|
||||
variant: "contained",
|
||||
color: "primary",
|
||||
};
|
||||
break;
|
||||
case "error":
|
||||
componentProps = {
|
||||
type: "message",
|
||||
message: "Message was not sent!",
|
||||
variant: "contained",
|
||||
color: "secondary",
|
||||
extraButton: "Resend",
|
||||
extraButtonClick: retryErrorNotification,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
componentProps = {
|
||||
type: "shipped",
|
||||
message: "The item was shipped",
|
||||
variant: "contained",
|
||||
color: "success",
|
||||
};
|
||||
}
|
||||
|
||||
var toastId = sendNotification(componentProps, {
|
||||
type: notificationType,
|
||||
position: positions[notificationsPosition],
|
||||
progressClassName: classes.progress,
|
||||
onClose: notificationType === "error" && (() => setErrorToastId(null)),
|
||||
className: classes.notification,
|
||||
});
|
||||
|
||||
if (notificationType === "error") setErrorToastId(toastId);
|
||||
}
|
||||
|
||||
function changeNotificationPosition(positionId) {
|
||||
setNotificationPosition(positionId);
|
||||
}
|
||||
}
|
||||
|
||||
// #############################################################
|
||||
function CloseButton({ closeToast, className }) {
|
||||
return <CloseIcon className={className} onClick={closeToast} />;
|
||||
}
|
||||
112
frontend/react-material-admin/src/pages/notifications/NotificationsContainer.js
vendored
Normal file
112
frontend/react-material-admin/src/pages/notifications/NotificationsContainer.js
vendored
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import React from "react";
|
||||
import { withStyles } from "@material-ui/core";
|
||||
import { compose, withState, withHandlers } from "recompose";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
import Notification from "../../components/Notification";
|
||||
import NotificationsView from "./NotificationsView";
|
||||
|
||||
const positions = [
|
||||
toast.POSITION.TOP_LEFT,
|
||||
toast.POSITION.TOP_CENTER,
|
||||
toast.POSITION.TOP_RIGHT,
|
||||
toast.POSITION.BOTTOM_LEFT,
|
||||
toast.POSITION.BOTTOM_CENTER,
|
||||
toast.POSITION.BOTTOM_RIGHT
|
||||
];
|
||||
|
||||
export default compose(
|
||||
withStyles(theme => ({
|
||||
/*progress: {
|
||||
visibility: "hidden"
|
||||
},
|
||||
notification: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
background: "transparent",
|
||||
boxShadow: "none",
|
||||
overflow: "visible"
|
||||
},
|
||||
notificationComponent: {
|
||||
paddingRight: theme.spacing.unit * 4
|
||||
}*/
|
||||
})),
|
||||
withState("notificationsPosition", "setNotificationPosition", 2),
|
||||
withState("errorToastId", "setErrorToastId", null),
|
||||
withHandlers({
|
||||
sendNotification: props => (componentProps, options) => {
|
||||
return toast(
|
||||
<Notification
|
||||
{...componentProps}
|
||||
className={props.classes.notificationComponent}
|
||||
/>,
|
||||
options
|
||||
);
|
||||
}
|
||||
}),
|
||||
withHandlers({
|
||||
retryErrorNotification: props => () => {
|
||||
const componentProps = {
|
||||
type: "message",
|
||||
message: "Message was sent successfully!",
|
||||
variant: "contained",
|
||||
color: "success",
|
||||
};
|
||||
|
||||
toast.update(props.errorToastId, {
|
||||
render: <Notification {...componentProps} />,
|
||||
type: "success"
|
||||
});
|
||||
props.setErrorToastId(null);
|
||||
}
|
||||
}),
|
||||
withHandlers({
|
||||
handleNotificationCall: props => notificationType => {
|
||||
let componentProps;
|
||||
|
||||
if (props.errorToastId && notificationType === "error") return;
|
||||
|
||||
switch (notificationType) {
|
||||
case "info":
|
||||
componentProps = {
|
||||
type: "feedback",
|
||||
message: "New user feedback received",
|
||||
variant: "contained",
|
||||
color: "primary"
|
||||
};
|
||||
break;
|
||||
case "error":
|
||||
componentProps = {
|
||||
type: "message",
|
||||
message: "Message was not sent!",
|
||||
variant: "contained",
|
||||
color: "secondary",
|
||||
extraButton: "Resend",
|
||||
extraButtonClick: props.retryErrorNotification
|
||||
};
|
||||
break;
|
||||
default:
|
||||
componentProps = {
|
||||
type: "shipped",
|
||||
message: "The item was shipped",
|
||||
variant: "contained",
|
||||
color: "success"
|
||||
};
|
||||
}
|
||||
|
||||
const toastId = props.sendNotification(componentProps, {
|
||||
type: notificationType,
|
||||
position: positions[props.notificationsPosition],
|
||||
progressClassName: props.classes.progress,
|
||||
onClose:
|
||||
notificationType === "error" && (() => props.setErrorToastId(null)),
|
||||
className: props.classes.notification
|
||||
});
|
||||
|
||||
if (notificationType === "error") props.setErrorToastId(toastId);
|
||||
},
|
||||
changeNotificationPosition: props => positionId => {
|
||||
props.setNotificationPosition(positionId);
|
||||
}
|
||||
})
|
||||
)(NotificationsView);
|
||||
175
frontend/react-material-admin/src/pages/notifications/NotificationsView.js
vendored
Normal file
175
frontend/react-material-admin/src/pages/notifications/NotificationsView.js
vendored
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
import React from 'react';
|
||||
import { Grid, withStyles } from '@material-ui/core';
|
||||
import { Close as CloseIcon } from '@material-ui/icons';
|
||||
import classnames from 'classnames';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import SyntaxHighlighter from 'react-syntax-highlighter';
|
||||
import { docco } from 'react-syntax-highlighter/dist/esm/styles/hljs';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
|
||||
import Widget from '../../components/Widget';
|
||||
import PageTitle from '../../components/PageTitle';
|
||||
import NotificationCustomComponent from '../../components/Notification';
|
||||
import { Typography, Button } from '../../components/Wrappers';
|
||||
|
||||
const CloseButton = ({ closeToast, className }) => (
|
||||
<CloseIcon
|
||||
className={className}
|
||||
onClick={closeToast}
|
||||
/>
|
||||
);
|
||||
|
||||
const NotificationsPage = ({ classes, ...props}) => (
|
||||
<React.Fragment>
|
||||
<PageTitle title="Notifications" />
|
||||
<Grid container spacing={32}>
|
||||
<ToastContainer className={classes.toastsContainer} closeButton={<CloseButton className={classes.notificationCloseButton} />} closeOnClick={false} progressClassName={classes.notificationProgress} />
|
||||
<Grid item xs={12} md={6} lg={4}>
|
||||
<Widget title="Layout Options" disableWidgetMenu>
|
||||
<Typography>There are few position options available for notifications. You can click any of them to change notifications position:</Typography>
|
||||
<div className={classes.layoutContainer}>
|
||||
<div className={classes.layoutButtonsRow}>
|
||||
<button onClick={() => props.changeNotificationPosition(0)} className={classnames(classes.layoutButton, { [classes.layoutButtonActive]: props.notificationsPosition === 0})} />
|
||||
<button onClick={() => props.changeNotificationPosition(1)} className={classnames(classes.layoutButton, { [classes.layoutButtonActive]: props.notificationsPosition === 1})} />
|
||||
<button onClick={() => props.changeNotificationPosition(2)} className={classnames(classes.layoutButton, { [classes.layoutButtonActive]: props.notificationsPosition === 2})} />
|
||||
</div>
|
||||
<Typography className={classes.layoutText} size="md">Click any position</Typography>
|
||||
<div className={classes.layoutButtonsRow}>
|
||||
<button onClick={() => props.changeNotificationPosition(3)} className={classnames(classes.layoutButton, { [classes.layoutButtonActive]: props.notificationsPosition === 3})} />
|
||||
<button onClick={() => props.changeNotificationPosition(4)} className={classnames(classes.layoutButton, { [classes.layoutButtonActive]: props.notificationsPosition === 4})} />
|
||||
<button onClick={() => props.changeNotificationPosition(5)} className={classnames(classes.layoutButton, { [classes.layoutButtonActive]: props.notificationsPosition === 5})} />
|
||||
</div>
|
||||
</div>
|
||||
</Widget>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6} lg={4}>
|
||||
<Widget title="Notifications Types" disableWidgetMenu>
|
||||
<Typography>Different types of notifications for lost of use cases. Custom classes are also supported.</Typography>
|
||||
<div className={classes.buttonsContainer}>
|
||||
<Button variant="contained" colortheme="primary" onClick={() => props.handleNotificationCall('info')} className={classnames(classes.notificationCallButton)}>Info Message</Button>
|
||||
<Button variant="contained" colortheme="warning" onClick={() => props.handleNotificationCall('error')} className={classnames(classes.notificationCallButton)}>Error + Retry Message</Button>
|
||||
<Button variant="contained" colortheme="success" onClick={() => props.handleNotificationCall('success')} className={classnames(classes.notificationCallButton)}>Success Message</Button>
|
||||
</div>
|
||||
</Widget>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6} lg={4}>
|
||||
<Widget title="Usage" disableWidgetMenu>
|
||||
<Typography>Notifications are created with the help of <a href="https://github.com/fkhadra/react-toastify">react-toastify</a></Typography>
|
||||
<div className={classes.codeContainer}>
|
||||
<SyntaxHighlighter className={classes.codeComponent} language='javascript' style={docco}>{`
|
||||
// import needed components, functions and styles
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
|
||||
const Page = () => {
|
||||
<div>
|
||||
<ToastContainer />
|
||||
<button onClick={() => toast('Toast Message')}>
|
||||
show notification
|
||||
</button>
|
||||
</div>
|
||||
};
|
||||
`}</SyntaxHighlighter>
|
||||
<Typography variant="caption">For more API information refer to the library documentation</Typography>
|
||||
</div>
|
||||
</Widget>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6} lg={4}>
|
||||
<Widget title="Notification Types Examples" disableWidgetMenu>
|
||||
<NotificationCustomComponent className={classes.notificationItem} shadowless type="message" message="Thanks for Checking out Messenger" variant="contained" color="secondary"/>
|
||||
<NotificationCustomComponent className={classes.notificationItem} shadowless type="feedback" message="New user feedback received" variant="contained" color="primary"/>
|
||||
<NotificationCustomComponent className={classes.notificationItem} shadowless type="customer" message="New customer is registered" variant="contained" color="success" />
|
||||
<NotificationCustomComponent className={classes.notificationItem} shadowless type="shipped" message="The order was shipped" variant="contained" color="warning" />
|
||||
<NotificationCustomComponent className={classes.notificationItem} shadowless type="delivered" message="The order was delivered" variant="contained" color="primary" />
|
||||
<NotificationCustomComponent className={classes.notificationItem} shadowless type="defence" message="5 Defence alerts" variant="contained" color="info" />
|
||||
</Widget>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6} lg={4}>
|
||||
<Widget title="Notification Types Examples" disableWidgetMenu>
|
||||
<NotificationCustomComponent className={classes.notificationItem} type="report" message="New report has been received" color="secondary" />
|
||||
<NotificationCustomComponent className={classes.notificationItem} type="feedback" message="New user feedback received" color="primary" />
|
||||
<NotificationCustomComponent className={classes.notificationItem} type="shipped" message="The item was shipped" color="success" />
|
||||
<NotificationCustomComponent className={classes.notificationItem} type="message" message="The new message from user @nahawaii" color="warning" />
|
||||
<NotificationCustomComponent className={classes.notificationItem} type="upload" message="Your file is ready to upload" color="primary" />
|
||||
<NotificationCustomComponent className={classes.notificationItem} type="disc" message="The disc is full" color="info" />
|
||||
</Widget>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6} lg={4}>
|
||||
<Widget title="Notification Types Examples" disableWidgetMenu>
|
||||
<NotificationCustomComponent className={classes.notificationItem} type="report" message="New report has been received" variant="rounded" color="secondary" />
|
||||
<NotificationCustomComponent className={classes.notificationItem} type="feedback" message="New user feedback received" variant="rounded" color="primary" />
|
||||
<NotificationCustomComponent className={classes.notificationItem} type="shipped" message="The item was shipped" variant="rounded" color="success" />
|
||||
<NotificationCustomComponent className={classes.notificationItem} type="message" message="The new message from user @nahawaii" variant="rounded" color="warning" />
|
||||
<NotificationCustomComponent className={classes.notificationItem} type="upload" message="Your file is ready to upload" variant="rounded" color="primary" />
|
||||
<NotificationCustomComponent className={classes.notificationItem} type="disc" message="The disc is full" variant="rounded" color="info" />
|
||||
</Widget>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
const styles = (theme) => ({
|
||||
layoutContainer: {
|
||||
height: 200,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginTop: theme.spacing.unit * 2,
|
||||
border: '1px dashed',
|
||||
borderColor: theme.palette.primary.main,
|
||||
position: 'relative',
|
||||
},
|
||||
layoutText: {
|
||||
color: tinycolor(theme.palette.background.light).darken().toHexString(),
|
||||
},
|
||||
layoutButtonsRow: {
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
layoutButton: {
|
||||
backgroundColor: theme.palette.background.light,
|
||||
width: 125,
|
||||
height: 50,
|
||||
outline: 'none',
|
||||
border: 'none',
|
||||
},
|
||||
layoutButtonActive: {
|
||||
backgroundColor: tinycolor(theme.palette.background.light).darken().toHexString(),
|
||||
},
|
||||
buttonsContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
marginTop: theme.spacing.unit * 2,
|
||||
},
|
||||
notificationCallButton: {
|
||||
color: 'white',
|
||||
marginBottom: theme.spacing.unit,
|
||||
textTransform: 'none',
|
||||
},
|
||||
codeContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
marginTop: theme.spacing.unit * 2,
|
||||
},
|
||||
codeComponent: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
notificationItem: {
|
||||
marginTop: theme.spacing.unit * 2,
|
||||
},
|
||||
notificationCloseButton: {
|
||||
position: 'absolute',
|
||||
right: theme.spacing.unit * 2,
|
||||
},
|
||||
toastsContainer: {
|
||||
width: 400,
|
||||
marginTop: theme.spacing.unit * 6,
|
||||
right: 0,
|
||||
}
|
||||
});
|
||||
|
||||
export default withStyles(styles, { withTheme: true})(NotificationsPage);
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "Notifications",
|
||||
"version": "0.0.0",
|
||||
"main": "Notifications.js",
|
||||
"private": true
|
||||
}
|
||||
82
frontend/react-material-admin/src/pages/notifications/styles.js
vendored
Normal file
82
frontend/react-material-admin/src/pages/notifications/styles.js
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import { makeStyles } from "@material-ui/styles";
|
||||
import tinycolor from "tinycolor2";
|
||||
|
||||
export default makeStyles(theme => ({
|
||||
layoutContainer: {
|
||||
height: 200,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginTop: theme.spacing(2),
|
||||
border: "1px dashed",
|
||||
borderColor: theme.palette.primary.main,
|
||||
position: "relative",
|
||||
},
|
||||
layoutText: {
|
||||
color: tinycolor(theme.palette.background.light)
|
||||
.darken()
|
||||
.toHexString(),
|
||||
},
|
||||
layoutButtonsRow: {
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
layoutButton: {
|
||||
backgroundColor: theme.palette.background.light,
|
||||
width: 125,
|
||||
height: 50,
|
||||
outline: "none",
|
||||
border: "none",
|
||||
},
|
||||
layoutButtonActive: {
|
||||
backgroundColor: tinycolor(theme.palette.background.light)
|
||||
.darken()
|
||||
.toHexString(),
|
||||
},
|
||||
buttonsContainer: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "flex-start",
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
notificationCallButton: {
|
||||
color: "white",
|
||||
marginBottom: theme.spacing(1),
|
||||
textTransform: "none",
|
||||
},
|
||||
codeContainer: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
codeComponent: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
notificationItem: {
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
notificationCloseButton: {
|
||||
position: "absolute",
|
||||
right: theme.spacing(2),
|
||||
},
|
||||
toastsContainer: {
|
||||
width: 400,
|
||||
marginTop: theme.spacing(6),
|
||||
right: 0,
|
||||
},
|
||||
progress: {
|
||||
visibility: "hidden",
|
||||
},
|
||||
notification: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
background: "transparent",
|
||||
boxShadow: "none",
|
||||
overflow: "visible",
|
||||
},
|
||||
notificationComponent: {
|
||||
paddingRight: theme.spacing(4),
|
||||
},
|
||||
}));
|
||||
101
frontend/react-material-admin/src/pages/tables/Tables.js
vendored
Normal file
101
frontend/react-material-admin/src/pages/tables/Tables.js
vendored
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Grid } from "@material-ui/core";
|
||||
import MUIDataTable from "mui-datatables";
|
||||
|
||||
// components
|
||||
import PageTitle from "../../components/PageTitle";
|
||||
import Widget from "../../components/Widget";
|
||||
import Table from "../dashboard/components/Table/Table";
|
||||
|
||||
// data
|
||||
import mock from "../dashboard/mock";
|
||||
|
||||
import { HelloRequest,
|
||||
HelloReply,
|
||||
GetFollowedSqueakDisplaysRequest,
|
||||
GetFollowedSqueakDisplaysReply } from "../../proto/squeak_admin_pb"
|
||||
import { SqueakAdminClient } from "../../proto/squeak_admin_grpc_web_pb"
|
||||
|
||||
var client = new SqueakAdminClient('http://' + window.location.hostname + ':8080')
|
||||
|
||||
const datatableData = [
|
||||
["Joe James", "Example Inc.", "Yonkers", "NY"],
|
||||
["John Walsh", "Example Inc.", "Hartford", "CT"],
|
||||
["Bob Herm", "Example Inc.", "Tampa", "FL"],
|
||||
["James Houston", "Example Inc.", "Dallas", "TX"],
|
||||
["Prabhakar Linwood", "Example Inc.", "Hartford", "CT"],
|
||||
["Kaui Ignace", "Example Inc.", "Yonkers", "NY"],
|
||||
["Esperanza Susanne", "Example Inc.", "Hartford", "CT"],
|
||||
["Christian Birgitte", "Example Inc.", "Tampa", "FL"],
|
||||
["Meral Elias", "Example Inc.", "Hartford", "CT"],
|
||||
["Deep Pau", "Example Inc.", "Yonkers", "NY"],
|
||||
["Sebastiana Hani", "Example Inc.", "Dallas", "TX"],
|
||||
["Marciano Oihana", "Example Inc.", "Yonkers", "NY"],
|
||||
["Brigid Ankur", "Example Inc.", "Dallas", "TX"],
|
||||
["Anna Siranush", "Example Inc.", "Yonkers", "NY"],
|
||||
["Avram Sylva", "Example Inc.", "Hartford", "CT"],
|
||||
["Serafima Babatunde", "Example Inc.", "Tampa", "FL"],
|
||||
["Gaston Festus", "Example Inc.", "Tampa", "FL"],
|
||||
];
|
||||
|
||||
export default function Tables() {
|
||||
const [msg, setMsg] = useState("waiting for message...");
|
||||
const [squeaks, setSqueaks] = useState([]);
|
||||
const getMsg = () => {
|
||||
console.log("called getMsg");
|
||||
|
||||
var helloRequest = new HelloRequest()
|
||||
helloRequest.setName('World');
|
||||
|
||||
client.sayHello(helloRequest, {}, (err, response) => {
|
||||
console.log(response.getMessage());
|
||||
setMsg(response.getMessage())
|
||||
});
|
||||
};
|
||||
const getSqueaks = () => {
|
||||
console.log("called getSqueaks");
|
||||
|
||||
var getSqueaksRequest = new GetFollowedSqueakDisplaysRequest()
|
||||
|
||||
client.getFollowedSqueakDisplays(getSqueaksRequest, {}, (err, response) => {
|
||||
console.log(response);
|
||||
console.log(response.getSqueakDisplayEntriesList());
|
||||
// console.log(response.getSqueakDisplayEntriesList(),length);
|
||||
setSqueaks(response.getSqueakDisplayEntriesList())
|
||||
});
|
||||
};
|
||||
useEffect(()=>{
|
||||
getMsg()
|
||||
},[]);
|
||||
useEffect(()=>{
|
||||
getSqueaks()
|
||||
},[]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title="Tables" />
|
||||
<Grid container spacing={4}>
|
||||
<Grid item xs={12}>
|
||||
<MUIDataTable
|
||||
title="Employee List"
|
||||
data={datatableData}
|
||||
columns={["Name", "Company", "City", "State"]}
|
||||
options={{
|
||||
filterType: "checkbox",
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Widget title="Material-UI Table" upperTitle noBodyPadding>
|
||||
<Table data={mock.table} />
|
||||
</Widget>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<div>
|
||||
Message : {msg}
|
||||
</div>
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "Tables",
|
||||
"version": "0.0.0",
|
||||
"main": "Tables.js",
|
||||
"private": true
|
||||
}
|
||||
126
frontend/react-material-admin/src/pages/typography/Typography.js
vendored
Normal file
126
frontend/react-material-admin/src/pages/typography/Typography.js
vendored
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
import React from "react";
|
||||
import { Grid } from "@material-ui/core";
|
||||
|
||||
// styles
|
||||
import useStyles from "./styles";
|
||||
|
||||
// components
|
||||
import PageTitle from "../../components/PageTitle";
|
||||
import Widget from "../../components/Widget";
|
||||
import { Typography } from "../../components/Wrappers";
|
||||
|
||||
export default function TypographyPage() {
|
||||
var classes = useStyles();
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title="Typography" />
|
||||
<Grid container spacing={4}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Widget title="Headings" disableWidgetMenu>
|
||||
<div className={classes.dashedBorder}>
|
||||
<Typography variant="h1" className={classes.text}>
|
||||
h1. Heading
|
||||
</Typography>
|
||||
<Typography variant="h2" className={classes.text}>
|
||||
h2. Heading
|
||||
</Typography>
|
||||
<Typography variant="h3" className={classes.text}>
|
||||
h3. Heading
|
||||
</Typography>
|
||||
<Typography variant="h4" className={classes.text}>
|
||||
h4. Heading
|
||||
</Typography>
|
||||
<Typography variant="h5" className={classes.text}>
|
||||
h5. Heading
|
||||
</Typography>
|
||||
<Typography variant="h6">h6. Heading</Typography>
|
||||
</div>
|
||||
</Widget>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Widget title="Typography Colors" disableWidgetMenu>
|
||||
<div className={classes.dashedBorder}>
|
||||
<Typography variant="h1" color="primary" className={classes.text}>
|
||||
h1. Heading
|
||||
</Typography>
|
||||
<Typography variant="h2" color="success" className={classes.text}>
|
||||
h2. Heading
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h3"
|
||||
color="secondary"
|
||||
className={classes.text}
|
||||
>
|
||||
h3. Heading
|
||||
</Typography>
|
||||
<Typography variant="h4" color="warning" className={classes.text}>
|
||||
h4. Heading
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h5"
|
||||
color="primary"
|
||||
colorBrightness="light"
|
||||
className={classes.text}
|
||||
>
|
||||
h5. Heading
|
||||
</Typography>
|
||||
<Typography variant="h6" color="info">
|
||||
h6. Heading
|
||||
</Typography>
|
||||
</div>
|
||||
</Widget>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Widget title="Basic Text Settings" disableWidgetMenu>
|
||||
<div className={classes.dashedBorder}>
|
||||
<Typography className={classes.text}>Basic text</Typography>
|
||||
<Typography className={classes.text} weight="light">
|
||||
Basic light text
|
||||
</Typography>
|
||||
<Typography className={classes.text} weight="medium">
|
||||
Basic medium text
|
||||
</Typography>
|
||||
<Typography className={classes.text} weight="bold">
|
||||
Basic bold text
|
||||
</Typography>
|
||||
<Typography className={classes.text}>
|
||||
BASIC UPPERCASE TEXT
|
||||
</Typography>
|
||||
<Typography className={classes.text}>
|
||||
basic lowercase text
|
||||
</Typography>
|
||||
<Typography className={classes.text}>
|
||||
Basic Capitalized Text
|
||||
</Typography>
|
||||
<Typography>
|
||||
<i>Basic Cursive Text</i>
|
||||
</Typography>
|
||||
</div>
|
||||
</Widget>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Widget title="Text Size" disableWidgetMenu>
|
||||
<div className={classes.dashedBorder}>
|
||||
<Typography className={classes.text} size="sm">
|
||||
Heading Typography SM Font Size
|
||||
</Typography>
|
||||
<Typography className={classes.text}>
|
||||
Heading Typography Regular Font Size
|
||||
</Typography>
|
||||
<Typography className={classes.text} size="md">
|
||||
Heading Typography MD Font Size
|
||||
</Typography>
|
||||
<Typography className={classes.text} size="xl">
|
||||
Heading Typography XL Font Size
|
||||
</Typography>
|
||||
<Typography className={classes.text} size="xxl">
|
||||
Heading Typography XXL Font Size
|
||||
</Typography>
|
||||
</div>
|
||||
</Widget>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "Typography",
|
||||
"version": "0.0.0",
|
||||
"main": "Typography.js",
|
||||
"private": true
|
||||
}
|
||||
15
frontend/react-material-admin/src/pages/typography/styles.js
vendored
Normal file
15
frontend/react-material-admin/src/pages/typography/styles.js
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { makeStyles } from "@material-ui/styles";
|
||||
|
||||
export default makeStyles(theme => ({
|
||||
dashedBorder: {
|
||||
border: "1px dashed",
|
||||
borderColor: theme.palette.primary.main,
|
||||
padding: theme.spacing(2),
|
||||
paddingTop: theme.spacing(4),
|
||||
paddingBottom: theme.spacing(4),
|
||||
marginTop: theme.spacing(1),
|
||||
},
|
||||
text: {
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
}));
|
||||
135
frontend/react-material-admin/src/serviceWorker.js
vendored
Executable file
135
frontend/react-material-admin/src/serviceWorker.js
vendored
Executable file
|
|
@ -0,0 +1,135 @@
|
|||
// This optional code is used to register a service worker.
|
||||
// register() is not called by default.
|
||||
|
||||
// This lets the app load faster on subsequent visits in production, and gives
|
||||
// it offline capabilities. However, it also means that developers (and users)
|
||||
// will only see deployed updates on subsequent visits to a page, after all the
|
||||
// existing tabs open on the page have been closed, since previously cached
|
||||
// resources are updated in the background.
|
||||
|
||||
// To learn more about the benefits of this model and instructions on how to
|
||||
// opt-in, read http://bit.ly/CRA-PWA
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === 'localhost' ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === '[::1]' ||
|
||||
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||
window.location.hostname.match(
|
||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||
)
|
||||
);
|
||||
|
||||
export function register(config) {
|
||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||
// The URL constructor is available in all browsers that support SW.
|
||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
|
||||
if (publicUrl.origin !== window.location.origin) {
|
||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||
// from what our page is served on. This might happen if a CDN is used to
|
||||
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||
|
||||
if (isLocalhost) {
|
||||
// This is running on localhost. Let's check if a service worker still exists or not.
|
||||
checkValidServiceWorker(swUrl, config);
|
||||
|
||||
// Add some additional logging to localhost, pointing developers to the
|
||||
// service worker/PWA documentation.
|
||||
navigator.serviceWorker.ready.then(() => {
|
||||
console.log(
|
||||
'This web app is being served cache-first by a service ' +
|
||||
'worker. To learn more, visit http://bit.ly/CRA-PWA'
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// Is not localhost. Just register service worker
|
||||
registerValidSW(swUrl, config);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function registerValidSW(swUrl, config) {
|
||||
navigator.serviceWorker
|
||||
.register(swUrl)
|
||||
.then(registration => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing;
|
||||
if (installingWorker == null) {
|
||||
return;
|
||||
}
|
||||
installingWorker.onstatechange = () => {
|
||||
if (installingWorker.state === 'installed') {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// At this point, the updated precached content has been fetched,
|
||||
// but the previous service worker will still serve the older
|
||||
// content until all client tabs are closed.
|
||||
console.log(
|
||||
'New content is available and will be used when all ' +
|
||||
'tabs for this page are closed. See http://bit.ly/CRA-PWA.'
|
||||
);
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onUpdate) {
|
||||
config.onUpdate(registration);
|
||||
}
|
||||
} else {
|
||||
// At this point, everything has been precached.
|
||||
// It's the perfect time to display a
|
||||
// "Content is cached for offline use." message.
|
||||
console.log('Content is cached for offline use.');
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onSuccess) {
|
||||
config.onSuccess(registration);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error during service worker registration:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function checkValidServiceWorker(swUrl, config) {
|
||||
// Check if the service worker can be found. If it can't reload the page.
|
||||
fetch(swUrl)
|
||||
.then(response => {
|
||||
// Ensure service worker exists, and that we really are getting a JS file.
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (
|
||||
response.status === 404 ||
|
||||
(contentType != null && contentType.indexOf('javascript') === -1)
|
||||
) {
|
||||
// No service worker found. Probably a different app. Reload the page.
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Service worker found. Proceed as normal.
|
||||
registerValidSW(swUrl, config);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.log(
|
||||
'No internet connection found. App is running in offline mode.'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function unregister() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister();
|
||||
});
|
||||
}
|
||||
}
|
||||
132
frontend/react-material-admin/src/themes/default.js
vendored
Normal file
132
frontend/react-material-admin/src/themes/default.js
vendored
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
import tinycolor from "tinycolor2";
|
||||
|
||||
const primary = "#536DFE";
|
||||
const secondary = "#FF5C93";
|
||||
const warning = "#FFC260";
|
||||
const success = "#3CD4A0";
|
||||
const info = "#9013FE";
|
||||
|
||||
const lightenRate = 7.5;
|
||||
const darkenRate = 15;
|
||||
|
||||
export default {
|
||||
palette: {
|
||||
primary: {
|
||||
main: primary,
|
||||
light: tinycolor(primary)
|
||||
.lighten(lightenRate)
|
||||
.toHexString(),
|
||||
dark: tinycolor(primary)
|
||||
.darken(darkenRate)
|
||||
.toHexString(),
|
||||
},
|
||||
secondary: {
|
||||
main: secondary,
|
||||
light: tinycolor(secondary)
|
||||
.lighten(lightenRate)
|
||||
.toHexString(),
|
||||
dark: tinycolor(secondary)
|
||||
.darken(darkenRate)
|
||||
.toHexString(),
|
||||
contrastText: "#FFFFFF",
|
||||
},
|
||||
warning: {
|
||||
main: warning,
|
||||
light: tinycolor(warning)
|
||||
.lighten(lightenRate)
|
||||
.toHexString(),
|
||||
dark: tinycolor(warning)
|
||||
.darken(darkenRate)
|
||||
.toHexString(),
|
||||
},
|
||||
success: {
|
||||
main: success,
|
||||
light: tinycolor(success)
|
||||
.lighten(lightenRate)
|
||||
.toHexString(),
|
||||
dark: tinycolor(success)
|
||||
.darken(darkenRate)
|
||||
.toHexString(),
|
||||
},
|
||||
info: {
|
||||
main: info,
|
||||
light: tinycolor(info)
|
||||
.lighten(lightenRate)
|
||||
.toHexString(),
|
||||
dark: tinycolor(info)
|
||||
.darken(darkenRate)
|
||||
.toHexString(),
|
||||
},
|
||||
text: {
|
||||
primary: "#4A4A4A",
|
||||
secondary: "#6E6E6E",
|
||||
hint: "#B9B9B9",
|
||||
},
|
||||
background: {
|
||||
default: "#F6F7FF",
|
||||
light: "#F3F5FF",
|
||||
},
|
||||
},
|
||||
customShadows: {
|
||||
widget:
|
||||
"0px 3px 11px 0px #E8EAFC, 0 3px 3px -2px #B2B2B21A, 0 1px 8px 0 #9A9A9A1A",
|
||||
widgetDark:
|
||||
"0px 3px 18px 0px #4558A3B3, 0 3px 3px -2px #B2B2B21A, 0 1px 8px 0 #9A9A9A1A",
|
||||
widgetWide:
|
||||
"0px 12px 33px 0px #E8EAFC, 0 3px 3px -2px #B2B2B21A, 0 1px 8px 0 #9A9A9A1A",
|
||||
},
|
||||
overrides: {
|
||||
MuiBackdrop: {
|
||||
root: {
|
||||
backgroundColor: "#4A4A4A1A",
|
||||
},
|
||||
},
|
||||
MuiMenu: {
|
||||
paper: {
|
||||
boxShadow:
|
||||
"0px 3px 11px 0px #E8EAFC, 0 3px 3px -2px #B2B2B21A, 0 1px 8px 0 #9A9A9A1A",
|
||||
},
|
||||
},
|
||||
MuiSelect: {
|
||||
icon: {
|
||||
color: "#B9B9B9",
|
||||
},
|
||||
},
|
||||
MuiListItem: {
|
||||
root: {
|
||||
"&$selected": {
|
||||
backgroundColor: "#F3F5FF !important",
|
||||
"&:focus": {
|
||||
backgroundColor: "#F3F5FF",
|
||||
},
|
||||
},
|
||||
},
|
||||
button: {
|
||||
"&:hover, &:focus": {
|
||||
backgroundColor: "#F3F5FF",
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiTouchRipple: {
|
||||
child: {
|
||||
backgroundColor: "white",
|
||||
},
|
||||
},
|
||||
MuiTableRow: {
|
||||
root: {
|
||||
height: 56,
|
||||
},
|
||||
},
|
||||
MuiTableCell: {
|
||||
root: {
|
||||
borderBottom: "1px solid rgba(224, 224, 224, .5)",
|
||||
},
|
||||
head: {
|
||||
fontSize: "0.95rem",
|
||||
},
|
||||
body: {
|
||||
fontSize: "0.95rem",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
30
frontend/react-material-admin/src/themes/index.js
vendored
Normal file
30
frontend/react-material-admin/src/themes/index.js
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import defaultTheme from "./default";
|
||||
|
||||
import { createMuiTheme } from "@material-ui/core";
|
||||
|
||||
const overrides = {
|
||||
typography: {
|
||||
h1: {
|
||||
fontSize: "3rem",
|
||||
},
|
||||
h2: {
|
||||
fontSize: "2rem",
|
||||
},
|
||||
h3: {
|
||||
fontSize: "1.64rem",
|
||||
},
|
||||
h4: {
|
||||
fontSize: "1.5rem",
|
||||
},
|
||||
h5: {
|
||||
fontSize: "1.285rem",
|
||||
},
|
||||
h6: {
|
||||
fontSize: "1.142rem",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
default: createMuiTheme({ ...defaultTheme, ...overrides }),
|
||||
};
|
||||
10919
frontend/react-material-admin/yarn.lock
Normal file
10919
frontend/react-material-admin/yarn.lock
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue